skip to content

Explain how Spring resolves the effective level of a logger given multiple logging.level entries at different specificities.

level: seniorimportance: should knowfreq 38%

answer

  1. names = dotted tree, root at top
  2. effective level = nearest configured ancestor
  3. longest matching prefix wins
  4. specificity by segments, not chars
  5. root = ultimate fallback (INFO)

basics

~20 s

Logger names form a dotted tree rooted at root. A logger's effective level comes from the most specific configured ancestor prefix; if none is set it inherits down from root. The longest matching prefix wins.

solid answer

~40 s

Logger names are hierarchical: com.example.orders.OrderService has ancestors com.example.orders, com.example, com, and finally root. When you configure levels via logging.level.<name>, each entry pins a threshold at that node. A logger that has no explicit level inherits its effective level from its nearest configured ancestor. When several entries match a logger, the most specific (longest matching prefix) governs — so logging.level.com.example=WARN with logging.level.com.example.orders=DEBUG means classes under com.example.orders log at DEBUG while the rest of com.example stays at WARN. root is the ultimate fallback and Boot defaults it to INFO. This is standard Logback/Log4j2 hierarchy behaviour; Spring Boot just feeds these properties into the backend. It lets you keep a quiet global baseline while turning up one noisy subsystem.

code

java · 21 lines
java
// Given:
//   logging.level.root=INFO
//   logging.level.com.example=WARN
//   logging.level.com.example.orders=DEBUG
//
// Effective levels:
//   com.example.orders.OrderService  -> DEBUG (nearest = com.example.orders)
//   com.example.billing.Invoice      -> WARN  (nearest = com.example)
//   com.other.Thing                  -> INFO  (nearest = root)

import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;

public class ResolveDemo {
    public static void main(String[] args) {
        LoggerContext ctx = (LoggerContext) LoggerFactory.getILoggerFactory();
        System.out.println(ctx.getLogger("com.example.orders.OrderService").getEffectiveLevel()); // DEBUG
        System.out.println(ctx.getLogger("com.example.billing.Invoice").getEffectiveLevel());     // WARN
        System.out.println(ctx.getLogger("com.other.Thing").getEffectiveLevel());                 // INFO
    }
}

go deeper

for a junior

Knows root is the fallback and package levels can be set.

for a middle

Understands inheritance from the nearest configured ancestor.

for a senior

Articulates most-specific-prefix-wins by dotted segments and the quiet-baseline/loud-subsystem pattern.

for a principal

Reasons about resolution when designing env-specific overrides and silencing noisy dependencies without collateral suppression.

## The logger name tree Every logger has a name, usually the fully-qualified class name (`LoggerFactory.getLogger(OrderService.class)` -> `com.example.orders.OrderService`). Names are split on dots into a tree: ``` root └─ com └─ example └─ orders └─ OrderService ``` Each node can have an explicitly configured level or not. ## Effective level via inheritance A logger's **effective level** is: its own configured level if it has one; otherwise the configured level of its **nearest ancestor** that has one; if none up the chain, then `root` (default INFO in Boot). This is called *level inheritance* and is a property of the logging backend (Logback/Log4j2), not something Spring invents. ## Most-specific-prefix-wins When you set several `logging.level.*` entries, they pin thresholds at different nodes. For a given logger, the entry with the **longest matching dotted prefix** is the closest ancestor and therefore governs: ```properties logging.level.root=INFO logging.level.com.example=WARN logging.level.com.example.orders=DEBUG ``` Resolution: - `com.example.orders.OrderService` -> nearest configured ancestor is `com.example.orders` -> **DEBUG**. - `com.example.billing.Invoice` -> nearest is `com.example` -> **WARN**. - `org.springframework.web.SomeClass` -> no `com.example*` match -> falls to `root` -> **INFO**. Specificity is by **path segments**, not string length: `com.example.orders` is more specific than `com.example` because it is a deeper node, regardless of characters. ## Interaction with groups A group expands to per-member `logging.level` entries, so group members participate in exactly the same hierarchy resolution. Setting a group level is equivalent to setting each member node's level. ## Common uses - **Quiet baseline, loud subsystem**: keep `root=WARN` in prod but raise one package to DEBUG while chasing a bug. - **Silence a noisy library**: `logging.level.org.apache.kafka=ERROR` without affecting your code. ## Gotchas - Prefix matching is on **dotted segments**: `logging.level.com.exampleX` does NOT match `com.example` — `example` and `exampleX` are different segments. - A more specific entry always overrides a broader one for that subtree, even if the broader entry appears later in the file — it is about node specificity, not file order (unlike raw property override, which is about which source wins for the *same* key). - `root` cannot be bypassed: any logger with no matching ancestor uses it. - Changing a parent's level does not affect a child that has its **own** explicit level — the child's pinned level wins for itself and its descendants.

  • Does logging.level.com.example affect a logger named com.exampleService.Foo?
    No. Matching is by dotted segments, and 'example' is a different segment from 'exampleService'. It would fall back to the next matching ancestor (or root).
  • If root=WARN comes after com.example=DEBUG in the file, does root win?
    No. Effective level is decided by node specificity, not file order. For loggers under com.example the more specific com.example=DEBUG governs; root only applies where no more-specific ancestor matches.

saying these in an interview costs you the question

  • Thinking the last logging.level line in the file globally wins regardless of specificity.
  • Believing specificity is by string length rather than dotted-segment depth.
  • Assuming a parent level always overrides a child that has its own explicit level.
  • Thinking com.example matches com.exampleFoo (partial-segment match).

context