Skip to content

feat(logger): format logs and errors like ponder - #75

Open
kyscott18 wants to merge 2 commits into
mainfrom
logs-and-errors
Open

kyscott18 wants to merge 2 commits into
mainfrom
logs-and-errors

Conversation

@kyscott18

Copy link
Copy Markdown
Collaborator

Logs and errors now print in ponder's format, in both pretty and JSON. An error prints its message and its causes. Stacks are printed only for unknown errors (bugs), not for typewriter's own tagged errors.

Output

Pretty:

17:06:51.055 DEBUG Executed mutation mutation=transfer execution_index=12 (3ms)
17:06:51.056 DEBUG Failed RPC request method=eth_getBlockByNumber attempt=2
RpcNetworkError: Failed 'eth_getBlockByNumber' request to http://localhost:8545
  cause: TypeError: fetch failed
  cause: Error: connect ECONNREFUSED 127.0.0.1:8545
17:29:05.145 DEBUG Rejected mutation mutation=A
MutationRevertedError: Mutation reverted with InsufficientBalance("0x0000000000000000000000000000000000000001", 5)
17:07:35.037 ERROR Failed to start typewriter
ConfigError: Typewriter entrypoint file does not exist: /nope/App.sol

JSON (one line per log):

{"level":"error","time":"2026-10-26T17:29:05.358Z","msg":"Stopped typewriter","error":{"name":"TypewriterStoppedError","message":"Typewriter stopped","cause":{"name":"DbError","message":"Failed database query: select 1","sql":"select 1","cause":{"name":"PostgresError","message":"relation \"foo\" does not exist"}}}}

Changes

  • Logger (src/logger.ts): a single logger replaces Effect's pretty and structured loggers.
    • Pretty: time LEVEL Msg key=value (duration).
    • JSON: a flat record with level, time, msg, the annotations, and error.
    • Errors are collected from Error message arguments, an error annotation, and the Effect Cause.
    • A _tag marks a known error, which prints as Name: message. Any other error keeps its stack. Causes never print stacks.
  • Error messages: tagged errors that had an empty message now have one. This includes the RPC errors (RpcResponseError used to log as a bare RpcResponseError:), DbError, BlockNotFoundError and the revert errors.
  • ConfigError: config validation and Solidity parsing throw it instead of Error. A forge build failure includes forge's output.
  • Logger provided at the edges: in createTypewriter and in the simulation entrypoint, no longer in typewriter.ts.
    • The simulation's own logs are at Info, and TYPEWRITER_LOG_LEVEL applies only to the runtime. This removes the None level workaround and the category: sim annotation.
  • Wording: messages are capitalized and past tense, keys are snake_case, and each simulation log says it's part of the simulation.

Behavior changes to review

  • createTypewriter exits the process on a startup failure, instead of rejecting. It logs "Failed to start typewriter" and calls process.exit(1). Bun ignores an overwritten error.stack and always prints a code frame and stack for an uncaught error, so letting the error escape would bring the noise back. A stopped program already crashed the process; it now logs "Stopped typewriter" first. Previously only typed failures crashed it, and a defect stopped the program silently.
  • The JSON shape changed: message becomes msg, annotations are flat instead of nested, and levels are lowercase.
  • "Rejected mutation" moved from ERROR to DEBUG. It fired on every contract revert, and the caller already receives the error.
  • Code that calls createTypewriterEffect directly gets Effect's default logger unless it provides loggerLayer itself.

Testing

  • tsc --noEmit, biome check, bun test (27 pass)
  • Simulation seed 0x01 passes at the default level and at TYPEWRITER_LOG_LEVEL=debug
  • Checked by hand: startup failure output and exit code through a pipe, a failed simulation, and JSON output

🤖 Generated with Claude Code

Replace Effect's pretty and structured loggers with one that prints a
`time LEVEL Msg key=value (12ms)` line, or a flat JSON record with `level`,
`time`, `msg`, the annotations, and `error`. An error prints as
`Name: message` with one `cause:` line per cause. Only an error without a
`_tag`, which is a bug, keeps its stack; a tagged error's stack points into
typewriter and is noise.

- Give the tagged errors that had an empty message (the RPC errors,
  DbError, BlockNotFoundError, NoDataReturnedError, EncodeMutationError,
  UnknownMutationError, SignTransactionError, ProgramStoppedError,
  TypewriterStoppedError, UnrecoverableReorgError) a message. Revert
  messages include the decoded error and its arguments.
- Throw ConfigError instead of Error from config validation and Solidity
  parsing. A forge build failure includes forge's output.
- Provide the logger once at the edges, in createTypewriter and in the
  simulation entrypoint, instead of in typewriter.ts. The simulation logs
  at Info and applies TYPEWRITER_LOG_LEVEL to the runtime only, so the
  `None` level workaround and the `category: sim` annotation go.
- createTypewriter logs a startup failure or a stopped program and exits
  with code 1, instead of an uncaught throw that Bun prints with a code
  frame and stack.
- Log a rejected mutation at debug: the caller gets the error.
- Use capitalized, past-tense messages and snake_case keys, and make each
  simulation log say it is part of the simulation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read log durations from the Clock's monotonic time instead of
performance.now(), so a simulation logs virtual durations that repeat for a
seed. The simulation clock's nanosecond time rounds a fractional
millisecond, which BigInt rejected.

Reword the lowercase ConfigError messages and the revm halt errors to the
sentence-case tone of the other messages: "Could not find …",
"Expected X to have Y", and "halted with <reason>" like "reverted with".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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