Skip to content
Telemetry
Browse docs
Discussion TopicsUpdated July 29, 2026Reviewed by the Telemetry editorial and product teams7 min read

Use this doc with your coding agent

Open a focused prompt pack for Claude Code, Codex, Cursor, or another coding agent, then adapt it to the workflow covered here.

On this page
  1. Start with the event grain
  2. Use a field taxonomy
  3. Three practical event shapes
  4. Keep the privacy boundary narrow
  5. Correlate instead of duplicating
  6. Plan schema evolution before launch
  7. Sample based on decisions, not convenience
  8. Validate the event with SQL
  9. Migrate one workflow at a time

Canonical Wide Events

A canonical wide event describes one completed unit of work with the context needed to explain its outcome. Instead of reconstructing a request from disconnected “started,” “database called,” and “finished” messages, the application emits one request outcome containing its route, account, release, duration, status, and categorized failure context.

This pattern is also called a canonical log line, structured event, or wide event. Stripe described canonical log lines as a way to collect the important context for a request in one place. Honeycomb uses context-rich structured events as the basis for observability. The OpenTelemetry Logs Data Model provides a standard representation that can correlate logs with traces. The names and transport differ, but the useful design question is the same: can one record explain a meaningful outcome without searching through a narrative?

“Wide” means the event may carry many purposeful fields. It does not mean copying every object in memory.

Start with the event grain

The event grain is the thing represented by one row. Write it down before choosing fields. Useful grains include:

  • one API request reached a terminal outcome
  • one background job completed or exhausted its retries
  • one webhook delivery was processed, rejected, or deduplicated
  • one agent run completed, failed, or reached a safety limit
  • one account reached an activation, billing, or retention milestone
  • one database operation completed with a bounded fingerprint

Avoid mixing grains in one table. If one row sometimes means a request attempt and sometimes means a logical request across all retries, counts and rates become ambiguous. Use a separate attempt_number or a separate attempt event when both views are required.

Build the canonical event over the lifecycle of the operation and emit it when the final outcome is known:

const outcome = {
  request_id: requestId,
  route_template: "/api/projects/:id/sync",
  method: "POST",
  team_id: teamId,
  release: process.env.APP_RELEASE,
  environment: "production",
  started_at: new Date().toISOString(),
};

try {
  await syncProject();
  await telemetry.log("api_request_completed", {
    ...outcome,
    status: "success",
    status_code: 200,
    latency_ms: Math.round(performance.now() - startedAt),
  });
} catch (error) {
  await telemetry.log("api_request_completed", {
    ...outcome,
    status: "failed",
    status_code: statusFor(error),
    error_type: classifyError(error),
    latency_ms: Math.round(performance.now() - startedAt),
  });
  throw error;
}

Instrumentation delivery should not turn a successful request into a failed request. Use bounded timeouts, observe delivery failures separately, and decide explicitly which critical events require a durable queue.

Use a field taxonomy

A useful canonical event usually draws from six field groups:

Group Examples Why it exists
Identity event_id, request_id, run_id Deduplicate and find one outcome
Grain and outcome event_name, status, error_type, attempt_number Define what is counted
Timing timestamp_utc, duration_ms, queue_wait_ms Build rates and latency distributions
Product context feature, plan, workflow, route_template Connect reliability to user-facing behavior
Deployment context service, environment, region, release Compare changes and isolate regressions
Correlation trace_id, job_id, team_id Move to deeper evidence or join related events

Use controlled categories for fields that will be grouped. error_type: "dependency_timeout" is more reliable than a raw exception message. Use explicit units in names: _ms, _bytes, _usd, and _count. Use normalized route templates instead of raw URLs.

Identifiers such as request IDs, account IDs, and trace IDs are high cardinality. That is often correct: they are valuable for filtering and correlation even when they are poor chart dimensions. Keep them only when the investigation benefit justifies the privacy, storage, and query cost. See High-Cardinality Fields.

Three practical event shapes

An API request event should keep the denominator and outcome together:

{
  "event_name": "api_request_completed",
  "request_id": "req_7d91",
  "route_template": "/api/projects/:id/sync",
  "method": "POST",
  "status_code": 503,
  "status": "failed",
  "error_type": "dependency_timeout",
  "latency_ms": 8420,
  "release": "2026.07.4",
  "schema_version": 2
}

A background-job event should make retry grain explicit:

{
  "event_name": "job_completed",
  "job_id": "job_82f1",
  "job_name": "sync_billing_account",
  "queue_name": "billing",
  "status": "failed",
  "terminal": true,
  "attempt_number": 4,
  "queue_wait_ms": 1820,
  "duration_ms": 9612,
  "error_type": "provider_timeout"
}

An agent-run event should separate operational outcomes from sensitive content:

{
  "event_name": "agent_run_completed",
  "run_id": "run_28bd",
  "workflow": "support_resolution",
  "agent_name": "support_agent",
  "model": "approved_model_alias",
  "status": "success",
  "tool_call_count": 3,
  "retry_count": 1,
  "duration_ms": 4820,
  "accepted": true,
  "prompt_version": "support-v4"
}

Do not log raw prompts, completions, tool arguments, or retrieved documents by default. The outcome event can answer volume, reliability, cost, and acceptance questions without retaining customer content.

Keep the privacy boundary narrow

Treat every field as data that may appear in a query result, dashboard, export, or support workflow. Use an allowlist at event construction time. Do not include authorization headers, cookies, credentials, connection strings, request or response bodies, webhook payloads, payment details, or unrestricted customer content.

Prefer an internal account identifier over an email address, a route template over a full URL, and a controlled error category over exception text. Hashing personal data does not automatically make it safe; stable hashes can still be linkable identifiers. Document ownership, purpose, retention, and deletion expectations in an event tracking plan.

Correlate instead of duplicating

A canonical wide event complements metrics, traces, and detailed diagnostic logs. It does not need to reproduce them.

  • Metrics remain efficient for aggregate service health and infrastructure alerting.
  • Traces show timing and causality across spans.
  • Diagnostic logs preserve local details such as a stack trace.
  • Canonical events preserve the completed application or business outcome.

Attach an approved trace_id or correlation ID when deeper evidence lives elsewhere. Responders can move from a failed outcome row to its trace without copying a span waterfall or stack trace into the event. The logs, metrics, and traces guide covers the boundaries in more detail.

Plan schema evolution before launch

Give the event an owner and a schema_version. Add optional fields before making them required. Never silently change a numeric field into a string or reuse a field name for a different meaning. During a migration, support both schema versions in SQL until producers and historical windows have converged.

Keep controlled categories bounded. If a new error category appears, review whether it changes a dashboard, alert, or runbook. If an application upgrade renames a route or workflow, preserve a stable analytical name separately from the implementation name.

The schema evolution guide and data types and nullability explain these rollout choices.

Sample based on decisions, not convenience

Do not sample away rare failures, terminal job outcomes, billing changes, security actions, or events used for exact reconciliation. High-volume successful requests may be candidates for deterministic or rate-based sampling, but retain the sampling decision and weight if the downstream analysis needs estimated totals.

Compare the storage saved with the questions lost. Sampling can preserve latency distributions while making exact account-impact counts impossible. The event sampling guide describes safe and unsafe cases.

Validate the event with SQL

An event is complete when it answers its intended questions with defensible SQL, not when it contains the most fields. Exercise success, failure, retry, timeout, duplicate-delivery, null-field, and late-arrival paths in a non-production environment. Inspect the stored schema before building a dashboard.

For an API outcome event, begin with a count and denominator:

SELECT
  route_template,
  COUNT(*) AS requests,
  SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failures,
  100.0 * SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END)
    / NULLIF(COUNT(*), 0) AS failure_rate_pct,
  approx_percentile_cont(latency_ms, 0.95) AS p95_latency_ms
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
  AND environment = 'production'
GROUP BY route_template
HAVING COUNT(*) >= 20
ORDER BY failure_rate_pct DESC;

Keep volume beside rates, exclude incomplete time buckets when comparing periods, and state whether retries are attempts or logical outcomes. Store a deterministic fixture and expected result for queries that become operationally important. The instrumentation testing in CI guide shows how to keep the contract from drifting.

Migrate one workflow at a time

Do not replace an entire log stream. Choose one recurring decision, emit its canonical event beside existing telemetry, and dual-run the old and new answers over the same closed UTC window. Investigate differences in retry handling, route normalization, timestamps, nulls, and exclusions. Promote the new query to a dashboard or alert only after its owner accepts the semantics.

The practical path is:

  1. Define the event grain and decision.
  2. Write the allowlisted field contract.
  3. Instrument terminal outcomes.
  4. Verify delivery and schema.
  5. Test the query with fixtures.
  6. Dual-run reports or alerts.
  7. Retire only the redundant consumer.

Continue with the structured log management guide, structured events versus text logs, or the complete migration guide.

Related product capability

Capture stable event names, typed fields, and privacy-reviewed context.

Ownership and technical references

The Telemetry editorial team owns this explanation; the product team reviews behavior, examples, and boundaries.

Review the editorial standard