Last updated: January 11, 2026
Understanding Log4j Log Levels in Practice
If you operate Java services in production, Log4j is likely somewhere in the stack, either directly or through a framework that depends on it. Despite its age and ubiquity, its log levels are still frequently misunderstood and misused, often leading to noisy logs, missing signals, or alerts that fire too late to matter.
Log levels are how you communicate intent to future operators, on call engineers, and automated systems. Every time you pick a level, you're answering a subtle but important question: how serious is this, and what should someone do about it later?
This article walks through Log4j log levels in practical terms, how they map to modern observability standards, and how to configure and use them without falling into the common traps that make Java logs expensive and hard to trust.
Log4j log levels in order
Log4j defines six primary log levels, ordered below from least to most severe. Each level represents a different expectation about frequency, urgency, and actionability:
| Level | When to use it |
|---|---|
TRACE | Extremely detailed execution flow used for deep diagnostics. |
DEBUG | Internal state and decision making for troubleshooting. |
INFO | Normal, expected application behavior under healthy conditions. |
WARN | The service is an an unusual state that may require attention if it continues. |
ERROR | An operation failed and functionality was impacted. |
FATAL | An unrecoverable condition that forces the application to shut down. |
Used correctly, Log4j levels create a clear separation between routine behavior and real problems. The more severe the level, the less often it should appear and the faster someone should care.
How to set the log level of an event
In Log4j, the log level is chosen when you emit the log. Most applications use the level specific methods provided by the logger, which keeps intent explicit and readable:
java123456789101112import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class OrderService {private static final Logger logger = LogManager.getLogger(OrderService.class);public void placeOrder(String orderId) {logger.trace("Entering placeOrder with orderId={}", orderId);logger.debug("Validating order {}", orderId);logger.info("Order {} placed successfully", orderId);}}
A practical way to think about using Log4j levels is as follows:
TRACEandDEBUGexplain how the system works internally. They are usually disabled in production and enabled temporarily when investigating an issue.INFOdescribes what the system is doing under normal conditions. It should be the majority of your logs.WARNsignals risk. It means the system still works, but something important is drifting away from the happy path.ERRORmeans a request or task failed, even though the process keeps running.FATALis reserved for explaining unrecoverable conditions that caused the application instance to terminate.
By following these simple guidelines, most level decisions become obvious. Routine behavior stays routine, and real problems stand out instead of hiding in noise.
Log4j default log level behavior
If no explicit configuration is provided, Log4j defaults to a default log level
of ERROR. In practice, most frameworks override this early, but it is
important to understand what the active minimum level is in your environment.
With a root level of INFO, anything below it is discarded:
xml123<Root level="info"><AppenderRef ref="Console"/></Root>
java1234logger.debug("This will not be logged");logger.info("This will be logged");logger.warn("This will be logged");logger.error("This will be logged");
Think of the configured level as a hard floor. Events below it are never created, which means they have almost zero runtime cost.
Configuring log levels in Log4j 2
Most applications configure Log4j using XML, YAML, or properties files. XML remains common in production systems.
A simple log4j2.xml configuration might look like this:
xml12345678910111213<Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %-5level %logger - %msg%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root></Loggers></Configuration>
This setup keeps logs focused on operational signal while suppressing debug noise by default.
Overriding log levels for specific packages
One of Log4j’s most useful features is per package or per class level overrides. This lets you increase detail in a narrow area without flooding the entire application.
xml123456789<Loggers><Logger name="com.myapp.payments" level="debug" additivity="false"><AppenderRef ref="Console"/></Logger><Root level="info"><AppenderRef ref="Console"/></Root></Loggers>
With this configuration:
- Payment related code logs at
DEBUG - Everything else remains at
INFO - Log volume stays predictable
This pattern is especially valuable when debugging production issues in large codebases.
Mapping Log4j levels to OpenTelemetry severity
Log4j’s level model aligns cleanly with OpenTelemetry’s standardized severity categories, making it straightforward to export logs without losing intent:
| Log4j level | OpenTelemetry SeverityText | OTel SeverityNumber range |
|---|---|---|
TRACE | TRACE | 1-4 |
DEBUG | DEBUG | 5-8 |
INFO | INFO | 9-12 |
WARN | WARN | 13-16 |
ERROR | ERROR | 17-20 |
FATAL | FATAL | 21-24 |
When Log4j logs are exported through proper OpenTelemetry instrumentation, this mapping preserves severity semantics across languages and platforms, which is critical in distributed systems.
Performance considerations with debug logging
Log4j does not evaluate log events below the active level, but any work you do before the call still happens. This matters in hot paths like request handlers and tight loops.
Avoid patterns like this:
java1logger.debug("Computed result {}", expensiveComputation());
Even if DEBUG is disabled, expensiveComputation() still runs. In high volume
paths, guard explicitly:
java123if (logger.isDebugEnabled()) {logger.debug("Computed result {}", expensiveComputation());}
You do not need this everywhere. Use it where log volume and computation cost make the overhead visible.
Making Log4j levels actionable with Dash0
In production services, the hard part of troubleshooting is rarely collecting logs but out which ones matter when everything is noisy and something is already broken.
Dash0 keeps Log4j’s severity model intact and uses it to drive investigation flow. You start with the small number of ERROR and WARN events that indicate real impact, then expand outward to INFO and DEBUG only for the specific requests involved.
Because Dash0 is OpenTelemetry-native, Log4j logs are tied directly to the requests that produced them. That means you can move from a failed log line to the exact execution path across threads and services, instead of grepping through millions of unrelated entries.
Final thoughts
Log4j log levels are simple, but they carry a lot of weight. Used deliberately, they give your logs a clear operational shape. Used carelessly, they turn into background noise that nobody trusts.
Treat log levels as part of your system design, not a cosmetic detail. If you do, Log4j will scale with your application and still be useful when things go wrong.
Try Dash0 today to see Log4j logs in the context they were produced, with severity guiding what you look at first instead of drowning you in noise.
