ContractLogger gives you a hook into every phase of a contract run — from the first attempt through parsing, verification, repair, retry, and terminal outcome. It’s a structural type (every hook optional), so you only implement the ones you need.
Event flow
Every hook receives a context object withcontractName so one logger can observe many contracts.
All 10 hooks
onRunStart
contract.accept(...) invocation, before any attempt runs.
onAttemptStart
repairs is empty. On later attempts, it contains the repair messages generated from prior failures.
onRawOutput
RunFn returned, before any cleaning or parsing.
onCleanedOutput
clean(raw) — JSON extracted from fences, de-prose’d, etc. Not yet validated.
onVerifySuccess
onVerifyFailure
category is the FailureCategory. issues is the list of violations.
onRepairGenerated
repairs: { CATEGORY: false }.
onRetryScheduled
delayMs is the computed delay for this retry based on your retry.backoff strategy.
onRunSuccess
attempts is the total number of attempts (including the successful one).
onRunFailure
category is the last failure’s category (may be undefined if the run errored before any verify).
Recipes
Custom metrics
OpenTelemetry spans
Structured debug logging
Use the built-in console logger for human-readable traces:debug: true for default verbosity.
Combining multiple loggers
You can only pass one logger todefineContract. To fan out to both Boundary and your metrics system, compose them manually:
Constraints
- Hooks are synchronous from the contract loop’s point of view. Returning a promise doesn’t delay the next phase — the loop moves on. Push heavy work into a batch (like
createBoundaryLoggerdoes) or a queue. - Exceptions inside hooks are caught and swallowed. Your logger cannot break a contract run.
- Hook order is guaranteed per attempt:
onAttemptStart→onRawOutput→onCleanedOutput→onVerifySuccess| (onVerifyFailure→onRepairGenerated→onRetryScheduled).
See also
SDK Overview
createBoundaryLogger is a ContractLogger
Engine primitives
Skip the loop, use the pieces directly