feat(pino,evlog): allow customizing log level for procedure errors in pino and evlog plugins - #1949
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
orpc | dec6126 | Commit Preview URL Branch Preview URL |
Aug 25 2026, 08:31 AM |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
ℹ️ Minor suggestions inline — the change is clean and well-tested.
Reviewed changes
procedureErrorLeveloption — new(error, level) => leveloption on bothPinoHandlerPluginandEvlogHandlerPluginto customize the per-procedure error log level (resolves #1930).- Default level mapping — refactored the two near-identical error-logging helpers into a single
logProcedureError+defaultErrorLevelper plugin; the deliberate behavior change is thatORPCErrorwithINTERNAL_SERVER_ERRORnow logs aterror(previouslywarn). - Plugin wiring — option applied at all three sites per plugin (interceptor catch, async-iterator and readable-stream
onError); internal handler failures stay aterror. - Tests + docs — both suites extended with exact assertions (INTERNAL-SERVER stays error, custom fn receives
(error, defaultLevel), returning the incoming level preserves the default); both integration pages gained an "Error Logging Levels" section.
The default mapping, the INTERNAL_SERVER_ERROR exception, the option plumbing at every logging site, and the interaction-safe behavior all check out. Tests use exact assertions rather than loose ones, so they can genuinely regress. Two small nits inline only.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues from the added commit — tests are solid. The two minor notes from the initial review remain open.
Reviewed changes
logProcedureErrorearly-return — evlog's helper now bails when no logger, so the customprocedureErrorLevelcallback isn't invoked for calls with no logger in context. Matches pino's existing short-circuit (logger?.[key](...)) for consistent behavior.- No-logger test — pins that
procedureErrorLevelis not called when the context has no logger. createLoggerStorageadapter test — newpackages/evlog/src/adapters/node.test.tscovers the AsyncLocalStorage-backeduseLogger(returns the stored logger insidestorage.run, throws the configured message outside).
The new tests use exact assertions and can genuinely fail. Production delta is limited to the early-return guard; both plugins now behave the same and skip the callback when there's no logger. No new findings.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| * | ||
| * @default (error, level) => level | ||
| */ | ||
| procedureErrorLevel?: (error: unknown, level: pino.Level) => pino.Level |
There was a problem hiding this comment.
Could we do something like this to allow for custom levels to be used?
export interface PinoHandlerPluginOptions<T extends Context, L extends string = never> {
logger?: Logger<L>
...
procedureErrorLevel?: (error: unknown, level: pino.Level) => pino.Level | L
}
There was a problem hiding this comment.
Feel free to create another github issue for this feature request, I need more votes
| error: unknown, | ||
| procedureErrorLevel: Exclude<PinoHandlerPluginOptions<any>['procedureErrorLevel'], undefined>, | ||
| ) { | ||
| logger?.[procedureErrorLevel(error, defaultProcedureErrorLevel(error))](error) |
There was a problem hiding this comment.
If a level isn't found, this would end up throwing. This would help mitigate issues around that, but perhaps there is also an improvement to wrap this in try/catch that way a log can never be swallowed.
| logger?.[procedureErrorLevel(error, defaultProcedureErrorLevel(error))](error) | |
| logger?.[procedureErrorLevel(error, defaultProcedureErrorLevel(error))]?.(error) |
| logger?.[procedureErrorLevel(error, defaultProcedureErrorLevel(error))](error) | |
| const defaultLevel = defaultProcedureErrorLevel(error); | |
| try { | |
| logger?.[procedureErrorLevel(error, defaultLevel)]?.(error) | |
| } catch (e) { | |
| // fallback to default level so the log can't be swallowed | |
| logger?.[defaultLevel](error); | |
| } |
There was a problem hiding this comment.
There are no typescript error, so it should be safe

The pino and evlog handler plugins now log procedure errors at a level matching their intent instead of treating every failure the same, and both accept a new
procedureErrorLeveloption to customize that level per error. This lets expected business rejections stay out of error-level alerting while genuine failures keep full severity.Resolves #1930
Behavior
info, deliberately thrownORPCErrors aswarn, and everything unexpected aserror— includingORPCErrorwith theINTERNAL_SERVER_ERRORcode, since oRPC itself throws it for failures like output validation.procedureErrorLevel: (error, level) => Levelreceives the level applied by default and returns the level to use, so overriding one case is a single condition withreturn levelas the fallback.error.Docs
Both integration pages gained an "Error Logging Levels" section stating the defaults and showing a
procedureErrorLevelexample.Testing
Existing suites extended:
INTERNAL_SERVER_ERRORno longer downgraded, custom resolver receives(error, defaultLevel)and its return value is honored, returning the incoming level preserves default behavior. All package tests, repo-wide type checks, and lint pass.