Skip to content

Pino Integration

Pino is a fast and lightweight JSON logger. This guide explains how to integrate oRPC with Pino to add structured logging, request tracking, and error monitoring to your applications.

WARNING

This guide assumes familiarity with Pino. Review the official documentation if needed.

Installation

sh
npm install @orpc/experimental-pino@latest pino@latest
sh
yarn add @orpc/experimental-pino@latest pino@latest
sh
pnpm add @orpc/experimental-pino@latest pino@latest
sh
bun add @orpc/experimental-pino@latest pino@latest
sh
deno add npm:@orpc/experimental-pino@latest npm:pino@latest

Setup

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

ts
import { LoggingHandlerPlugin } from '@orpc/experimental-pino'
import pino from 'pino'

const logger = pino()

const handler = new RPCHandler(router, {
  plugins: [
    new LoggingHandlerPlugin({
      logger, // Custom logger instance
      generateId: ({ request }) => crypto.randomUUID(), // Custom ID generator
      logRequestResponse: true, // Log request start/end (disabled by default)
      logRequestAbort: true, // Log when requests are aborted (disabled by default)
    }),
  ],
})

INFO

The handler can be any supported oRPC handler, such as RPCHandler, OpenAPIHandler, or another custom handler.

TIP

For improved log readability during development, consider using pino-pretty to format your logs in a human-friendly way.

bash
npm run dev | npx pino-pretty

Using the Logger in Your Code

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

ts
import { getLogger } from '@orpc/experimental-pino'

const procedure = os.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:

ts
import {
  CONTEXT_LOGGER_SYMBOL,
  LoggerContext,
  LoggingHandlerPlugin
} from '@orpc/experimental-pino'

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

interface ORPCContext extends LoggerContext {} 

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

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

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

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

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

Released under the MIT License.