Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

Pino Integration

Pino integration for oRPC provides structured logging capabilities, allowing you to easily track requests, monitor errors, and gain insights into your application’s 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 6, 2026

Was this page helpful?