CodeGym /Courses /JAVA 25 SELF /Formatting and log levels: best practices

Formatting and log levels: best practices

JAVA 25 SELF
Level 63 , Lesson 1
Available

1. Structure of a log message

Let’s imagine that logs are not just a “stream of consciousness” of your program, but a valuable journal where, a month or a year later, you or your colleague can find the answer to the question: “What the heck happened here?”. For that to be possible, every log message must be structured. Typically (and this is the standard in most libraries) each message contains:

  • Event time — when it happened.
  • Level — how important it is (INFO, ERROR, etc.).
  • Logger name — usually the class or component name.
  • Message text — what exactly happened.
  • Stack trace (if there’s an error) — to understand where and why.

Here is an example of a well-formatted log line (Log4j/SLF4J):

2024-06-16 18:42:07,123 INFO  com.example.MainApp - User logged in: username=vasya

And if an error occurred:

2024-06-16 18:42:10,456 ERROR com.example.LoginService - User authentication error: vasya
java.lang.IllegalArgumentException: Invalid password
    at com.example.LoginService.checkPassword(LoginService.java:42)
    ...

Why does this matter?
When an application runs for a long time, logs can take up gigabytes. If messages aren’t structured, finding a problem becomes a “guess the tune by the sound of a fan” challenge.

2. Formatting messages

Why you shouldn’t do this:

logger.info("User " + username + " logged in");

This looks simple, but there is a catch: even if the current logging level is ERROR, the string inside the parentheses will still be constructed (concatenation happens), which wastes resources. In large systems, where logs are thousands of lines per second, this can lead to real delays.

The right way: templates and parameters

Modern libraries (for example, SLF4J and Log4j 2) support parameterized templates:

logger.info("User {} logged in", username);

Here the string will be assembled only if the logging level allows this message to be output. If the level is, for example, WARN, then the string won’t even be computed — saving resources and nerves.

Bonus: if you pass several parameters, they are substituted in order:

logger.info("User {} performed action {} on object {}", username, action, objectId);

Logging exceptions (stack trace)

If you catch an exception, don’t manually append a stack trace to the message:

// DON'T DO THIS:
logger.error("Error: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));

The correct way:

logger.error("Error processing request", ex);

SLF4J and Log4j will nicely append the stack trace to the log for you.

Example: comparing approaches

// Bad (concatenation always executes)
logger.debug("Object: " + expensiveToString(obj));

// Good (lazy formatting)
logger.debug("Object: {}", obj);

3. Choosing logging levels

If everything in your logs is at ERROR, that’s not logging anymore, it’s a “red light.” If everything is at DEBUG, you’ll drown in details. Let’s figure out when to use which level.

Level Purpose Sample message
ERROR
Critical failures that cause the system to malfunction or not work at all “Database connection error”
WARN
Important warnings that aren’t critical but require attention “Failed to find user, using guest”
INFO
Routine events that reflect normal application operation “User registered: vasya”
DEBUG
Detailed information for debugging; not needed in production “Method checkPassword called with parameters ...”
TRACE
The most detailed information, usually for deep diagnostics “Processing loop start: i=0”

Typical message examples

  • ERROR — failed to write file, unhandled exception caught, service unavailable.
  • WARN — deprecated API, suspicious user behavior, attempt limit exceeded.
  • INFO — user logged in/out, order processing completed, application startup.
  • DEBUG — request parameters, variable values, intermediate computation results.
  • TRACE — method entry/exit, internal loops, algorithm details.

Tip:
In production, you usually enable only INFO and above, sometimes WARN and ERROR. DEBUG and TRACE — only when hunting tricky bugs.

4. Best practices (logging best practices)

Don’t log sensitive data

Passwords, tokens, credit card numbers — none of this belongs in logs. Even if it seems like “the log file is just for me,” remember GDPR and the colleague who might accidentally post the log in a public chat.

// Bad:
logger.info("User {} logged in with password {}", username, password);

// Good:
logger.info("User {} logged in", username);

Don’t overuse the ERROR level

If you write everything via logger.error, then when a real catastrophe happens, nobody will notice — everyone’s used to the “red lights.” Use ERROR only for situations where the application truly cannot continue or business logic is violated.

Log exceptions with the full stack

Don’t log only ex.getMessage(), or you’ll never know where the error occurred. Pass the exception as the second parameter to the logger.

logger.error("Error processing request", ex);

Use unique identifiers (event correlation)

In large systems, it’s useful to assign each request, user, or operation a unique identifier. This helps “stitch together” events from different parts of the system.

logger.info("Order processing started: orderId={}", orderId);
logger.info("Order processed successfully: orderId={}", orderId);

Don’t log everything

If there’s too much logging, it becomes useless. Don’t log every line of code, or you won’t be able to find what you need.

Format messages clearly

Write messages so that not only the code author understands them, but also the person reading the logs six months later. Avoid abbreviations, obscure shortcuts, and “inside jokes.”

5. Practice: configuring log format and levels

Example of format configuration in Log4j2 (log4j2.xml)

<Configuration>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

What does this mean?

  • %d{...} — event time.
  • %-5level — level (ERROR, INFO, etc.).
  • %logger{36} — logger name (usually the class).
  • %msg — the message itself.

Code example with different logging levels (SLF4J)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        logger.info("Application started");
        logger.debug("Variable x value: {}", 42);

        try {
            throw new IllegalArgumentException("Uh-oh!");
            // ...
        } catch (Exception ex) {
            logger.error("An error occurred during startup", ex);
        }
    }
}

Demonstrating the difference between levels

If the logger is configured at INFO level, messages at DEBUG and below won’t be printed. Try changing the level to debug in the config — you’ll see the details.

6. Common mistakes

Mistake No. 1: String concatenation in logs. Very often beginners write this:

logger.debug("User: " + user.getName() + ", role: " + user.getRole());

As a result, even with DEBUG disabled, these strings are still constructed, causing unnecessary overhead. Use parameters!

Mistake No. 2: Logging without the exception stack. They log only a message:

logger.error("Error: " + ex.getMessage());

As a result, there’s no information in the logs about where the error occurred. Pass the exception as the second parameter!

Mistake No. 3: Logging everything at ERROR level. If everything is red — nothing is red. Use levels as intended, or important errors will be buried among “trivia.”

Mistake No. 4: Logging sensitive data. Never put passwords, tokens, or card numbers in logs. Even if you think nobody will see it, life loves surprises.

Mistake No. 5: Unclear messages. If a log message looks like “ERR42: fail,” in a month you won’t remember what it means. Write clearly and with sufficient detail.

Mistake No. 6: Missing unique identifiers. In complex systems, without orderId, userId, and other identifiers, you won’t be able to “stitch” events together and understand what happened to a specific user or order.

1
Task
JAVA 25 SELF, level 63, lesson 1
Locked
Architect
Architect
1
Task
JAVA 25 SELF, level 63, lesson 1
Locked
Social Network
Social Network
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION