Node.js アプリケーション向け New Relic APM 設定ガイド

Node.js アプリケーションでの New Relic APM 導入は、イベント駆動アーキテクチャと非同期処理の特性を活かした高度な監視ソリューションを提供します。Node.js エージェントは、非同期コンテキストの追跡、マイクロサービス間の連携監視、リアルタイム性能分析を実現します。

Node.js エージェントの特徴

New Relic Node.js エージェントは、V8 エンジンレベルでの統合により、JavaScript の非同期実行モデルを深く理解した監視を提供します。Promise、async/await、コールバック関数などの非同期パターンを自動的に追跡し、複雑な実行フローを可視化します。

Express、Koa、Fastify、NestJS などの主要フレームワークとの自動統合により、ルーティング、ミドルウェア、HTTP リクエスト処理を包括的に監視できます。また、WebSocket 通信、Server-Sent Events、GraphQL リゾルバーなど、現代的なWeb技術パターンにも対応しています。

導入前の準備

環境要件の確認

Node.js エージェントは Node.js 14.x 以降をサポートしており、LTS バージョンでの使用を推奨します。ES6+ モジュール、TypeScript、CommonJS の各モジュールシステムに対応しています。

プロジェクト構成の確認

パッケージ管理には npm、yarn、pnpm などの標準的なツールを使用でき、モノレポ構成でも適切に動作します。

エージェントのインストール

npm を使用したインストール

bash
npm install newrelic

yarn を使用したインストール

bash
yarn add newrelic

TypeScript プロジェクトでの型定義

bash
npm install --save-dev @types/newrelic

基本設定の構成

設定ファイルの作成

プロジェクトルートに newrelic.js 設定ファイルを作成します。

javascript
// newrelic.js
'use strict'

exports.config = {
  app_name: ['My Node.js Application'],
  license_key: 'YOUR_LICENSE_KEY_HERE',
  logging: {
    level: 'info',
    filepath: 'stdout'
  },
  allow_all_headers: true,
  attributes: {
    exclude: [
      'request.headers.cookie',
      'request.headers.authorization',
      'request.headers.proxyAuthorization',
      'request.headers.setCookie*',
      'request.headers.x*',
      'response.headers.cookie',
      'response.headers.authorization',
      'response.headers.proxyAuthorization',
      'response.headers.setCookie*',
      'response.headers.x*'
    ]
  }
}

環境変数による設定

bash
# .env ファイル
NEW_RELIC_APP_NAME="My Node.js Application"
NEW_RELIC_LICENSE_KEY="your_license_key_here"
NEW_RELIC_LOG_LEVEL="info"
NEW_RELIC_ENABLED="true"

エージェントの初期化

アプリケーション起動時の初期化

アプリケーションの最初に New Relic エージェントを読み込む必要があります。

javascript
// app.js または index.js の最上部
require('newrelic');

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

ES6 モジュールでの初期化

javascript
// main.js
import './newrelic.js';
import express from 'express';

const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000);

TypeScript での初期化

typescript
// main.ts
import './newrelic';
import express, { Request, Response } from 'express';
import newrelic from 'newrelic';

const app = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Hello World!');
});

app.listen(3000);

フレームワーク別設定

Express.js アプリケーション

Express フレームワークでは自動的にルート監視が有効になります。

javascript
const newrelic = require('newrelic');
const express = require('express');
const app = express();

// ミドルウェアの自動監視
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ルートハンドラーの自動監視
app.get('/users/:id', async (req, res) => {
  try {
    const user = await getUserById(req.params.id);
    res.json(user);
  } catch (error) {
    newrelic.noticeError(error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

// エラーハンドリングミドルウェア
app.use((err, req, res, next) => {
  newrelic.noticeError(err);
  res.status(500).json({ error: 'Something went wrong!' });
});

Koa.js アプリケーション

javascript
const newrelic = require('newrelic');
const Koa = require('koa');
const Router = require('@koa/router');

const app = new Koa();
const router = new Router();

// ルートの定義
router.get('/api/data', async (ctx) => {
  try {
    const data = await fetchData();
    ctx.body = data;
  } catch (error) {
    newrelic.noticeError(error);
    ctx.status = 500;
    ctx.body = { error: 'Internal Server Error' };
  }
});

app.use(router.routes());

NestJS アプリケーション

typescript
// main.ts
import './newrelic';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

// controller例
import { Controller, Get } from '@nestjs/common';
import * as newrelic from 'newrelic';

@Controller('users')
export class UsersController {
  @Get()
  async findAll() {
    return newrelic.startBackgroundTransaction('Users/findAll', async () => {
      // ビジネスロジック
      return await this.userService.findAll();
    });
  }
}

非同期処理の監視

Promise チェーンの監視

javascript
const newrelic = require('newrelic');

async function processOrder(orderId) {
  return newrelic.startBackgroundTransaction('Order/process', async () => {
    try {
      const order = await fetchOrder(orderId);
      const payment = await processPayment(order);
      const fulfillment = await scheduleFulfillment(order);
      
      newrelic.recordCustomEvent('OrderProcessed', {
        orderId: orderId,
        amount: order.amount,
        processingTime: Date.now() - order.createdAt
      });
      
      return { order, payment, fulfillment };
    } catch (error) {
      newrelic.noticeError(error);
      throw error;
    }
  });
}

async/await パターンの監視

javascript
const newrelic = require('newrelic');

class DataProcessor {
  async processData(data) {
    return newrelic.startSegment('DataProcessor/processData', true, async () => {
      const validatedData = await this.validateData(data);
      const enrichedData = await this.enrichData(validatedData);
      const savedData = await this.saveData(enrichedData);
      
      return savedData;
    });
  }
  
  async validateData(data) {
    return newrelic.startSegment('DataProcessor/validateData', true, async () => {
      // データ検証ロジック
      await new Promise(resolve => setTimeout(resolve, 100));
      return data;
    });
  }
}

カスタム計測の実装

カスタムメトリクスの記録

javascript
const newrelic = require('newrelic');

class MetricsService {
  recordBusinessMetrics(orderData) {
    // ビジネスメトリクスの記録
    newrelic.recordMetric('Custom/Revenue', orderData.amount);
    newrelic.recordMetric('Custom/OrdersProcessed', 1);
    
    // カスタムイベントの記録
    newrelic.recordCustomEvent('BusinessEvent', {
      eventType: 'order_completed',
      revenue: orderData.amount,
      customerId: orderData.customerId,
      productCategory: orderData.category
    });
  }
  
  trackPerformance(functionName, duration) {
    newrelic.recordMetric(`Custom/Performance/${functionName}`, duration);
  }
}

関数レベルの詳細監視

javascript
const newrelic = require('newrelic');

// デコレータ関数の作成
function traced(name) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(...args) {
      return newrelic.startSegment(name || `${target.constructor.name}/${propertyKey}`, true, () => {
        return originalMethod.apply(this, args);
      });
    };
    
    return descriptor;
  };
}

class PaymentService {
  @traced('PaymentService/processPayment')
  async processPayment(paymentData) {
    // 支払い処理ロジック
    const result = await this.callPaymentGateway(paymentData);
    return result;
  }
}

データベース監視の設定

MongoDB 監視

javascript
const newrelic = require('newrelic');
const mongoose = require('mongoose');

// MongoDB接続の監視
mongoose.connect('mongodb://localhost/myapp');

const UserSchema = new mongoose.Schema({
  name: String,
  email: String
});

UserSchema.pre('save', function() {
  newrelic.addCustomAttribute('mongodb.operation', 'save');
  newrelic.addCustomAttribute('mongodb.collection', 'users');
});

const User = mongoose.model('User', UserSchema);

Redis 監視

javascript
const newrelic = require('newrelic');
const redis = require('redis');

const client = redis.createClient();

// Redis操作の監視
const originalGet = client.get.bind(client);
client.get = function(key, callback) {
  return newrelic.startSegment('Redis/get', true, () => {
    newrelic.addCustomAttribute('redis.key', key);
    return originalGet(key, callback);
  });
};

パフォーマンス最適化

エージェント設定の調整

javascript
// newrelic.js
exports.config = {
  app_name: ['My Node.js Application'],
  license_key: 'YOUR_LICENSE_KEY_HERE',
  
  // パフォーマンス最適化設定
  transaction_tracer: {
    enabled: true,
    transaction_threshold: 2.0,
    top_n: 20,
    record_sql: 'obfuscated'
  },
  
  error_collector: {
    enabled: true,
    ignore_status_codes: [404, 401]
  },
  
  slow_sql: {
    enabled: true,
    max_samples: 10
  },
  
  // カスタム属性の制御
  attributes: {
    enabled: true,
    include: ['request.parameters.*'],
    exclude: ['request.headers.cookie']
  }
}

メモリ使用量の監視

javascript
const newrelic = require('newrelic');

setInterval(() => {
  const memUsage = process.memoryUsage();
  newrelic.recordMetric('Custom/Memory/RSS', memUsage.rss);
  newrelic.recordMetric('Custom/Memory/HeapUsed', memUsage.heapUsed);
  newrelic.recordMetric('Custom/Memory/HeapTotal', memUsage.heapTotal);
}, 60000); // 1分間隔

マイクロサービス環境での設定

分散トレーシングの有効化

javascript
// newrelic.js
exports.config = {
  distributed_tracing: {
    enabled: true
  },
  span_events: {
    enabled: true,
    max_samples_stored: 2000
  }
}

サービス間通信の監視

javascript
const newrelic = require('newrelic');
const axios = require('axios');

class ApiClient {
  async callExternalService(endpoint, data) {
    return newrelic.startSegment('ExternalService/call', true, async () => {
      try {
        const response = await axios.post(endpoint, data, {
          headers: {
            // 分散トレーシングヘッダーの自動追加
            ...newrelic.getTraceMetadata()
          }
        });
        
        newrelic.addCustomAttribute('external.service.endpoint', endpoint);
        newrelic.addCustomAttribute('external.service.response_code', response.status);
        
        return response.data;
      } catch (error) {
        newrelic.noticeError(error);
        throw error;
      }
    });
  }
}

本番環境でのデプロイメント

PM2 との統合

javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-app',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      NEW_RELIC_ENABLED: 'true',
      NEW_RELIC_APP_NAME: 'MyApp (Production)'
    }
  }]
}

Docker コンテナでの設定

dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY newrelic.js ./
COPY . .

# New Relic エージェントが最初に読み込まれるように設定
CMD ["node", "-r", "./newrelic.js", "app.js"]

Kubernetes での設定

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodejs-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        env:
        - name: NEW_RELIC_APP_NAME
          value: "MyApp (Kubernetes)"
        - name: NEW_RELIC_LICENSE_KEY
          valueFrom:
            secretKeyRef:
              name: newrelic-secret
              key: license_key

トラブルシューティング

エージェント状態の確認

javascript
const newrelic = require('newrelic');

// エージェント状態の確認
console.log('New Relic Agent Version:', newrelic.version);
console.log('Application Name:', newrelic.config.app_name);
console.log('License Key Configured:', !!newrelic.config.license_key);

// アプリケーション登録状態の確認
newrelic.getTransaction(); // 現在のトランザクション取得

ログとデバッグ

javascript
// newrelic.js でのデバッグ設定
exports.config = {
  logging: {
    level: 'trace', // より詳細なログ出力
    filepath: './newrelic_agent.log'
  }
}

Node.js アプリケーションでの New Relic APM 導入により、非同期処理とイベント駆動アーキテクチャの特性を活かした詳細な監視を実現できます。適切な設定により、現代的なWebアプリケーションの複雑な実行パターンを包括的に可視化し、継続的な最適化を支援します。


関連記事: .NET APM設定関連記事: 分散トレーシング設定