Add log4cats logging with --verbose and --debug - #127
Merged
Conversation
Closes #63. There was no way to see what cellar was doing. DocstringExtractor in particular swallowed every failure with `catch case _: Exception => ()`, so a TASTy inspection that failed was indistinguishable from a symbol that genuinely has no docstring. Adds log4cats-core and a StderrLogger implementing Logger[IO]. It is deliberately not slf4j-backed: slf4j-nop is on the CLI classpath to silence coursier, and a real backend would un-silence it and add native-image weight. Diagnostics go to stderr only, so stdout stays clean enough to pipe into a prompt. CELLAR_LOG=verbose|debug sets the level without a flag, which allows debugging an installed binary; an explicit flag wins over it. DocstringExtractor.extract now returns IO and reports which .tasty entry it inspected, whether the inspector callback ever fired, and any failure. It catches Throwable rather than Exception — a compiler that cannot start under native-image fails with an Error, which the old catch let escape the diagnostic entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closes #56. The previous commit added the logging mechanism but pointed it only at the docstring path. #56's motivating case is the opposite one — a symbol reported as not found despite being in the JAR — which was still silent. Adds a ResolutionTrace that records which lookup strategies SymbolResolver tried. The lookups are synchronous and cannot write to a Logger[IO] as they go, so the trace is drained and logged by `resolve`. A miss is what people actually debug, so the trail is reported at verbose on failure and kept at debug on success. Also logs, at debug: the resolved classpath, build-tool detection, and classpath cache hits. And at warn: classpath entries dropped by readClasspathRobust, which silently discarded entries — dropping one can turn a present symbol into a "not found". NearMatchFinder used a java.util.logging logger gated behind Level.FINE with no configuration, so its output went nowhere; it now uses the same Logger[IO]. Its skipped-entry reports are summarised at verbose and detailed at debug, since the JRE alone contributes dozens every run and the per-entry detail would drown the trail. The flags now apply to get, get-external, get-source, list, list-external, search and search-external. findStaticTerm/findStaticType/findPackage keep using a non-recording variant of the trace helper, mirroring tryOrNone: their failures must not promote a NotFound into a LookupFailed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A resolution failure previously printed only "Could not resolve '<coord>'. Check that the group ID, artifact ID, and version are correct." Coursier names every location it consulted in its own message, and CoordinateNotFound was already holding that cause and discarding it. The first question on a resolution failure is whether cellar even looked at the repository you expected, so this belongs in the error rather than behind a flag. The leading line of coursier's message is dropped since it restates the coordinate we already printed. Also attaches coursier's progress bars to stderr when diagnostics are on, so a slow fetch shows which URL it is waiting on. coursierapi.Logger exposes only nop() and progressBars() — no overridable callbacks — so this is the only route through the interface API. The bars emit ANSI cursor control unconditionally, which corrupts a redirected stream, so they are attached only when stderr is a terminal. --verbose/--debug now also apply to deps and meta, which resolve coordinates and so hit the same failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
rochala
force-pushed
the
feat/log4cats-verbose-debug
branch
from
August 22, 2026 11:05
a1a43c5 to
6def988
Compare
Catch inside the blocking thunk rather than via `.attempt`. The failure this diagnostic exists for — a compiler that cannot start under native-image — arrives as a LinkageError, which is not NonFatal. cats-effect routes those to onFatalFailure and tears down the runtime without ever reaching `.attempt`, so the previous code would have died silently on exactly the case it was written to report. Confirmed by running both shapes: `.attempt` never fires, the in-thunk catch does. Delete the coursier progress bars. They were gated on `System.console() != null`, which reflects stdin/stdout and never stderr, and which returns non-null even when redirected on JDK 22+ (the native image targets GraalVM 23). Redirecting stderr to collect a diagnostics file would therefore have written ANSI cursor control into that file. Java exposes no way to test whether stderr is a terminal, so this cannot be gated correctly; the unconditional "Tried:" list already names the URLs, which was the point. Delete the temp file when the copy out of the jar throws. Only the caller's `guarantee` deleted it, and that never runs if the copy fails. Scope the docs to what is actually instrumented: the lookup-strategy trail comes from SymbolResolver, which list and search do not use — they resolve through SymbolLister. They still get classpath and fetch diagnostics at --debug. 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.
Closes #63. Closes #56.
There was no way to see what cellar was doing.
DocstringExtractorswallowed every failure withcatch case _: Exception => (),NearMatchFinderlogged through ajava.util.logginglogger gated behindLevel.FINEwith no configuration (so its output went nowhere), andSymbolResolverreported "not found" without saying what it had tried.The logger
log4cats-coreplus aStderrLoggerimplementingLogger[IO]. Deliberately not slf4j-backed:slf4j-nopis on the CLI classpath to silence coursier, and a real backend would both un-silence it and add native-image weight.-v/--verbose— progress and warnings--debug— detail plus stack tracesCELLAR_LOG=verbose|debug— same, without a flag, so an installed binary can be debugged without a rebuild. An explicit flag wins.Available on
get,get-external,get-source,list,list-external,search,search-external. Output goes to stderr only, so stdout stays clean enough to pipe into a prompt. The logger is threaded as a defaulted parameter (StderrLogger.off), so no existing call site or test changed.Resolution diagnostics (#56)
ResolutionTracerecords which strategiesSymbolResolvertried. The lookups are synchronous and cannot write to aLogger[IO]as they go, so the trace is drained and logged byresolve. A miss is what people actually debug, so the trail is reported at verbose on failure and kept at debug on success:--debugadds the resolved classpath, build-tool detection, cache hits, per-entry scan failures, and stack traces.Two silent behaviours now surfaced
ContextResource.readClasspathRobustdiscarded classpath entries that trip a tasty-queryMatchError, with no report. Dropping one can turn a present symbol into a "not found", so it is now logged at warn.DocstringExtractornow reports which.tastyentry it inspected, whether the inspector callback ever fired, and any failure. It uses.attempt, which catchesThrowablerather thanException— a compiler that cannot start under native-image fails with anError, which the old catch let escape the diagnostic entirely.Behaviour preserved
findStaticTerm/findStaticType/findPackageuse a non-recording variant of the trace helper, mirroringtryOrNone: their failures must not promote aNotFoundinto aLookupFailed. Routing them through the recording variant would have been a behaviour change smuggled in under a logging patch.Why this exists now
Docstrings appear on the JVM but not in the released
v0.1.0-M10native binary, even thoughDocstringExtractoris byte-identical between M10 and HEAD. Buildingcli.assemblyand running it reproduces the working behaviour, which rules out fat-jar classpath merging and leaves native-image as the suspect. This PR is the instrument needed to see the actual failure instead of a swallowed one; diagnosing it is follow-up work.Testing
./mill lib.test372/372,./mill cli.test236/236. Verified via./mill cli.runand thecli.assemblyjar, on both the success and not-found paths.🤖 Generated with Claude Code