Structured Logging is an approach to logging where each message is represented in a machine-readable format with key-value pairs, rather than as unstructured text. Unlike flat strings, structured logs contain metadata: timestamp, level, module, request ID — and can be indexed by analysis systems. According to O'Reilly Effective Logging, switching to structured formats reduces incident search time from hours to minutes thanks to field-based filtering. This is the de facto standard in modern mobile and server development: JSON and logfmt allow logs to be processed by programs, not by eyes.
Key Takeaways
Structured Logging is a method of recording logs where each message contains named fields with typed values. Instead of a string like User 42 logged in from device ABC, a structured log looks like a set of fields: user_id=42, event=login, device_id=ABC, timestamp=2026-07-04T10:30:00Z.
The main advantage of structured logs over text logs is the ability for programmatic processing. Parsing text logs requires regular expressions and assumptions about the string format. Structured logs are parsed without loss: each field has a known type and name, allowing queries like find all authentication errors in the last hour for user 42 without additional processing.
According to Honeycomb.io (2023), teams using structured logging in production detect incidents on average 4 times faster compared to teams relying on text logs and grep.
Structured Logging supports several serialization formats. The choice of format depends on the infrastructure: JSON is convenient for integration with Elasticsearch and cloud systems, logfmt for console viewing via tail and grep, Protocol Buffers for high-performance systems with bandwidth constraints.
| Format | Example | When to Use |
|---|---|---|
| JSON | {"event":"login","user_id":42} | ELK Stack, cloud collectors, microservices |
| Logfmt | event=login user_id=42 duration_ms=150 | Console, tail, heroku logs |
| MessagePack | Binary equivalent of JSON | High-load systems, IoT |
JSON is the most common format for structured logs. It is natively supported by all collection systems: Logstash, Fluentd, Amazon CloudWatch, Google Cloud Logging. JSON logs are easily readable by humans and parsed by any programming language without additional libraries. The main drawback is verbosity: each key-value pair requires quotes and colons, increasing stored data volume by 30–50% compared to logfmt.
Logfmt was developed at Heroku for console visibility. It is more compact than JSON, retains human readability, and is easily parsable with cut and awk. Example: ts=2026-07-04T10:30:00Z level=error module=api status=500. Logfmt does not require escaping of most characters and is well suited for stdout logging in containers.
In mobile applications, Structured Logging solves three key problems: finding crash causes without reproducing on a device, tracking user sessions, and analyzing performance across app versions.
Text logs on mobile devices are almost useless — a developer cannot grep logs on a user's device. Structured logs are sent to cloud systems (Firebase, Sentry, Datadog) and indexed there. You can build a query like show all crashes on iOS 17.4, app version 3.2, in the checkouts module and get an accurate selection within seconds.
According to Sentry (2024), apps using structured breadcrumbs have 60% more context in each crash report compared to apps logging only the error text. This directly impacts bug fix speed.
ELK Stack — Elasticsearch, Logstash, Kibana — remains the standard infrastructure for working with structured logs. Logstash receives logs in JSON, transforms and sends them to Elasticsearch for indexing, Kibana provides a visual interface for queries and dashboards.
For mobile applications, cloud solutions are popular: Firebase Crashlytics with custom logs, Sentry with breadcrumbs, Datadog with APM tracking. They accept structured logs directly from the mobile SDK and do not require deploying your own backend. Firebase provides a free package for crash reports, Sentry adds distributed tracing, and Datadog integrates with APM to track request performance on both client and server simultaneously.
Grafana Loki — an alternative to Elasticsearch optimized for logs. Loki does not index message content by default but uses labels for filtering. This is significantly cheaper to store and faster for queries on a fixed set of fields.
// Structured logging via Swift Logger in JSON
struct StructuredLog {
let event: String
let attributes: [String: Any]
let level: String
func serialize() -> String {
var base = "event=\(event) level=\(level)"
for (key, value) in attributes {
base += " \(key)=\(value)"
}
return base
}
}
The first rule of structured logging: every message must contain a request or session identifier. Without context, an individual log is useless — it is impossible to determine which user or request it belongs to. Add a correlation ID at session start and pass it through all application layers.
The second rule: field typing. Numeric fields (duration_ms, status_code, retry_count) should be passed as numbers, not strings. Elasticsearch and similar systems index numbers and strings differently: numbers can be aggregated (average, median, percentile), strings support full-text search. Incorrect typing prevents building analytical dashboards.
The third rule: avoid nested objects. JSON logs with nesting depth beyond 2 levels are difficult to filter and visualize. Instead of {"user": {"name": "Alice", "role": "admin"}}, use flat keys: user_name=Alice user_role=admin.
// Structured logging on Android via Timber + logfmt
class StructuredTree : Timber.Tree() {
override fun log(priority: Int, tag: String?,
message: String?, t: Throwable?) {
val level = priorityToLevel(priority)
val logfmt = "level=$level tag=$tag message=$message"
sendToRemote(logfmt)
}
}
The minimum set of fields for every structured message: timestamp in ISO 8601, level (debug/info/warn/error/fatal), logger (module or class name), message (human-readable event description). Additionally: correlation_id, user_id (if known), version (app version), platform (iOS/Android), environment (dev/staging/prod).
Without correlation_id, structured logs become a collection of disjointed records that cannot be linked into a single user scenario. Generate a UUID at each app launch and add it to all session logs. In practice, correlation_id should be passed through all layers: from UI events to network requests and background tasks — otherwise some logs will remain without context and will not participate in analytics. With end-to-end tracking, a single UUID allows collecting the complete picture of the user journey.
On iOS, structured logging can be implemented via a wrapper over os_log that serializes fields into logfmt format. On Android, via Timber with a custom Tree that converts messages to JSON or logfmt before sending to the server.
import OSLog
struct StructuredLogger {
let subsystem: String
let category: String
func log(level: OSLogType,
event: String,
context: [String: Any]) {
let oslogger = Logger(
subsystem: subsystem,
category: category
)
let fields = context.map {
"\($0.key)=\($0.value)"
}.joined(separator: " ")
oslogger.log(level: level,
"\(event) \(fields)")
}
}
The choice between structured and text logs depends on the project stage. In early development stages, text logs are simpler and faster — the developer writes a message directly without additional wrappers. But once a project extends beyond a single team or server, structured logs become mandatory.
| Criterion | Text Logs | Structured Logs |
|---|---|---|
| Readability | High in console | Medium (requires pretty-print) |
| Search | grep by substring | Queries by fields and values |
| Aggregation | Not supported | Average, median, percentiles |
| Integration | Requires parsing | Native in ELK/Loki/Datadog |
| Storage volume | Smaller (no metadata) | Larger (fields + values) |
Frequently Asked Questions
For server-side sending, use JSON — it is natively supported by Firebase Crashlytics, Sentry and Datadog. For local viewing in Xcode or Android Studio logs, use logfmt — it is more compact and readable without formatting.
Yes, structured logs on the client allow adding context to each crash report: OS version, network state, recent user actions. Without structured breadcrumbs, a crash report contains only a call stack without a user scenario.
Logfmt is more compact (30–50% less volume) and easier to read in a terminal. JSON supports nested objects and arrays but requires quote escaping. The choice depends on infrastructure: for ELK — JSON, for console viewing — logfmt.
Create a single instance of UUID at app launch, store it in a singleton or DI container, and pass it to all loggers via constructor. Alternatively, use thread-local or Continuation Local Storage in Kotlin coroutines.
You can, but it is not recommended — mixing loses the ability for automatic indexing. If some logs are text-based, they must be parsed with regular expressions, which reduces search performance and reliability. It is better to migrate all logs to a structured format.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also