Skip to content

fix: log instead of printing to stdout/stderr in platform code - #6438

Merged
delchev merged 1 commit into
masterfrom
fix/no-println-use-logger
Jul 28, 2026
Merged

fix: log instead of printing to stdout/stderr in platform code#6438
delchev merged 1 commit into
masterfrom
fix/no-println-use-logger

Conversation

@delchev

@delchev delchev commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Why

A print has no level, no timestamp, no logger name and no thread. It never reaches the Logs view or log aggregation, cannot be turned down in production, and pollutes the console stream the IDE terminal shows. Found while reviewing #6435, then swept the whole repo.

The five real cases

File Was Now
EntityParser System.err.println("Warning: Could not parse length value…") — from a class with no logger at all LOGGER.warn("Could not parse the length value [{}] - ignoring it", length)
TypeScriptService every esbuild output line printed to stdout LOGGER.debug("[esbuild] {}", line)
WorkspaceSelectionTargetPair.skipByPath leftover debug print of two paths, every iteration deleted
VelocityGenerationEngine e.printStackTrace() on engine-init failure — with the proper logger call sitting right above it, commented out LOGGER.error("Failed to initialize the Velocity engine", e)
StaticObjects logged the error, then new Exception(message).printStackTrace() on top one logger.error(message, new IllegalStateException(message)), so the stack showing who asked for the missing object lands where something collects it

The one that must keep printing

ConsoleWebsocketHandler.distribute is called by the logging appender (ConsoleLoggingAppender), so logging inside it re-enters the appender and recurses. System.err is correct in that single method — I left it, added a comment saying why, and made it print the cause instead of discarding everything but getMessage(). (Worth stating plainly: blind-fixing that one would have introduced a recursion bug.)

Out of scope on purpose

main() CLIs (EntityTransformer, TypeScriptApiDocGenerator) and the startup banner, where stdout is the output; test-support modules and ITs.

The rule, written down

CLAUDE.md "Conventions worth knowing" gains "Never print to stdout or stderr - log" next to the existing "Always log the throwable", with the documented exception and the grep to run before committing. It also covers generated code: templates and scaffolded custom/ stubs use the client SDK logger (org.eclipse.dirigible.sdk.log.Logging), because a generated class is read as house style by every application developer — that half is repeated in engine-intent/CLAUDE.md, where it leaked. (The template + stub fixes themselves ride in #6435, which introduced them.)

🤖 Generated with Claude Code

A print has no level, no timestamp, no logger name and no thread; it never
reaches the Logs view or log aggregation, cannot be turned down in production,
and pollutes the console stream the IDE terminal shows. Five real cases:

- EntityParser: a "Could not parse length value" warning went to System.err from
  a class that had no logger at all -> LOGGER.warn with the value.
- TypeScriptService: every esbuild output line was printed to stdout ->
  LOGGER.debug("[esbuild] {}", line), so tool diagnostics are levelled like the
  rest of what the engine reports.
- WorkspaceSelectionTargetPair.skipByPath: a leftover debug print of two paths on
  every iteration -> deleted.
- VelocityGenerationEngine: engine-init failure did e.printStackTrace() with the
  proper logger call sitting right above it, commented out -> LOGGER.error with
  the throwable.
- StaticObjects: logged the error and THEN dumped a synthetic stack to stderr ->
  one logger.error(message, new IllegalStateException(message)), so the stack that
  shows who asked for the missing object lands where something collects it.

ConsoleWebsocketHandler.distribute keeps System.err deliberately: it is called BY
the logging appender (ConsoleLoggingAppender), so logging there would re-enter the
appender and recurse. Comment added saying so, and it now prints the cause instead
of discarding everything but getMessage().

The rule is now in CLAUDE.md ("Never print to stdout or stderr - log") next to the
existing "Always log the throwable", including the generated-code half: templates
and scaffolded stubs use the client SDK logger, because a generated class is read
as house style by every application developer.

Out of scope on purpose: main() CLIs (EntityTransformer, TypeScriptApiDocGenerator)
and the startup banner, where stdout IS the output; test-support modules and ITs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit 5bdd80f into master Jul 28, 2026
10 checks passed
@delchev
delchev deleted the fix/no-println-use-logger branch July 28, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant