fix: log instead of printing to stdout/stderr in platform code - #6438
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
EntityParserSystem.err.println("Warning: Could not parse length value…")— from a class with no logger at allLOGGER.warn("Could not parse the length value [{}] - ignoring it", length)TypeScriptServiceLOGGER.debug("[esbuild] {}", line)WorkspaceSelectionTargetPair.skipByPathVelocityGenerationEnginee.printStackTrace()on engine-init failure — with the proper logger call sitting right above it, commented outLOGGER.error("Failed to initialize the Velocity engine", e)StaticObjectsnew Exception(message).printStackTrace()on toplogger.error(message, new IllegalStateException(message)), so the stack showing who asked for the missing object lands where something collects itThe one that must keep printing
ConsoleWebsocketHandler.distributeis called by the logging appender (ConsoleLoggingAppender), so logging inside it re-enters the appender and recurses.System.erris correct in that single method — I left it, added a comment saying why, and made it print the cause instead of discarding everything butgetMessage(). (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 scaffoldedcustom/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 inengine-intent/CLAUDE.md, where it leaked. (The template + stub fixes themselves ride in #6435, which introduced them.)🤖 Generated with Claude Code