What Are Logging Levels

Logging levels (also called log severity levels) classify each log message by how serious it is, so a logging framework can filter messages below a chosen threshold. Every message you emit is tagged with a level - TRACE, DEBUG, INFO, WARN, ERROR, or FATAL - and the framework drops anything less severe than the configured minimum. That single threshold lets you run verbose tracing in development and only ERROR-and-above in production without changing application code.

The level is set per message at the call site. When you write log.debug("connecting to %s", host), you are declaring that this message matters only when someone is actively debugging. Setting the runtime threshold to INFO discards it. The point of levels is signal-to-noise control: keep the detail in the code, decide at runtime how much of it reaches disk.

The Standard Severity Hierarchy

Most frameworks share the same ordered scale, from most verbose to most severe: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. The exact set and numeric encoding differ, and the numbers matter because filtering is a numeric comparison. The table below maps the four most common systems.

Severity Log4j / Logback / SLF4J Python logging Syslog (RFC 5424)
Most severe FATAL (intLevel 100) CRITICAL (50) 0 Emergency / 1 Alert / 2 Critical
Error ERROR (200) ERROR (40) 3 Error
Warning WARN (300) WARNING (30) 4 Warning
Notice - - 5 Notice
Informational INFO (400) INFO (20) 6 Informational
Debug DEBUG (500) DEBUG (10) 7 Debug
Trace TRACE (600) - (use DEBUG) -

Two ordering conventions exist and they run in opposite directions. In Log4j 2 a lower intLevel is more severe: FATAL is 100, TRACE is 600, plus the special OFF (0) and ALL (Integer.MAX_VALUE) that disable or enable everything. In Python a higher number is more severe: CRITICAL is 50, DEBUG is 10, and NOTSET is 0. Syslog per RFC 5424 uses 0-7 where lower is more severe (0 Emergency through 7 Debug). When you wire two systems together, map by name and severity meaning, not by number.

A practical note on defaults. Python's root logger is created at WARNING, so DEBUG and INFO calls are silently dropped until you lower the level. That trips up people who add logging.info(...) and see nothing.

What to Log at Each Level

Each level answers a different question, so the content of the message should match its severity. Picking the wrong level is the most common logging mistake: an ERROR that fires on every request becomes noise, and a real failure logged at DEBUG never reaches your alerts.

  • TRACE - the finest granularity, for following control flow line by line. Loop iterations, raw payloads, every method entry/exit. Almost never enabled in production.
  • DEBUG - diagnostic detail useful when reproducing a problem: variable values, branch decisions, query parameters. On in development, off or sampled in production.
  • INFO - normal lifecycle events worth keeping: service started, request handled, job completed, config loaded. The default production level for most services.
  • WARN - something unexpected that the system recovered from: a retry, a deprecated API call, a fallback path, low disk space. Worth reviewing, not paging.
  • ERROR - an operation failed and a user or request was affected: an unhandled exception, a failed write, a rejected request. This is the level alerts usually watch.
  • FATAL / CRITICAL - the process cannot continue: a failed startup dependency, corrupted state, an unrecoverable condition that triggers shutdown.

A useful test: if a level would page a human at 3am, it should be ERROR or above. If it is background context you only read while investigating, it is INFO or below.

Structured Logging, Correlation IDs, and Sampling

Levels control how much you log. Structure controls how usable it is. Emit logs as JSON with a consistent schema rather than free-text strings, so that downstream tools can filter by field instead of regex. A structured record carries the level as a field alongside a machine-sortable timestamp, the service name, and request context.

{
  "ts": "2026-06-01T13:08:23.125Z",
  "level": "ERROR",
  "service": "checkout",
  "trace_id": "a1b2c3d4e5f6",
  "msg": "payment gateway timeout",
  "gateway": "stripe",
  "latency_ms": 30000
}

Start the timestamp with the year (ISO 8601) so lexical sort equals chronological sort. The trace_id (or correlation ID) is what ties every log line for one request together across services. When an ERROR fires, you filter every service's logs by that one ID and reconstruct the full path - the single highest-leverage move for debugging distributed systems. See structured logging in log management best practices for schema and pipeline detail.

Sampling addresses high-volume levels. Logging every DEBUG line under load costs CPU, disk, and money. Sample DEBUG/INFO (keep 1 in N), but never sample ERROR and above - you want every failure. Many frameworks support changing the level at runtime (Log4j 2 Configurator.setLevel, Python logger.setLevel, JMX or an admin endpoint) so you can raise verbosity on a misbehaving instance for a few minutes, then drop it back without a redeploy. That is tracing on demand: full detail only when and where you need it.

Logging Levels in PostgreSQL

Databases use the same idea with their own vocabulary. PostgreSQL ranks messages, from most to least verbose, as DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, PANIC. Three parameters apply this scale. log_min_messages (default WARNING) sets what reaches the server log; client_min_messages sets what is sent back to the client session; and log_min_error_statement (default ERROR) sets the severity at which the offending SQL statement is logged alongside the error. The position of LOG differs between server and client ordering, which is why LOG messages always reach the server log but not the client.

-- Server-side: log everything WARNING and above (the default)
SET log_min_messages = 'WARNING';

-- Capture the SQL text for any statement that errors
SET log_min_error_statement = 'ERROR';

Statement logging is separate from severity. log_statement accepts none (default), ddl, mod, or all, controlling which categories of SQL are recorded regardless of whether they error. Setting it to all logs every statement, which is invaluable for debugging and expensive at scale - reach for it briefly, not as a standing default. To find and inspect the active log file you can ask PostgreSQL directly (requires superuser or pg_monitor):

-- Path of the current log file, then its size in bytes
SELECT pg_current_logfile();
SELECT size FROM pg_stat_file(pg_current_logfile());

In production, rotate old files (logrotate, or PostgreSQL's built-in log_rotation_age / log_rotation_size) so logs do not fill the disk, and ship them to a central store. Async, centralized logging into an ELK / OpenSearch stack - typically through Logstash - lets you search across instances, run anomaly detection, and filter by correlation ID in one place. Pulse plugs into exactly this layer: it ingests database logs and metrics, runs anomaly detection across them, and performs automated root-cause analysis, and it is the only tool built specifically for monitoring and optimizing Logstash itself, so the pipeline carrying your logs does not become the blind spot.

Frequently Asked Questions

Q: What is the standard order of logging levels from least to most severe?
A: The standard order is TRACE, DEBUG, INFO, WARN, ERROR, then FATAL (or CRITICAL) as most severe. Filtering at a given level passes that level and everything more severe, and drops everything below it.

Q: What log level should I use in production versus development?
A: Use DEBUG or TRACE in development to see full detail, and INFO or WARN in production to keep volume and cost down while still recording lifecycle events and recoverable problems. Keep ERROR and FATAL flowing in every environment, and never sample them.

Q: What is the difference between WARN and ERROR?
A: WARN means the system hit something unexpected but recovered, such as a retry or a fallback - worth reviewing later. ERROR means an operation actually failed and affected a request or user, which is the level alerts usually watch.

Q: What are the syslog severity levels and their numbers?
A: RFC 5424 defines eight, from 0 to 7: 0 Emergency, 1 Alert, 2 Critical, 3 Error, 4 Warning, 5 Notice, 6 Informational, 7 Debug. Lower numbers are more severe, the reverse of Python's logging module.

Q: Why are my Python INFO and DEBUG messages not showing up?
A: Python's root logger defaults to WARNING (numeric value 30), so DEBUG (10) and INFO (20) are discarded until you lower the threshold. Call logging.basicConfig(level=logging.DEBUG) or logger.setLevel(logging.INFO) to see them.

Q: What are PostgreSQL's logging levels?
A: PostgreSQL ranks messages DEBUG5 through DEBUG1, then INFO, NOTICE, WARNING, ERROR, LOG, FATAL, PANIC. log_min_messages (default WARNING) controls the server log, client_min_messages controls what the client sees, and log_min_error_statement (default ERROR) controls when the failing SQL is recorded.

Q: Can I change the log level without restarting the application?
A: Yes. Most frameworks expose dynamic level changes - Log4j 2 through Configurator.setLevel or JMX, Python through logger.setLevel, and PostgreSQL through SET log_min_messages or a pg_reload_conf(). This lets you raise verbosity on a failing instance temporarily, then lower it again.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.