#11281: fix(logging): prevent subsystem loggers from bypassing file log level filters
stale
Cluster:
Hooks and UI Fixes
## Summary
Fixes a critical bug where subsystem loggers (used by all plugins) were writing debug/trace logs to file even when `logging.level` was set to `info`.
This fix ensures that the configured log level is properly respected for file logging.
## Root Cause Analysis
Two independent bugs were causing debug log leakage:
### Bug 1: `getChildLogger()` in `src/logging/logger.ts`
**Problem**: Line 134 explicitly passed `minLevel: undefined` to tslog's `getSubLogger()`:
```typescript
const minLevel = opts?.level ? levelToMinLevel(opts.level) : undefined;
return base.getSubLogger({ name, minLevel, prefix: ... });
```
When `createSubsystemLogger` called `getChildLogger({ subsystem })` without `opts`, this resulted in `minLevel: undefined` being **explicitly passed** to tslog's `getSubLogger()`.
In tslog v4, an explicit `undefined` value **overwrites** the parent logger's `minLevel: 3` (info level), causing the child logger's minLevel to become 0 (no filtering), allowing all debug/trace logs through.
**Fix**: Conditionally add `minLevel` to the options object only when it's defined, allowing tslog to inherit the parent's value:
```typescript
const subOpts: { name?: string; minLevel?: number; prefix?: string[] } = {
name,
prefix: bindings ? [name ?? ""] : [],
};
if (minLevel !== undefined) {
subOpts.minLevel = minLevel;
}
return base.getSubLogger(subOpts);
```
### Bug 2: Missing level check in `emit()` function in `src/logging/subsystem.ts`
**Problem**: Line 250 called `logToFile()` without any level check:
```typescript
logToFile(getFileLogger(), level, message, fileMeta);
if (!shouldLogToConsole(level, { level: consoleSettings.level })) {
return; // ← Console has proper guard, file does not
}
```
Console logging correctly used `shouldLogToConsole()` as a gate, but file logging had no corresponding check.
**Fix**: Add `isFileLogLevelEnabled()` guard before `logToFile()` call:
```typescript
if (isFileLogLevelEnabled(level)) {
logToFile(getFileLogger(), level, message, fileMeta);
}
```
## Changes
- **src/logging/logger.ts**: Modified `getChildLogger()` to conditionally add `minLevel` only when defined
- **src/logging/subsystem.ts**: Added `isFileLogLevelEnabled()` check before calling `logToFile()`
## Verification
Tested with the following steps:
1. Set `logging.level: "info"` in config
2. Restarted gateway
3. Waited for plugin activity (which triggers `api.logger.debug()` calls)
4. Checked file logs: No new DEBUG logs appeared in `openclaw-YYYY-MM-DD.log`
5. Changed `logging.level` to `"debug"` and verified debug logs now appear correctly
## Test Plan
- [x] Ran test suite: `pnpm test -- src/logging`
- [x] Verified debug logs are filtered when `logging.level: "info"`
- [x] Verified debug logs appear when `logging.level: "debug"`
- [x] Built project successfully: `pnpm build`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR fixes file logging level leakage for plugin/subsystem loggers.
- In `src/logging/logger.ts`, `getChildLogger()` now only passes `minLevel` to `tslog` sub-loggers when a level override is explicitly provided, ensuring sub-loggers inherit the parent logger’s configured `minLevel`.
- In `src/logging/subsystem.ts`, subsystem `emit()` now gates `logToFile()` with `isFileLogLevelEnabled(level)`, matching the existing console guard and preventing debug/trace entries from being appended to log files when `logging.level` is higher (e.g. `info`).
Overall, the change aligns file logging behavior with configured log-level filtering while keeping console behavior unchanged.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- Changes are narrowly scoped to logging level propagation and file logging guards, and they align file and console filtering semantics without altering unrelated behavior. No functional regressions were found in the updated code paths.
- No files require special attention
<!-- greptile_other_comments_section -->
**Context used:**
- Context from `dashboard` - CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=fd949e91-5c3a-4ab5-90a1-cbe184fd6ce8))
- Context from `dashboard` - AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=0d0c8278-ef8e-4d6c-ab21-f5527e322f13))
<!-- /greptile_comment -->
Most Similar PRs
#11305: fix(logging): remove redundant subsystem prefix from log output
by janckerchen · 2026-02-07
82.1%
#9974: refactor(agents): replace console.warn with SubsystemLogger in comp...
by dinakars777 · 2026-02-05
80.8%
#23669: refactor(logging): migrate node-host and tailscale console calls to...
by kevinWangSheng · 2026-02-22
80.1%
#9461: refactor(cli): replace console.warn with SubsystemLogger in health.ts
by dinakars777 · 2026-02-05
79.7%
#22478: fix(diagnostics-otel): wire OTLP exporter to emit traffic to config...
by LuffySama-Dev · 2026-02-21
79.1%
#15948: fix(logging): prevent recursive logger/config stack overflow
by Glucksberg · 2026-02-14
77.2%
#11153: refactor(hooks): replace console.warn/error with subsystem logger
by hclsys · 2026-02-07
77.1%
#12308: fix(cli): redirect log output to stderr during completion script ge...
by mcaxtr · 2026-02-09
77.0%
#22139: Fix(ui): improve log formatting for JSON payloads
by npmisantosh · 2026-02-20
76.6%
#8617: fix: truncate large console payloads before writing to file log
by dbottme · 2026-02-04
76.4%