ブログ記事

Pino完全ガイド 2025 - Node.js最速のロギングライブラリ

5倍以上の高速化を実現するNode.jsロガーPinoを徹底解説。JSONロギング、トランスポートシステム、本番環境での活用方法まで、パフォーマンスを重視したロギング戦略の全てを網羅します。

ツール
Pino Node.js ロギング パフォーマンス JSON
Pino完全ガイド 2025 - Node.js最速のロギングライブラリのヒーロー画像

Pino は、Node.jsアプリケーションのための超高速JSONロガーです。従来のロギングライブラリと比較して5倍以上の高速化を実現し、本番環境でのパフォーマンス低下を最小限に抑えます。

この記事で学べること

  • Pino の基本概念と高速化の仕組み
  • Winston/Bunyan からの移行方法
  • トランスポートシステムとログ処理
  • 本番環境での最適な設定方法
  • エコシステムツールの活用方法

なぜPinoを選ぶべきなのか?

ロギングのオーバーヘッド

従来のロガーが原因でアプリケーションが遅延

メモリ使用量

大量のログでメモリ逼迫

ログフォーマット

構造化されていないログの解析困難

Pinoの登場

最小オーバーヘッド・JSON形式

デファクトスタンダード

Fastify等の主要フレームワークが採用

パフォーマンスベンチマーク

Node.jsロギングライブラリ パフォーマンス比較
ロガー 処理速度 (ops/sec) Pino比 メモリ使用量
Pino 150,000 1.0x 10MB
Winston 25,000 6.0x遅い 45MB
Bunyan 30,000 5.0x遅い 38MB
Debug 15,000 10.0x遅い 25MB
Console.log 100,000 1.5x遅い 5MB
Pino - 最速のNode.jsロガー 100 %
完了

クイックスタート

インストールと基本設定

// インストール
npm install pino

// 基本的な使用方法
const pino = require('pino');
const logger = pino({
  level: process.env.PINO_LOG_LEVEL || 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true,
      translateTime: 'HH:MM:ss Z',
      ignore: 'pid,hostname'
    }
  }
});

logger.info('Hello world');
logger.info({ user: 'john', action: 'login' }, 'User logged in');
logger.error(new Error('Something went wrong'));

// 子ロガーの作成
const childLogger = logger.child({ module: 'auth' });
childLogger.info('Authentication module initialized');
// express-pinoのインストール
npm install express-pino-logger

const express = require('express');
const pino = require('pino');
const expressPino = require('express-pino-logger');

const logger = pino({ level: 'info' });
const expressLogger = expressPino({ logger });

const app = express();
app.use(expressLogger);

app.get('/', (req, res) => {
  // req.logが自動的に利用可能
  req.log.info('Homepage requested');
  res.send('Hello World');
});

// リクエストIDの自動追加
app.use(expressPino({
  logger,
  genReqId: (req) => req.headers['x-request-id'] || crypto.randomUUID(),
  serializers: {
    req: (req) => ({
      method: req.method,
      url: req.url,
      headers: req.headers
    })
  }
}));
// Fastifyには標準搭載
const fastify = require('fastify')({
  logger: {
    level: 'info',
    prettyPrint: process.env.NODE_ENV !== 'production',
    serializers: {
      res(reply) {
        return {
          statusCode: reply.statusCode,
          responseTime: reply.getResponseTime()
        }
      },
      req(request) {
        return {
          method: request.method,
          url: request.url,
          path: request.routerPath,
          parameters: request.params
        }
      }
    }
  }
});

fastify.get('/', async (request, reply) => {
  request.log.info('Homepage route');
  return { hello: 'world' };
});

// カスタムログレベル
fastify.log.level = 'debug';

ログレベルとフィルタリング

Pinoのログレベル階層

チャートを読み込み中...

// カスタムレベルの定義
const logger = pino({
  customLevels: {
    audit: 35,
    critical: 55
  },
  useOnlyCustomLevels: false,
  level: 'info'
});

// 条件付きロギング
logger.level = 'info';
logger.debug('This will not be logged');
logger.info('This will be logged');

// 動的レベル変更
if (process.env.NODE_ENV === 'development') {
  logger.level = 'debug';
}

トランスポートシステム

Pino の強力な機能の 1 つは、ログ処理を別プロセスに委譲するトランスポートシステムです:

// Winstonの例 const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ // ファイル書き込み(同期的) new winston.transports.File({ filename: 'error.log', level: 'error' }), // コンソール出力(同期的) new winston.transports.Console({ format: winston.format.simple() }) ] }); // メインスレッドをブロック logger.info('Blocking operation');
// Pinoトランスポート const pino = require('pino'); const transport = pino.transport({ targets: [{ target: 'pino-pretty', options: { destination: 1 } // stdout }, { target: 'pino/file', options: { destination: './app.log' } }, { target: '@logtail/pino', options: { sourceToken: 'xxx' } }] }); const logger = pino(transport); // 非ブロッキング logger.info('Non-blocking operation');
従来の方法
// Winstonの例 const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ // ファイル書き込み(同期的) new winston.transports.File({ filename: 'error.log', level: 'error' }), // コンソール出力(同期的) new winston.transports.Console({ format: winston.format.simple() }) ] }); // メインスレッドをブロック logger.info('Blocking operation');
Pinoトランスポート
// Pinoトランスポート const pino = require('pino'); const transport = pino.transport({ targets: [{ target: 'pino-pretty', options: { destination: 1 } // stdout }, { target: 'pino/file', options: { destination: './app.log' } }, { target: '@logtail/pino', options: { sourceToken: 'xxx' } }] }); const logger = pino(transport); // 非ブロッキング logger.info('Non-blocking operation');

カスタムトランスポートの作成

// transport.js
module.exports = (options) => {
  return require('pino-abstract-transport')(async (source) => {
    for await (const obj of source) {
      // カスタム処理
      if (obj.level >= 50) { // error以上
        await sendToSlack(obj);
      }
      
      // Elasticsearch送信
      await indexToElasticsearch({
        ...obj,
        '@timestamp': new Date(obj.time).toISOString()
      });
    }
  });
};

// 使用方法
const logger = pino({
  transport: {
    target: './transport.js',
    options: {
      slackWebhook: process.env.SLACK_WEBHOOK,
      esEndpoint: process.env.ES_ENDPOINT
    }
  }
});

本番環境での最適化

1. 環境別設定

// config/logger.js
const pino = require('pino');

const isProduction = process.env.NODE_ENV === 'production';

const options = {
  level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
  
  // 本番環境では整形を無効化
  ...(isProduction
    ? {}
    : {
        transport: {
          target: 'pino-pretty',
          options: {
            colorize: true,
            translateTime: 'SYS:standard',
            ignore: 'pid,hostname'
          }
        }
      }
  ),
  
  // リダクション設定
  redact: {
    paths: ['password', 'token', '*.secret'],
    remove: true
  },
  
  // シリアライザー
  serializers: {
    err: pino.stdSerializers.err,
    req: (req) => ({
      id: req.id,
      method: req.method,
      url: req.url,
      remoteAddress: req.remoteAddress
    }),
    res: (res) => ({
      statusCode: res.statusCode
    })
  }
};

module.exports = pino(options);

2. ログローテーション

// pino-rotating-file-stream の使用
const pino = require('pino');
const { multistream } = require('pino');
const { createStream } = require('rotating-file-stream');

const streams = [
  // 標準出力(JSON形式)
  { stream: process.stdout },
  
  // ローテーションファイル
  {
    level: 'error',
    stream: createStream('error.log', {
      size: '10M',
      interval: '1d',
      compress: 'gzip',
      path: './logs'
    })
  }
];

const logger = pino({
  level: 'info'
}, multistream(streams));

構造化ロギングのベストプラクティス

// 構造化されたログ
const logger = pino();

// ❌ 避けるべきパターン
logger.info(`User ${userId} logged in from ${ip}`);

// ✅ 推奨パターン
logger.info({
  event: 'user_login',
  userId,
  ip,
  userAgent: req.headers['user-agent'],
  timestamp: Date.now()
}, 'User login successful');

// コンテキスト付きロギング
const userLogger = logger.child({ 
  userId,
  sessionId: req.sessionID 
});

userLogger.info({ action: 'view_profile' }, 'Profile viewed');
userLogger.info({ action: 'update_settings' }, 'Settings updated');
// エラーログの構造化
class AppError extends Error {
  constructor(message, code, statusCode) {
    super(message);
    this.code = code;
    this.statusCode = statusCode;
    this.timestamp = new Date().toISOString();
  }
}

// エラーハンドリング
app.use((err, req, res, next) => {
  const errorLog = {
    error: {
      message: err.message,
      stack: err.stack,
      code: err.code || 'INTERNAL_ERROR',
      statusCode: err.statusCode || 500
    },
    request: {
      method: req.method,
      url: req.url,
      headers: req.headers,
      body: req.body
    },
    context: {
      userId: req.user?.id,
      requestId: req.id
    }
  };
  
  req.log.error(errorLog, 'Request error');
  
  res.status(errorLog.error.statusCode).json({
    error: errorLog.error.message,
    requestId: req.id
  });
});
// パフォーマンス計測
const logger = pino();

// 時間計測ヘルパー
function createTimer(name) {
  const start = process.hrtime.bigint();
  
  return {
    end: (metadata = {}) => {
      const duration = Number(process.hrtime.bigint() - start) / 1e6;
      logger.info({
        metric: 'timing',
        name,
        duration,
        unit: 'ms',
        ...metadata
      }, `${name} completed in ${duration}ms`);
      return duration;
    }
  };
}

// 使用例
async function processOrder(orderId) {
  const timer = createTimer('order_processing');
  
  try {
    const order = await fetchOrder(orderId);
    await validateOrder(order);
    await processPayment(order);
    
    timer.end({ 
      orderId, 
      amount: order.total,
      success: true 
    });
  } catch (error) {
    timer.end({ 
      orderId, 
      success: false,
      error: error.message 
    });
    throw error;
  }
}

エコシステムツール

pino-pretty(開発環境用)

# インストール
npm install -D pino-pretty

# 使用方法
node app.js | pino-pretty

# カスタマイズ
node app.js | pino-pretty \
  --colorize \
  --translateTime "SYS:standard" \
  --ignore "pid,hostname" \
  --messageKey "msg"

ログ分析ツール

// pino-elasticsearch
const pinoElastic = require('pino-elasticsearch');

const streamToElastic = pinoElastic({
  index: 'app-logs',
  consistency: 'one',
  node: 'http://localhost:9200',
  'es-version': 8,
  'flush-bytes': 1000
});

const logger = pino({ level: 'info' }, streamToElastic);

// pino-cloudwatch
const pinoCloudWatch = require('pino-cloudwatch');

const stream = pinoCloudWatch({
  group: '/aws/lambda/my-function',
  prefix: process.env.AWS_LAMBDA_FUNCTION_NAME,
  interval: 5000,
  aws_region: process.env.AWS_REGION
});

const logger = pino({ level: 'info' }, stream);

移行ガイド

Winstonからの移行

const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.Console({ format: winston.format.simple() }) ] }); logger.info('Info message'); logger.error('Error message', { error: new Error('Test') });
const pino = require('pino'); const logger = pino({ level: 'info', base: { service: 'user-service' }, timestamp: pino.stdTimeFunctions.isoTime, transport: { targets: [{ level: 'error', target: 'pino/file', options: { destination: 'error.log' } }, { target: 'pino-pretty', options: { destination: 1 } }] } }); logger.info('Info message'); logger.error({ err: new Error('Test') }, 'Error message');
Winston
const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.Console({ format: winston.format.simple() }) ] }); logger.info('Info message'); logger.error('Error message', { error: new Error('Test') });
Pino
const pino = require('pino'); const logger = pino({ level: 'info', base: { service: 'user-service' }, timestamp: pino.stdTimeFunctions.isoTime, transport: { targets: [{ level: 'error', target: 'pino/file', options: { destination: 'error.log' } }, { target: 'pino-pretty', options: { destination: 1 } }] } }); logger.info('Info message'); logger.error({ err: new Error('Test') }, 'Error message');

Pino は単なるロガーではありません。アプリケーションのパフォーマンスを犠牲にすることなく、本番環境で必要な全ての情報を記録できるよう設計されています。

Matteo Collina Pino作者・Node.js TSC

まとめ

Pino は、パフォーマンスと開発体験の両立を実現した、Node.jsアプリケーションのための理想的なロギングソリューションです:

  1. 圧倒的な高速性: 従来のロガーの 5 倍以上の速度
  2. 低オーバーヘッド: アプリケーションへの影響を最小化
  3. 構造化ログ: JSON 形式で解析・検索が容易
  4. 柔軟なトランスポート: ログ処理の分離
  5. 豊富なエコシステム: 様々なツールとの統合

特に、高トラフィックな API サーバーやマイクロサービスアーキテクチャでの採用を強く推奨します。

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。