Log Level Formatting: Customize Output Display in Your Applications

Published:

If your logs are a random mess, you’re wasting hours chasing issues.
Log level formatting decides how severity shows up, as strings or numbers, colored console lines or strict JSON, padded fields or freeform text.
This post walks you through practical rules you can apply now: pick the right formatter, align timestamps, use JSON for pipelines, colorize only in dev, and inject trace IDs.
Do this and your logs stop being noise and become a reliable tool for debugging and alerting.

Core Principles of Log Level Formatting for Immediate Implementation

GF6Fd6ktQ4aytmTTE2ZHXQ

Log level formatting controls how severity shows up in your output. You’re deciding whether levels appear as strings like “INFO” or numeric codes, where timestamps sit, which colors light up your terminal, and if you’re writing JSON objects or plain text. Get this right and you’ve got logs machines can index and humans can actually read when things break.

Most frameworks give you a few core options:

ISO-8601 timestamps with millisecond precision (2023-09-17T13:00:00.123Z works everywhere) so you can line up events across services
Color-coded console levels using ANSI escape codes (red for ERROR, yellow for WARN, cyan for INFO, blue for DEBUG) because your eyes can’t parse gray walls of text
JSON structured fields with predictable key names (timestamp, level, message, trace_id) that monitoring pipelines don’t choke on
Numeric versus string levels (Pino uses level: 30 for INFO and level: 50 for ERROR, but most libraries default to strings)

Setting this up means picking a formatter (JSON, plain-text pattern, or colorized console), defining field order, and turning on level styling. Log4j uses PatternLayout or JsonLayout. Python logging takes a Formatter with a format string or you can grab python-json-logger. Winston combines format.timestamp() and format.colorize(). Go’s slog configures handlers that control level display and field structure.

Log Level Formatting Across Common Log Types

XprIcR1LTliuQ52SkiVbmg

Formatting changes depending on whether your logs are unstructured, semi-structured, or fully structured. Unstructured logs just dump the level somewhere in a sentence: “2023-09-20 10:15:45 Error: Connection failed for user JohnDoe. Server timeout.” Semi-structured logs use delimited pairs, usually pipes or commas: “Timestamp: 2023-09-20 10:15:45 | Level: ERROR | Message: Connection failed | User: JohnDoe.” Structured logs, almost always JSON, put the level in its own field next to timestamp, message, and metadata. Parsing becomes trivial.

JSON makes automated parsing simple because every field has a key and a type. When the level always lives in a “level” key with a consistent string value, your ingestion pipeline (Elasticsearch, Kafka consumer, Fluentd) can filter, count, and alert on severity without regex gymnastics. You’re not guessing where the level starts or ends.

Log Type Example Level Display Pros Cons
Unstructured 2023-09-20 10:15:45 Error: Connection failed… Human-readable, fast to write by hand Hard to parse reliably; regex brittle
Semi-structured Timestamp: 2023-09-20 10:15:45 | Level: ERROR | … Better parsing than free text; still readable Inconsistent delimiters; schema drift
JSON {“level”:”ERROR”,”timestamp”:”2023-09-20T10:15:45Z”,…} Consistent schema; easy to index and query Verbose; harder to read raw in terminals

JSON-Level Formatting Patterns for Reliable Machine Parsing

JMvB4jW6S_2hGjGOILr5KQ

A JSON log object needs three keys to be useful: timestamp (ISO-8601 with timezone), level (the severity string), and message (what actually happened). Skip the timestamp and you can’t order events across distributed services. Skip the level and you can’t filter by severity. Skip the message and you’ve got metadata noise. A minimal production-ready JSON log is {“timestamp”:”2023-09-17T13:00:00.123Z”,”level”:”ERROR”,”message”:”payment gateway timeout”}.

Beyond that, you want metadata that ties logs to traces and the runtime:

traceid and spanid for linking logs to distributed trace spans (“traceid”:”abcd-1234″)
host, pid, and container
id to identify which process and node emitted the log (“host”:”svc-01″,”pid”:1234,”containerid”:”abc123″)
build version and commit hash for traceability back to deployment (“version”:”1.0.0″,”commit
hash”:”a1b2c3d4″)

Structured stack traces beat plain multiline strings for parsing and indexing. Don’t dump the stack trace as one big text blob under “message.” Use a nested exception object with type, message, and frames array. Like this: “exception”:{“type”:”ZeroDivisionError”,”message”:”division by zero”,”frames”:[{“filename”:”app.py”,”lineno”:42,”function”:”calculate”,”code”:”result = x / y”},…]}. Now you can query by exception type or filter for errors in a specific function, which is impossible when the stack is freeform text.

Color-Coded Log Level Formatting for Development Environments

YTmu4QzMSBaTragWecG_fA

ANSI escape codes wrap the level string when your logger detects terminal output. The code looks like \x1b[31mERROR\x1b[0m, where \x1b[31m turns text red and \x1b[0m resets it. Common color conventions are ERROR and FATAL in red (\x1b[31m), WARN in yellow (\x1b[33m), INFO in cyan (\x1b[36m), DEBUG in blue (\x1b[34m), and TRACE in dim gray. When you’re tailing logs during development, severity jumps out. Red lines mean something broke, yellow means check this later, cyan and blue scroll past without alarm.

Production systems skip colored output because ANSI codes clutter JSON and break parsers. Most JSON formatters strip or never insert ANSI codes. Even plain-text production logs avoid color because ingestion tools (Kafka, Fluentd, Elasticsearch) don’t interpret escape sequences, and the extra bytes inflate storage. Use color only in local dev or debugging sessions where you’re reading raw output in a terminal.

Timestamp and Level Alignment Techniques in Log Level Formatting

reAaMT_VT3m1K43tC-o5IA

ISO-8601 and RFC3339 are the recommended timestamp formats because they pack date, time, milliseconds, and timezone into one parseable string. A timestamp like 2023-09-17T13:00:00.123Z tells you the exact moment in UTC without ambiguity. When you’re correlating logs from services in different timezones, converting to UTC kills offset confusion. 2019-01-18T01:19:42-03:00 becomes 2019-01-18T04:19:42Z after adding three hours. Always log in UTC or include the offset so your aggregation tool can normalize the timeline.

Padding level names to a fixed width (five characters) keeps log columns lined up in plain-text output. If you print “INFO ” and “ERROR” with the same field width, timestamps and messages start at the same horizontal position in every line. Scanning hundreds of lines gets easier. Padding looks like %-5p in Log4j or %-8s in Python format strings. “INFO ” gets one trailing space, “DEBUG” gets none, “ERROR” stays five characters.

Format Example Notes
Local + offset 2019-01-18T01:19:42-03:00 Human-friendly; shows actual wall-clock time and offset
UTC 2019-01-18T04:19:42Z No conversion needed; best for aggregation and correlation
Mixed (local timestamp, no offset) 2019-01-18 01:19:42 Ambiguous timezone; avoid in production

Framework-Specific Log Level Formatting (Log4j, Python, Winston, Go)

5DWyL5aLQ1GXeOMgaKe_Dg

Log4j and Logback use PatternLayout to control how levels show up in plain-text logs. A typical pattern is %d{ISO8601} %-5p %c{1}:%L – %m%n. %d formats the timestamp, %-5p pads the level to five characters, %c{1} prints the logger name, %L prints the line number, %m is the message, %n adds a newline. For colorized output, wrap the level with the highlight plugin: %d{ISO8601} %highlight{%-5p} %c{1} – %m%n. Switching to JSON means using in your Log4j2.xml and the framework serializes every field, including level as a string key, automatically.

Python’s logging module takes a Formatter with a format string like ‘%(asctime)s %(levelname)-8s %(name)s:%(lineno)d %(message)s’. For color, install colorlog and use ColoredFormatter(‘%(logcolor)s%(asctime)s %(levelname)s %(name)s: %(message)s’), which assigns colors based on level. For JSON, add python-json-logger and initialize jsonlog_formatter.JsonFormatter() to spit out structured objects with “levelname” and “timestamp” keys instead of plain text.

Winston in Node.js combines format plugins. Colorized console output looks like this: const logger = winston.createLogger({ level: ‘info’, format: winston.format.combine( winston.format.colorize(), winston.format.timestamp({ format: ‘YYYY-MM-DDTHH:mm:ss.SSSZ’ }), winston.format.printf(({timestamp, level, message}) => ${timestamp} ${level}: ${message}) ), transports: [ new winston.transports.Console() ] }); For production JSON, replace printf with winston.format.json() and drop colorize().

Go’s slog package and Nginx JSON access logs handle level formatting at the handler or directive level. In Go, slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}) writes JSON with “level”:”INFO”, while slog.NewTextHandler writes key=value pairs. Nginx JSON access logs use logformat myjsonlogs escape=json with fields like ‘”level”:”$status”‘ and accesslog /var/log/nginx/mycustomlogs.json myjsonlogs; to capture request metadata in structured form.

Common formatter options you can configure across frameworks:

  1. Pattern layouts that set field order, padding, and delimiters in plain-text output
  2. JSON formatters that serialize every log field as a key-value pair for ingestion
  3. Colorizers that inject ANSI codes around level strings for terminal readability
  4. Custom field injection to add trace_id, host, pid, or build metadata automatically

Production-Safe Log Level Formatting and Data Hygiene

Xkcml2uuR56ZExAg9Ji-FA

Selective logging and redaction stop sensitive data leaks and keep log volume under control. Don’t log full user objects or raw passwords. Log only safe identifiers like user_id. In Go’s slog, implement a LogValue method that returns only the ID: type User struct { ID string; Password string }; func (u User) LogValue() slog.Value { return slog.StringValue(u.ID) }. When you call slog.Info(“login”, “user”, user), only the ID shows up in the log output, not the password. Structured error traces should strip or mask any field marked as sensitive before serialization.

Make sure your JSON schema matches what your ingestion pipeline expects. If Elasticsearch, Kafka consumers, or Fluentd require specific key names for timestamp and level, configure your formatter to emit those exact keys. Mismatched field names force manual remapping downstream and break out-of-the-box dashboards.

Three production hygiene practices:

Mask PII (redact email addresses, credit card numbers, and tokens before they enter the log message or JSON fields)
Avoid oversized payloads (truncate large request bodies or stack traces to 8–64 KB to prevent storage bloat and parser failures)
Use consistent keys (always name your timestamp “timestamp” and level “level” so ingestion configs don’t need per-service overrides)

Storage Format Considerations Affecting Log Level Formatting

dLt0MlQ-QJKzjdVZNjMkmQ

JSON works well for real-time ingestion stacks because it’s schema-flexible, human-readable in small batches, and supported natively by Elasticsearch, Kafka, and Fluentd. Your level formatting in JSON just needs a consistent key name and string value. The tradeoff is verbosity. Every log line repeats key names, which inflates storage compared to columnar or binary formats. JSON works best when logs flow continuously to a short-retention index (30–90 days) and you query by level, timestamp, or trace_id frequently.

Parquet’s batch analytics role fits long-term storage and selective I/O. When you archive months or years of logs into Parquet files on Hadoop or S3, the columnar layout compresses repeated level strings and timestamps efficiently. Queries that filter by level (“WHERE level = ‘ERROR'”) read only the level column without scanning entire rows. Level formatting in Parquet still uses strings (“ERROR”, “INFO”), but the schema enforces consistent types and compression minimizes the verbosity penalty.

Storage Format Typical Use Case Effect on Level Formatting
JSON Real-time ingestion, Elasticsearch/Kafka/Fluentd, short retention (30–90 days) Requires consistent “level” key; verbosity tolerated for flexibility
Parquet Long-term archives, batch analytics with Hadoop/Spark, columnar compression Schema enforces string or enum level type; compression reduces size; selective column reads

Final Words

In the action, we covered core principles: how level display, timestamp placement, color, and key/value structure shape readability and parsing. You saw JSON formatting patterns, ANSI color rules for dev, timestamp alignment tips, and concrete configs for Log4j, Python, Winston, and Go.

Quick next steps: pick a formatter, test ISO‑8601 timestamps, prefer JSON for production, and add minimal metadata. Watch for PII and keep keys consistent for ingestion.

Treat log level formatting as a deploy checklist item: set patterns, validate parsing, iterate — your logs will be clearer and easier to debug.

FAQ

Q: How should logs be formatted?

A: Logs should be formatted to show timestamp, level, message, and context fields; use ISO‑8601 timestamps, consistent level names (INFO/DEBUG/ERROR), and choose JSON for production or readable plain text for dev.

Q: What should my log output level be?

A: Your log output level should be the lowest severity that still gives actionable info: INFO in production, DEBUG during development or troubleshooting, and WARN if you need to cut noise while keeping issues visible.

Q: What is level 30 in logs?

A: Level 30 in logs corresponds to INFO in numeric schemes like Pino; treat it as regular operational messages you want stored in production, not noisy debug details.

Q: What are the five levels of logging?

A: The five common logging levels are DEBUG, INFO, WARN, ERROR, and FATAL, which increase in severity from verbose diagnostics up to unrecoverable failures that need immediate attention.

curtisharmon
Curtis has spent over two decades guiding hunters and anglers through the backcountry of Montana and Wyoming. His expertise in elk hunting and fly fishing has made him a sought-after voice in the outdoor community. Curtis combines traditional woodsmanship with modern techniques to help readers succeed in the field.

Related articles

Recent articles