A payment service emits a JSON record whenever an authorisation fails. The dashboard query works until a release changes duration_ms from a number to a string and nests the outcome under a new object. The records still parse as JSON, but an alert no longer includes them.
Structured logging makes events easier for tools to consume when their meaning and shape remain dependable. Valid JSON is only the encoding layer. Operators also need stable event names, understood types, appropriate context, and evidence that records reach the place being queried.
Treat the schema as a contract between producers and consumers. The consumers include dashboards, alerts, incident queries, support tools, and sometimes other services. A convenient change to one log statement can affect all of them even when the application itself continues to work.
Define an Event Before Formatting Its Message
Start with what happened and where the observation was made. A failed dependency attempt, a final failed customer request, and a payment whose outcome is uncertain are different events. Calling all three payment_error makes counting and recovery harder.
Choose a stable event name and a concise readable message. Put queryable values in fields rather than requiring a regular expression to recover them from prose. A message can change wording without forcing an alert to relearn the event, provided the structured contract remains stable.
The following is an illustrative application schema, not the OpenTelemetry wire format:
{
"schema_version": 1,
"timestamp": "2026-10-24T01:02:03.456Z",
"severity": "error",
"service": "payment-api",
"event": "payment.authorization_attempt_failed",
"message": "Payment authorisation attempt did not return a confirmed result",
"operation_id": "op_4821",
"attempt": 2,
"duration_ms": 318,
"outcome": "uncertain",
"error": {
"category": "timeout"
}
}
The outcome is uncertain because a timeout describes the caller’s observation, not proof that the provider rejected the authorisation. That meaning should be explicit in the event definition. The record also distinguishes an attempt from the wider payment operation.
Use an appropriate reference to find the durable payment record. A log can support diagnosis, but it should not become the only evidence that a business obligation exists or that a payment completed. Retention and delivery policies may make operational logs unsuitable as the authoritative ledger.
Make Types and Units Stable
RFC 8259 defines JSON’s data model and interoperability considerations. A field containing 318 is a number, while a field containing the string 318ms needs additional parsing before numeric analysis. Agree the representation and unit for common quantities.
Identifiers often belong in strings even when they contain digits. They are references rather than values to add, and a string can preserve leading zeroes or values beyond a consumer’s exact integer range. Decide this in the schema instead of letting different languages choose incompatible representations.
Distinguish missing, null, empty, and zero. A missing duration might mean that timing was unavailable; zero can be a legitimate measured value. A transformation that converts both to zero can create misleading latency results while preserving syntactically valid JSON.
Use a standard timestamp representation and document the time being recorded. The OpenTelemetry logs data model distinguishes an event timestamp from an observed timestamp. Collection delay can make those different, which matters when an incident query filters a particular interval.
Keep Correlation and Error Meaning Explicit
Include trace and span context where available, alongside the business-operation reference needed for later recovery. The scopes differ: a workflow can span several traces, and one trace can contain many attempts. The article on correlation and trace IDs explains those relationships.
Use a stable error category or code when consumers need to group failures. Raw exception messages often contain variable details and should not determine every alert. Retain appropriate diagnostic detail separately, subject to privacy and access controls.
| Field family | Contract to establish | Common failure |
|---|---|---|
| Event name | Which observation occurred | Several meanings share one generic event |
| Duration | Numeric type and unit | Strings or mixed units break comparisons |
| Operation reference | Scope and lifetime | A request ID is mistaken for workflow history |
| Error category | Stable grouping semantics | Variable exception text becomes a dimension |
| Outcome | Confirmed, failed, pending, or uncertain meaning | A timeout is reported as a rejection |
| Timestamp | Event or observation time | Delayed records are compared in different windows |
Avoid logging the same exception at every layer without a purpose. Several records can be legitimate when they describe different observations, but they should be distinguishable. Otherwise a query for error severity can count one failed request several times, as discussed in telemetry disagreements.
Prevent Sensitive Data From Entering the Record
OWASP’s logging guidance discusses data exclusion, event selection, protection, and testing. Apply those decisions at the producer where practical. Once a secret enters several collectors and indexes, deleting one visible field may not remove every copy.
Prefer an allowlist of useful context over serialising whole request, response, or user objects. Such objects can acquire new sensitive fields as the application evolves. A logging statement that was acceptable last month may expose a newly added token tomorrow without changing its own code.
Error objects deserve review too. Messages, stack traces, URLs, headers, and nested causes can contain credentials or customer content. Redaction should cover the actual shapes the application emits rather than only replacing a few obvious field names in the top-level object.
Protect diagnostic access according to the retained information. Opaque references reduce some exposure but can still reveal relationships when combined with other records. Define retention, access, and export practices alongside the event schema, without treating JSON formatting as a privacy control.
Preserve Framing Through the Collection Path
Many log collectors expect one JSON object per line. Use a serializer and an agreed framing format so quotes, backslashes, and newlines inside values remain encoded correctly. Pretty-printing a record over several physical lines can cause a line-oriented collector to treat its fragments as separate events.
Do not construct JSON through string concatenation with user-controlled values. A correct serializer handles JSON escaping; subsequent renderers and export tools still need their own safe handling for their output context. Syntactic validity at the producer does not guarantee safe display everywhere downstream.
Set appropriate size limits and define truncation behaviour. Cutting a serialised record halfway through can leave invalid JSON. Prefer bounded fields and a clear truncation indicator, or another documented mechanism supported by the logging system.
Test the full route from application output through collector processing to the backend query. A parser can flatten objects, rename fields, reject types, or attach another timestamp. Verify the stored representation rather than assuming it matches the producer’s object exactly.
Budget Volume, Indexing, and Failure Behaviour
A unique request reference can be useful in logs even though it would be unsuitable as an unbounded metric label. Whether a high-cardinality log field is expensive depends on the backend’s indexing, storage, and query design. Avoid applying one universal cardinality rule to every telemetry system.
Decide which fields need indexing and which only need retrieval in a selected record. Repeated payloads and large stack traces can dominate transfer and retention cost. Measure those costs using representative traffic rather than treating the number of fields as the complete budget.
Logging can also affect the application. Synchronous writes can add latency, and an unbounded asynchronous buffer can exhaust memory. Define what happens when the destination is unavailable or capacity is reached. Ordinary diagnostics and required audit records may need different delivery and failure policies.
Monitor dropped, rejected, and delayed records. A quiet error query can reflect an unhealthy collection path. If the service relies on a particular event to detect an incident, include the event’s delivery path in that reliability assessment.
Evolve the Contract Deliberately
JSON Schema’s object rules can express required properties and permitted structures for suitable validation. Use representative event fixtures and consumer checks to catch changes such as a number becoming a string. Parse success alone does not verify the intended contract.
Choose an evolution policy. Adding an optional field may be compatible with some consumers and rejected by a strict schema elsewhere. Renaming an event, changing units, or altering an outcome’s meaning requires explicit coordination. A version field helps identify the shape, but consumers still need a transition plan.
Test redaction and limits with synthetic sensitive values, nested exceptions, non-ASCII text, and multiline messages. Confirm that malformed records are visible to operators and do not silently disable an alert. These checks should exercise a meaningful consumer expectation rather than mirror the logging implementation.
Dependable JSON logging gives an investigator events whose meaning, structure, and collection path are understood. That contract makes the data useful during a failure, when there is little time to rediscover what a field meant or why an apparently successful parser left the important records out.





