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 receivescontractName and runHandle. Use contractName to group by contract and runHandle to correlate hooks from the same run.
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. On RULE_ERROR, ruleIssues includes structured rule names and fields.
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 with a tiny helper:
null (the dev-mode SDK fallback) and skipping any logger that doesn’t implement that hook. Drop it into your codebase as-is.
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