When I first started building apps, my logging strategy was simple: console.log("User logged in: " + user.id). It felt fine until my app hit a few thousand users and I had to spend three hours using grep and awk just to find out why a specific set of users were seeing 500 errors. That was the moment I realized I didn’t understand the difference between structured and unstructured logging.
If you’re currently staring at a wall of text in your terminal trying to find a needle in a haystack, this guide is for you. I’ll break down these two approaches and show you why moving toward structure is the single biggest upgrade you can give your observability stack.
Core Concepts: What Exactly Are We Talking About?
At its simplest, logging is the act of recording events that happen in your application. The “structure” refers to how that data is formatted and stored.
Unstructured Logging (The “Plain Text” Approach)
Unstructured logs are essentially just strings of text. They are designed to be read by humans, not machines. You write a message, maybe add a timestamp, and send it to a file.
2026-04-23 11:48:17 INFO User 4521 logged in from 192.168.1.1
2026-04-23 11:49:02 ERROR Failed to upload image for User 4521: timeout after 30s
To a human, this is perfectly clear. To a computer, this is just a blob of characters. If I want to find all “timeout” errors for “User 4521”, I have to write a complex Regular Expression (regex) and hope I didn’t make a typo in the log message elsewhere in the code.
Structured Logging (The “Data” Approach)
Structured logging treats logs as data rather than text. Instead of a string, you emit a machine-readable format—usually JSON. Each piece of information is assigned a key.
{
"timestamp": "2026-04-23T11:49:02Z",
"level": "ERROR",
"event": "image_upload_failed",
"user_id": 4521,
"error": "timeout",
"duration_ms": 30000,
"ip": "192.168.1.1"
}
Now, the log is an object. I can query my logging platform for user_id = 4521 AND level = 'ERROR' and get instant results without any regex gymnastics.
Getting Started: Moving from Text to JSON
In my experience, you don’t need to rewrite your entire app to start structured logging. You just need a logging library that supports structured output. In Node.js, I highly recommend Pino or Winston. In Python, structlog is the gold standard.
Here is a quick comparison of how the code changes. Instead of string interpolation, you pass an object.
Unstructured (Avoid this):
logger.info(`Order ${orderId} processed for ${customerEmail}`);
Structured (Do this):
logger.info({ orderId, customerEmail }, "Order processed");
As shown in the image below, the resulting output changes from a line of text to a searchable object, which makes a world of difference when you’re using tools like ElasticSearch or Datadog.
Your First Project: Setting Up a Searchable Log Pipeline
To really see the power of structured logging, you need a place to send these JSON blobs. If you’re just starting out, I suggest a simple pipeline: App → Pino (JSON) → Loki/Grafana.
- Install a structured logger:
npm install pino - Configure a standard schema: Decide on keys you’ll use everywhere (e.g.,
request_id,user_id,env). Consistency is key; if one dev usesuserIdand another usesuser_id, your queries will break. - Implement Correlation IDs: This is a pro tip. Generate a unique ID for every incoming request and attach it to every log entry during that request’s lifecycle. This allows you to trace a single request across multiple functions or microservices.
If you are managing multiple services, you should look into best practices for centralized logging in microservices to avoid managing ten different log files.
Common Mistakes Beginners Make
- Logging Sensitive Data: I’ve seen beginners log entire
userobjects, accidentally sending passwords or credit card numbers into the logs in plain text. Always whitelist the fields you want to log. - Over-logging: Structured logs are larger than text logs. If you log every single function entry/exit in JSON, your storage costs will skyrocket. Focus on state changes and errors.
- Inconsistent Keys: Using
msgin one place andmessagein another makes your dashboards useless. Create a shared logging utility wrapper to enforce a schema.
Learning Path: Mastering Observability
Once you’ve grasped the difference between structured and unstructured logging, here is the path I recommend for leveling up:
- Level 1: Local JSON logs. Get your app outputting JSON to the console.
- Level 2: Log Aggregation. Use a tool to collect logs from different containers into one place. If you’re on a budget, check out low cost cloud logging solutions for side projects.
- Level 3: Metric Extraction. Use your structured logs to create dashboards (e.g., “Count of 404 errors per minute”).
- Level 4: Distributed Tracing. Move beyond logs to OpenTelemetry to see how requests flow through your system.
Tools Recommendation
| Role | Recommended Tools | Why? |
|---|---|---|
| Libraries | Pino (JS), structlog (Py), Zap (Go) | High performance, JSON-first. |
| Storage/Search | ELK Stack, Grafana Loki, Datadog | Built specifically for indexing structured data. |
| Viewing | pino-pretty | Turns JSON back into readable text for local development. |
Ready to stop guessing why your app is crashing? Start by swapping one console.log for a structured object today!