Skip to content

Playbook · 5 steps

Structured Logging

Also called: JSON logging · Structured logs

5steps

Level
Practitioner
Reading time
About 6 min

Reviewed

Definition
Writing logs as machine-readable key-value records, usually JSON, so every event can be searched, correlated and alerted on by field.

01 · How it works, in 5 steps

How does structured logging work?

  1. 1

    Use a JSON logger

    Configure your logging library to write one JSON object per line to stdout, with a timestamp, level, service name and message, and let the platform ship stdout to the log store.

  2. 2

    Agree a field schema

    Fix the names once — wamid, phone_number_id, template.name, meta_error.code, trace_id — and use them in every service so queries work across the stack. OpenTelemetry's semantic conventions are a sensible base.

  3. 3

    Propagate context

    Bind request-scoped fields such as the trace ID, tenant and order ID to a child logger at the start of each request or job, so every line inherits them automatically. Reuse the trace ID from your distributed tracing, so a log line opens its matching trace.

  4. 4

    Mask at the source

    Redact or hash phone numbers, names, OTP codes and message bodies in the logger configuration, before anything is written. Keep the last few digits if support needs to confirm a customer.

  5. 5

    Log events, not chatter

    Emit one line per meaningful event — send attempted, API error, status received, retry scheduled — at the right level, and sample high-volume success lines if cost demands it.

02Try it

Hands-on: use the controls to work through it yourself.

Click through one structured log line

9 / 9
{
"ts": "2026-10-19T10:42:07.318+05:30",
"level": "warn",
"service": "whatsapp-webhooks",
"event": "message_status",
"msg": "Status update: failed",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"order_id": "ORD-58213",
"phone_number_id": "106540352242922",
"wamid": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAEhggNDRCQ0Q1RkM",
"status": "failed",
"template": {
"name": "delivery_slot",
"language": "en"
},
"recipient": "91XXXXXX4821",
"meta_error": {
"code": 131026,
"title": "Message undeliverable"
},
"lag_ms": 1840
}
A real-shaped log record for a failed WhatsApp delivery. Select each field to see why it is there, how it gets queried and which value was masked before it was written.

Field 9: lag_ms — How long after Meta's status timestamp the handler processed it: a cheap early signal of webhook backlog.

03What is structured logging?

Structured logging means emitting log events as records with named fields rather than as sentences. Instead of a line such as 'Failed to send order_update to customer: error 131026', the service writes one JSON object per line with level, event, template, phone_number_id, meta_error.code and a masked recipient. Each field can then be filtered, grouped, counted and alerted on without fragile text parsing.

In a WhatsApp integration the most valuable fields are the platform's own identifiers: the wamid (WhatsApp Message ID) returned by every send and repeated in every status webhook, the phone_number_id of the sending number, the template name and language, the Graph API error.code, and the fbtrace_id that Meta support asks for. Add your own order_id or ticket_id and a trace_id that follows the request through queues and workers, and a single search reconstructs everything that happened to one message.

Structure also makes privacy enforceable. Phone numbers, names and message bodies are personal data under India's DPDP Act, and logs get copied, shipped and retained widely, so PII masking in the logger — not in the log viewer — keeps them out of the store entirely. This is operational logging for debugging and alerting; an audit log, which records who did what for accountability, has different completeness and retention rules.

Key takeaways

  • Structured logging writes each log event as named fields, usually one JSON object per line, instead of free-form sentences.
  • For WhatsApp, log the wamid, phone_number_id, template name, Meta error code and fbtrace_id as separate fields.
  • Mask phone numbers and leave message content out before anything reaches the log store: logs hold personal data too.
  • A shared trace_id links the API call, the queue job and the status webhook into one searchable story.
  • Operational logs are for debugging and may be sampled; an audit log records who did what and must be complete.

04 · In code

Structured logging in code

import pino from "pino";
const log = pino({
base: { service: "whatsapp-sender" },
redact: {
paths: ["payload.to", "payload.template.components", "recipient", "*.otp"],
censor: "[masked]",
},
});
export async function sendTemplate(job) {
const jlog = log.child({ trace_id: job.traceId, order_id: job.orderId });
const res = await callCloudApi(job.payload); // POST /{PHONE_NUMBER_ID}/messages
if (res.error) {
jlog.warn({
event: "send_failed",
phone_number_id: job.phoneNumberId,
template: { name: job.payload.template.name },
meta_error: { code: res.error.code, fbtrace_id: res.error.fbtrace_id },
recipient_last4: job.payload.to.slice(-4),
}, "Cloud API rejected the template send");
return;
}
jlog.info({ event: "send_accepted", wamid: res.messages[0].id }, "Template accepted");
}

Values go in fields, the message text stays constant, and redaction runs inside the logger so masked data never reaches stdout.

05 · Why it matters

Why does structured logging matter?

When a customer says 'I never got my OTP', unstructured logs mean grepping text across services and hoping the formats match. With structured logs, one query by order ID or wamid shows the send, the API response, the retry and the status webhook in order, and the same fields drive dashboards and alerts — error 130429 spiking, delivered statuses drying up for one number. Structured logs are also the raw material for SLIs, so the reliability dashboard and the evidence in the logs always agree — and, with metrics and traces, they are one of the three signals that observability is built on.

06 · Mistakes to avoid

Common mistakes with structured logging

  • Logging full payloads

    Dumping whole webhook bodies or send requests puts phone numbers, names and message text in a store many people can read. Log identifiers and outcomes; mask or drop content.

  • Inconsistent field names

    wamid in one service, messageId in another and msg_id in a third make cross-service queries impossible. Publish a schema and check it in code review or a linter.

  • Interpolating values into the message

    log.info('sent ' + wamid) buries the value in text again. Keep the message constant and put values in fields, so lines group cleanly.

  • No correlation ID

    Without a trace ID carried through the API call, the queue and the webhook handler, each service's logs are an island. Generate it at the edge and propagate it everywhere.

  • Using debug logs as the audit trail

    Operational logs get sampled, rotated and dropped under load. Records of who edited a template or exported contacts belong in a separate, complete audit log.

07 · Key facts

Structured logging: key facts and limits

Format
One JSON object per line (JSON Lines) on stdout
Correlation keys
wamid, phone_number_id, trace_id, order_id
Error fields
meta_error.code, error_subcode, fbtrace_id
Mask before writing
Phone numbers, names, OTP codes, message bodies
Trace context standard
W3C traceparent (trace ID and span ID)
CERT-In directions (India)
Covered entities keep ICT system logs for a rolling 180 days

08Example: Tracing a missing delivery update in Ahmedabad

A logistics company in Ahmedabad gets a complaint that a customer never received a delivery-slot message. Support searches the logs for the order ID and finds four lines: the send attempt with template.name: delivery_slot, the API response with a wamid, a sent status a second later and, ninety seconds after that, a failed status with meta_error.code: 131026. The masked recipient ending in 4821 matches the customer, and the error code points to a number that cannot receive the message. The team switches that customer to SMS within minutes, without anyone reading raw message content.

09Structured Logging FAQs

What is the difference between structured and unstructured logging?+
Structured logs are records with named fields, usually JSON; unstructured logs are free text. Structured logs can be filtered and aggregated by field without regular expressions, which is what makes them searchable at scale.
What should I log for a WhatsApp API integration?+
Log the event name, `wamid`, `phone_number_id`, template name, status, Meta error code, `fbtrace_id`, duration and a trace ID. Mask the recipient's number and leave message content out.
Can I log customers' phone numbers?+
Avoid it. Phone numbers are personal data under India's DPDP Act and logs spread widely, so mask them in the logger and keep at most the last few digits for support.
Is structured logging the same as an audit log?+
No. Structured logging is a format for operational logs used in debugging and alerting. An audit log is a complete, tamper-evident record of who did what, kept for accountability — and it is usually structured too.

10 · Pro tips

Best practices for structured logging

  • Log the FB Trace ID (fbtrace_id) on every Graph API error — Meta support will ask for it.
  • Record durations as numbers in milliseconds (duration_ms) so they can be charted, never as strings.
  • Set retention deliberately: the CERT-In Directions require covered entities to keep ICT system logs for a rolling 180 days, while privacy law favours keeping personal data no longer than needed.
  • Add a unit test that fails if a ten-digit number ever appears in log output.
  • Treat logged wamid values as personal data too: the ID is opaque, but its encoded part can carry the recipient's number, so restrict who can read the log store.

See InfiQ pricing

From definition to your first message

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.