Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Pino Integration

Add structured logging to oRPC with the Pino integration to track requests, monitor errors, and gain insight into application behavior.

Installation

npm install @orpc/pino@beta pino@beta
pnpm add @orpc/pino@beta pino@beta
yarn add @orpc/pino@beta pino@beta
bun add @orpc/pino@beta pino@beta

Setup

To set up Pino with oRPC, use the PinoHandlerPlugin class. This plugin automatically instruments your handler with structured logging, request tracking, and error monitoring.

import { class PinoHandlerPlugin<T extends Context>
Instruments an oRPC handler with Pino structured logging, request tracking, and error monitoring.
@see{@link https://orpc.dev/docs/integrations/pino Pino Integration}
PinoHandlerPlugin
} from '@orpc/pino'
import function pino<CustomLevels extends string = never, UseOnlyCustomLevels extends boolean = boolean>(optionsOrStream?: pino.LoggerOptions<CustomLevels, UseOnlyCustomLevels> | pino.DestinationStream): pino.Logger<CustomLevels, UseOnlyCustomLevels> (+1 overload)
@paramoptionsOrStream : an options object or a writable stream where the logs will be written. It can also receive some log-line metadata, if the relative protocol is enabled. Default: process.stdout@returnsa new logger instance.
pino
from 'pino'
const const logger: pino.Logger<never, boolean>logger = pino<never, boolean>(optionsOrStream?: pino.DestinationStream | pino.LoggerOptions<never, boolean> | undefined): pino.Logger<never, boolean> (+1 overload)
@paramoptionsOrStream : an options object or a writable stream where the logs will be written. It can also receive some log-line metadata, if the relative protocol is enabled. Default: process.stdout@returnsa new logger instance.
pino
()
const
const handler: RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
handler
= new
new RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>(router: Router<{
    headers?: IncomingHttpHeaders;
} & object>, options?: NoInfer<RPCHandlerOptions<{
    headers?: IncomingHttpHeaders;
} & object>>): RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
Serves an oRPC router over the RPC protocol using the Fetch API (Request/Response), supported by modern runtimes like Deno, Bun, Cloudflare Workers, and browsers.
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCHandler
(
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<...>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
, {
FetchHandlerOptions<{ headers?: IncomingHttpHeaders; } & object>.plugins?: FetchHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>[] | undefined
plugins
: [
new
new PinoHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>(options?: PinoHandlerPluginOptions<{
    headers?: IncomingHttpHeaders;
} & object>): PinoHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>
Instruments an oRPC handler with Pino structured logging, request tracking, and error monitoring.
@see{@link https://orpc.dev/docs/integrations/pino Pino Integration}
PinoHandlerPlugin
({
PinoHandlerPluginOptions<T extends Context>.logger?: pino.Logger | undefined
Logger instance to use for logging.
@defaultpino()
logger
, // <- custom logger instance
PinoHandlerPluginOptions<{ headers?: IncomingHttpHeaders; } & object>.generateRequestId?: ((options: StandardHandlerRoutingInterceptorOptions<{
    headers?: IncomingHttpHeaders;
} & object>) => string) | undefined
Function to generate a unique ID for each request.
@default({ request }) => flattenStandardHeader(request.headers['x-request-id']) ?? crypto.randomUUID()
generateRequestId
: ({ request: StandardLazyRequestrequest }) => var crypto: Crypto
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/crypto)
crypto
.Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}`
The **`randomUUID()`** method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator. Available only in secure contexts. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID)
randomUUID
(), // <- custom request id generator
PinoHandlerPluginOptions<T extends Context>.logLifecycle?: boolean | undefined
If true, this plugin will log information about request lifecycle, including when a request is received, handled, or no matching procedure is found.
@defaultfalse
logLifecycle
: true, // <- log information about request lifecycle (disabled by default)
PinoHandlerPluginOptions<T extends Context>.logAbort?: boolean | undefined
If true, this plugin will log when a request signal is aborted.
@defaultfalse
logAbort
: true, // <- log information when requests are aborted (disabled by default)
}), ], })

Using the Logger in Your Code

You can access the logger from the context object using the getLogger function:

import { getLogger, LoggerContext } from '@orpc/pino'

interface ServerContext extends LoggerContext {} 

const procedure = os
  .$context<ServerContext>()
  .handler(({ context }) => {
    const logger = getLogger(context) 

    logger?.info('Processing request')
    logger?.debug({ userId: 123 }, 'User data')

    return { success: true }
  })

Providing Custom Logger per Request

You can provide a custom logger instance for specific requests by passing it through the context. This is especially useful when integrating with pino-http for enhanced HTTP logging:

import {
  LOGGER_CONTEXT_SYMBOL,
  LoggerContext,
  PinoHandlerPlugin
} from '@orpc/pino'

const logger = pino()
const httpLogger = pinoHttp({ logger })

interface ServerContext extends LoggerContext {} 

const router = {
  ping: os.$context<ServerContext>().handler(() => 'pong')
}

const handler = new RPCHandler(router, {
  plugins: [
    new PinoHandlerPlugin({ logger }), 
  ],
})

const server = createServer(async (req, res) => {
  httpLogger(req, res)

  const { matched } = await handler.handle(req, res, {
    prefix: '/api',
    context: {
      [LOGGER_CONTEXT_SYMBOL]: req.log, 
    },
  })

  if (!matched) {
    res.statusCode = 404
    res.end('Not Found')
  }
})

Last updated on August 8, 2026

Was this page helpful?