Skip to content

fix(logs): respect log level explicitly declared by structured loggers - #4624

Merged
Siumauricio merged 2 commits into
Dokploy:canaryfrom
fcsouza:fix/structured-log-level
Aug 6, 2026
Merged

fix(logs): respect log level explicitly declared by structured loggers#4624
Siumauricio merged 2 commits into
Dokploy:canaryfrom
fcsouza:fix/structured-log-level

Conversation

@fcsouza

@fcsouza fcsouza commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

What

The Docker log viewer classifies log lines purely by keyword matching and ignores the log level the application actually declared. Structured log lines (pino, bunyan, winston, zap, slog, logfmt) end up with wrong badges:

  • {"level":"error",...} never matches the error patterns (they expect whitespace-delimited words like error: ), so a pino error line falls through to the debug branch via \b(?:version|...|get|post)\b — pino lines always carry "version" / "method":"GET".
  • {"level":"warn","msg":"Webhook status event..."} is classified as info because \bstatus\b matches first.
  • {"level":"info","msg":"Request completed"} is classified as success because of \bcompleted\b.

Fix

Check for an explicitly declared level before any inference, and only fall back to the existing keyword heuristics when none is present:

  • JSON string levels: "level":"error", "severity":"ERROR" (GCP), "log.level" (ECS)
  • JSON numeric levels: "level":50 (pino/bunyan 10–60 scale, syslog/GELF 0–7 scale)
  • logfmt: level=error

A declared level also wins over the inferred statusCode classification. Unknown level names fall back to the keyword detection, which is unchanged.

Kept intentionally minimal (no scoring system) per the maintainer feedback on #3070.

Issues related

Closes #4589. Related: #4538, #1996.

Before / After

Same container (pino JSON + logfmt lines), tested on a local dev instance (pnpm dokploy:dev):

Before"level":"error" shows as debug, "level":"warn" as info, "level":"info" as success:

before: pino error line classified as debug

After — badges match the declared level; numeric pino levels and logfmt are also respected:

after: badges match the declared level

Tests

Added __test__/utils/log-type.test.ts — 12 tests covering pino/winston/zap string levels, pino/bunyan numeric levels, syslog/GELF numeric levels, GCP severity, ECS log.level, logfmt, precedence over statusCode and keywords, fallback for unknown level names, and the regression cases from #4589/#4538. Full vitest suite, tsc --noEmit and Biome pass.

Checklist

Greptile Summary

This PR makes explicitly declared structured-log levels take precedence over status-code and keyword inference.

  • Adds mappings for common string severity names.
  • Supports pino/bunyan and syslog/GELF numeric levels.
  • Recognizes JSON, ECS/GCP, and logfmt declarations.
  • Adds regression and fallback tests for log classification.

Confidence Score: 4/5

The PR should not merge until explicit-level extraction is constrained to actual structured logger fields rather than nested or quoted payload data.

Raw application log text is searched with unstructured regular expressions, so an earlier nested or quoted level token can override the real logger declaration and produce incorrect badges and filtering.

Files Needing Attention: apps/dokploy/components/dashboard/docker/logs/utils.ts

Reviews (1): Last reviewed commit: "Merge branch 'canary' into fix/structure..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

@Siumauricio Siumauricio left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and tested locally: applied the diff on current canary, all 12 tests pass, and verified it composes cleanly with #4824 (16/16 tests passing with both changes merged — declared levels short-circuit before the keyword fallback, which #4824 fixes). Nice work covering pino/bunyan numeric scales, syslog/GELF, logfmt and GCP severity, with the LOG_LEVEL= env-var guard.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Aug 3, 2026
Comment on lines +109 to +123
const jsonStringMatch = message.match(
/"(?:level|severity|log\.level|loglevel)"\s*:\s*"([a-z]+)"/i,
);
if (jsonStringMatch?.[1]) {
return LEVEL_NAME_TO_TYPE[jsonStringMatch[1].toLowerCase()] ?? null;
}

// JSON numeric levels: {"level":50}
const jsonNumericMatch = message.match(/"level"\s*:\s*(\d{1,2})\b/);
if (jsonNumericMatch?.[1]) {
return numericLevelToType(Number(jsonNumericMatch[1]));
}

// logfmt: level=error
const logfmtMatch = message.match(/(?:^|\s)(?:level|severity)=([a-z]+)\b/i);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Nested fields override declared levels

When a structured log contains a nested level field before its top-level declaration, or a level-like token inside a quoted logfmt value, these regexes select that earlier payload value, causing the dashboard to assign the wrong badge and filtering category.

Knowledge Base Used: Monitoring and Live Terminal/Log Streaming

@Siumauricio
Siumauricio merged commit 309dd56 into Dokploy:canary Aug 6, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Log viewer incorrectly marks lines as 'error' based on text keywords instead of actual log level

2 participants