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

PHP アプリケーションでの New Relic APM 導入は、Webアプリケーションの包括的な監視と最適化を実現します。PHP エージェントは、PHP エンジンレベルでの統合、主要フレームワークとの自動統合、レガシーシステムから最新のPHP 8.x まで幅広いバージョンサポートを提供します。

PHP エージェントの特徴

New Relic PHP エージェントは、Zend Engine の拡張機能として動作し、PHP の実行時動作を詳細に監視します。オペコードレベルでの計測により、関数呼び出し、データベースクエリ、外部API呼び出しを自動的に追跡し、パフォーマンスボトルネックを特定します。

Laravel、Symfony、CodeIgniter、CakePHP、Drupal、WordPress などの主要フレームワークとCMSとの自動統合により、既存のアプリケーションに最小限の変更で詳細な監視を導入できます。また、Composer ベースのモダンなPHPアプリケーションから従来のレガシーシステムまで幅広く対応しています。

導入前の準備

システム要件の確認

PHP エージェントは以下の環境をサポートしています:

  • PHP バージョン: 7.0 以降(PHP 8.x 推奨)
  • Web サーバー: Apache、Nginx、IIS
  • PHP SAPI: mod_php、PHP-FPM、CGI
  • オペレーティングシステム: Linux、macOS、Windows

必要な権限の確認

PHP エージェントのインストールには、PHP 拡張機能のインストール権限とWebサーバーの再起動権限が必要です。

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

Linux(RPM ベース)での自動インストール

bash
# New Relic リポジトリの追加
sudo rpm -Uvh http://yum.newrelic.com/pub/newrelic/el5/x86_64/newrelic-repo-5-3.noarch.rpm

# エージェントのインストール
sudo yum install newrelic-php5

# 設定スクリプトの実行
sudo newrelic-install install

Linux(DEB ベース)での自動インストール

bash
# New Relic リポジトリの追加
echo 'deb http://apt.newrelic.com/debian/ newrelic non-free' | sudo tee /etc/apt/sources.list.d/newrelic.list
wget -O- https://download.newrelic.com/548C16BF.gpg | sudo apt-key add -

# パッケージリストの更新とインストール
sudo apt-get update
sudo apt-get install newrelic-php5

# 設定スクリプトの実行
sudo newrelic-install install

Docker コンテナでのインストール

dockerfile
FROM php:8.2-apache

# New Relic PHP エージェントのインストール
RUN curl -L https://download.newrelic.com/php_agent/release/newrelic-php5-10.13.0.3-linux.tar.gz | tar -C /tmp -zx \
    && export NR_INSTALL_USE_CP_NOT_LN=1 \
    && export NR_INSTALL_SILENT=1 \
    && /tmp/newrelic-php5-*/newrelic-install install \
    && rm -rf /tmp/newrelic-php5-* /tmp/nrinstall*

# PHP拡張機能の有効化
RUN echo "extension=newrelic.so" > /usr/local/etc/php/conf.d/newrelic.ini

# New Relic設定の追加
RUN echo "newrelic.license = \"YOUR_LICENSE_KEY\"" >> /usr/local/etc/php/conf.d/newrelic.ini \
    && echo "newrelic.appname = \"My PHP App\"" >> /usr/local/etc/php/conf.d/newrelic.ini

COPY . /var/www/html/

Composer を使用したランタイムインストール

一部の環境では、Composer パッケージを使用してエージェントを統合できます。

bash
composer require newrelic/monolog-enricher

基本設定の構成

php.ini での設定

ini
; New Relic PHP エージェントの基本設定
extension=newrelic.so

; ライセンスキーとアプリケーション名
newrelic.license = "YOUR_LICENSE_KEY_HERE"
newrelic.appname = "My PHP Application"

; エージェントの有効化
newrelic.enabled = true

; ログ設定
newrelic.logfile = "/var/log/newrelic/php_agent.log"
newrelic.loglevel = "info"

; トランザクション追跡設定
newrelic.transaction_tracer.enabled = true
newrelic.transaction_tracer.threshold = 2.0
newrelic.transaction_tracer.detail = 1
newrelic.transaction_tracer.record_sql = "obfuscated"
newrelic.transaction_tracer.explain_enabled = true
newrelic.transaction_tracer.explain_threshold = 500

; エラー収集設定
newrelic.error_collector.enabled = true
newrelic.error_collector.record_database_errors = true

; ブラウザ監視の自動注入
newrelic.browser_monitoring.auto_instrument = true

; 分散トレーシング
newrelic.distributed_tracing.enabled = true
newrelic.span_events.enabled = true

環境変数による設定

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

フレームワーク別統合

Laravel アプリケーション

Laravel では、サービスプロバイダーとミドルウェアを使用して詳細な監視を実装できます。

config/app.php での設定

php
<?php
// config/app.php

return [
    'providers' => [
        // その他のプロバイダー
        App\Providers\NewRelicServiceProvider::class,
    ],
];

New Relic サービスプロバイダーの作成

php
<?php
// app/Providers/NewRelicServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Log;

class NewRelicServiceProvider extends ServiceProvider
{
    public function boot()
    {
        if (extension_loaded('newrelic')) {
            // アプリケーション名の設定
            newrelic_set_appname(config('app.name') . ' (' . config('app.env') . ')');
            
            // ユーザー情報の追加
            if (auth()->check()) {
                newrelic_set_user_attributes(
                    auth()->id(),
                    auth()->user()->name ?? '',
                    auth()->user()->email ?? ''
                );
            }
        }
    }
}

ミドルウェアでのカスタム計測

php
<?php
// app/Http/Middleware/NewRelicMiddleware.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class NewRelicMiddleware
{
    public function handle(Request $request, Closure $next)
    {
        if (extension_loaded('newrelic')) {
            // トランザクション名の設定
            $route = $request->route();
            if ($route) {
                $transactionName = $route->getActionMethod() ?: $route->uri();
                newrelic_name_transaction("Web/" . $transactionName);
            }
            
            // カスタム属性の追加
            newrelic_add_custom_parameter('request.method', $request->method());
            newrelic_add_custom_parameter('request.url', $request->url());
            
            if ($request->user()) {
                newrelic_add_custom_parameter('user.id', $request->user()->id);
                newrelic_add_custom_parameter('user.role', $request->user()->role ?? '');
            }
        }

        $response = $next($request);

        if (extension_loaded('newrelic')) {
            // レスポンス属性の追加
            newrelic_add_custom_parameter('response.status', $response->getStatusCode());
        }

        return $response;
    }
}

Eloquent でのデータベース監視

php
<?php
// app/Models/Order.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    public static function boot()
    {
        parent::boot();
        
        // データベースクエリの監視
        static::creating(function ($order) {
            if (extension_loaded('newrelic')) {
                newrelic_record_custom_event('OrderCreated', [
                    'order_id' => $order->id,
                    'customer_id' => $order->customer_id,
                    'total_amount' => $order->total_amount,
                ]);
            }
        });
    }
    
    public function scopeExpensive($query, $threshold = 1000)
    {
        if (extension_loaded('newrelic')) {
            newrelic_add_custom_parameter('query.type', 'expensive_orders');
            newrelic_add_custom_parameter('query.threshold', $threshold);
        }
        
        return $query->where('total_amount', '>=', $threshold);
    }
}

Symfony アプリケーション

Symfony では、イベントリスナーとサービスを使用して監視を統合します。

services.yaml での設定

yaml
# config/services.yaml
services:
    App\EventListener\NewRelicListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, priority: 255 }
            - { name: kernel.event_listener, event: kernel.response }
            - { name: kernel.event_listener, event: kernel.exception }

イベントリスナーの実装

php
<?php
// src/EventListener/NewRelicListener.php

namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

class NewRelicListener
{
    public function onKernelRequest(RequestEvent $event)
    {
        if (!extension_loaded('newrelic') || !$event->isMainRequest()) {
            return;
        }

        $request = $event->getRequest();
        
        // トランザクション名の設定
        $route = $request->attributes->get('_route');
        if ($route) {
            newrelic_name_transaction("Web/" . $route);
        }
        
        // リクエスト属性の追加
        newrelic_add_custom_parameter('request.method', $request->getMethod());
        newrelic_add_custom_parameter('request.path', $request->getPathInfo());
    }
    
    public function onKernelResponse(ResponseEvent $event)
    {
        if (!extension_loaded('newrelic') || !$event->isMainRequest()) {
            return;
        }

        $response = $event->getResponse();
        newrelic_add_custom_parameter('response.status_code', $response->getStatusCode());
    }
    
    public function onKernelException(ExceptionEvent $event)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }

        $exception = $event->getThrowable();
        newrelic_notice_error($exception->getMessage(), $exception);
    }
}

WordPress 統合

WordPress では、プラグインまたはテーマの functions.php でエージェントを統合します。

php
<?php
// wp-content/themes/your-theme/functions.php または プラグインファイル

class NewRelicWordPressIntegration
{
    public function __construct()
    {
        if (extension_loaded('newrelic')) {
            add_action('init', [$this, 'setTransactionName']);
            add_action('wp_head', [$this, 'addBrowserTiming']);
            add_action('wp_login', [$this, 'recordLogin'], 10, 2);
            add_filter('wp_die_handler', [$this, 'recordError']);
        }
    }
    
    public function setTransactionName()
    {
        global $wp_query;
        
        $transactionName = 'WordPress/';
        
        if (is_front_page()) {
            $transactionName .= 'FrontPage';
        } elseif (is_single()) {
            $transactionName .= 'Post/' . get_post_type();
        } elseif (is_page()) {
            $transactionName .= 'Page';
        } elseif (is_category()) {
            $transactionName .= 'Category';
        } elseif (is_admin()) {
            $transactionName .= 'Admin/' . (isset($_GET['page']) ? $_GET['page'] : 'Dashboard');
        } else {
            $transactionName .= 'Other';
        }
        
        newrelic_name_transaction($transactionName);
        
        // カスタム属性の追加
        if (is_user_logged_in()) {
            $user = wp_get_current_user();
            newrelic_set_user_attributes($user->ID, $user->display_name, $user->user_email);
        }
    }
    
    public function addBrowserTiming()
    {
        if (function_exists('newrelic_get_browser_timing_header')) {
            echo newrelic_get_browser_timing_header();
        }
    }
    
    public function recordLogin($user_login, $user)
    {
        newrelic_record_custom_event('UserLogin', [
            'user_id' => $user->ID,
            'user_login' => $user_login,
            'user_role' => implode(',', $user->roles),
        ]);
    }
    
    public function recordError($handler)
    {
        if (is_wp_error($GLOBALS['wp_error'] ?? null)) {
            $error = $GLOBALS['wp_error'];
            newrelic_notice_error($error->get_error_message());
        }
        
        return $handler;
    }
}

new NewRelicWordPressIntegration();

データベース監視

PDO でのカスタム監視

php
<?php

class MonitoredPDO extends PDO
{
    public function __construct($dsn, $username = null, $password = null, $options = null)
    {
        parent::__construct($dsn, $username, $password, $options);
        
        if (extension_loaded('newrelic')) {
            newrelic_add_custom_parameter('database.dsn', $this->sanitizeDsn($dsn));
        }
    }
    
    public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, ...$args)
    {
        if (extension_loaded('newrelic')) {
            $start = microtime(true);
            newrelic_record_datastore_segment(function() use ($statement, $mode, $args) {
                return parent::query($statement, $mode, ...$args);
            }, [
                'product' => 'MySQL', // またはPostgreSQL, SQLite など
                'collection' => $this->extractTableName($statement),
                'operation' => $this->extractOperation($statement),
                'host' => $this->getHost(),
                'portPathOrId' => $this->getPort(),
                'databaseName' => $this->getDatabaseName(),
            ]);
            
            $duration = microtime(true) - $start;
            newrelic_record_custom_metric('Custom/Database/QueryTime', $duration);
        }
        
        return parent::query($statement, $mode, ...$args);
    }
    
    private function extractTableName($sql)
    {
        if (preg_match('/(?:FROM|INTO|UPDATE|JOIN)\s+`?(\w+)`?/i', $sql, $matches)) {
            return $matches[1];
        }
        return 'unknown';
    }
    
    private function extractOperation($sql)
    {
        if (preg_match('/^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)/i', $sql, $matches)) {
            return strtoupper($matches[1]);
        }
        return 'UNKNOWN';
    }
}

カスタム計測の実装

ビジネスメトリクスの記録

php
<?php

class ECommerceMetrics
{
    public static function recordOrder($order)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }
        
        // カスタムメトリクスの記録
        newrelic_record_custom_metric('Custom/Orders/Total', 1);
        newrelic_record_custom_metric('Custom/Revenue/Amount', $order['total_amount']);
        newrelic_record_custom_metric('Custom/Orders/ItemCount', count($order['items']));
        
        // カスタムイベントの記録
        newrelic_record_custom_event('OrderCompleted', [
            'order_id' => $order['id'],
            'customer_id' => $order['customer_id'],
            'total_amount' => $order['total_amount'],
            'item_count' => count($order['items']),
            'payment_method' => $order['payment_method'],
            'shipping_method' => $order['shipping_method'],
        ]);
        
        // カスタム属性の追加
        newrelic_add_custom_parameter('business.order_value', $order['total_amount']);
        newrelic_add_custom_parameter('business.customer_segment', $this->getCustomerSegment($order['customer_id']));
    }
    
    public static function recordPayment($payment)
    {
        if (!extension_loaded('newrelic')) {
            return;
        }
        
        $success = $payment['status'] === 'completed';
        
        newrelic_record_custom_metric('Custom/Payments/Total', 1);
        newrelic_record_custom_metric($success ? 'Custom/Payments/Success' : 'Custom/Payments/Failed', 1);
        
        if ($success) {
            newrelic_record_custom_metric('Custom/Payments/Amount', $payment['amount']);
        } else {
            newrelic_notice_error("Payment failed: " . $payment['error_message']);
        }
    }
}

関数レベルの詳細監視

php
<?php

function monitoredFunction($name, callable $callback, array $attributes = [])
{
    if (!extension_loaded('newrelic')) {
        return $callback();
    }
    
    // カスタム属性の設定
    foreach ($attributes as $key => $value) {
        newrelic_add_custom_parameter($key, $value);
    }
    
    $start = microtime(true);
    
    try {
        $result = $callback();
        
        $duration = microtime(true) - $start;
        newrelic_record_custom_metric("Custom/Function/{$name}/Duration", $duration);
        newrelic_record_custom_metric("Custom/Function/{$name}/Success", 1);
        
        return $result;
    } catch (Exception $e) {
        newrelic_notice_error($e);
        newrelic_record_custom_metric("Custom/Function/{$name}/Error", 1);
        throw $e;
    }
}

// 使用例
$result = monitoredFunction('ProcessOrder', function() use ($orderData) {
    return $this->processOrder($orderData);
}, [
    'order.type' => $orderData['type'],
    'order.priority' => $orderData['priority']
]);

パフォーマンス最適化

PHP-FPM での設定最適化

ini
; php.ini でのパフォーマンス設定
newrelic.framework = "laravel"  ; または "symfony", "drupal", "wordpress" など
newrelic.webtransaction.name.remove_trailing_path = true
newrelic.webtransaction.name.functions = ""
newrelic.transaction_tracer.max_segments_web = 2000
newrelic.transaction_tracer.max_segments_cli = 100000

; 本番環境での最適化
newrelic.daemon.loglevel = "error"
newrelic.daemon.max_threads = 8
newrelic.daemon.metric_address_time = 120

メモリ使用量の監視

php
<?php

class PerformanceMonitor
{
    public static function recordMemoryUsage()
    {
        if (!extension_loaded('newrelic')) {
            return;
        }
        
        $memoryUsage = memory_get_usage(true);
        $peakMemory = memory_get_peak_usage(true);
        
        newrelic_record_custom_metric('Custom/Memory/Current', $memoryUsage);
        newrelic_record_custom_metric('Custom/Memory/Peak', $peakMemory);
        
        // メモリ使用量が閾値を超えた場合のアラート
        if ($memoryUsage > 128 * 1024 * 1024) { // 128MB
            newrelic_record_custom_event('HighMemoryUsage', [
                'memory_usage' => $memoryUsage,
                'memory_limit' => ini_get('memory_limit'),
                'script_name' => $_SERVER['SCRIPT_NAME'] ?? 'CLI',
            ]);
        }
    }
}

// リクエスト終了時にメモリ使用量を記録
register_shutdown_function([PerformanceMonitor::class, 'recordMemoryUsage']);

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

Apache での設定

apache
# .htaccess または Apache 設定ファイル
<IfModule mod_php5.c>
    php_value newrelic.appname "MyApp (Production)"
    php_value newrelic.enabled true
    php_value newrelic.browser_monitoring.auto_instrument true
</IfModule>

<IfModule mod_php7.c>
    php_value newrelic.appname "MyApp (Production)"
    php_value newrelic.enabled true
    php_value newrelic.browser_monitoring.auto_instrument true
</IfModule>

Nginx + PHP-FPM での設定

nginx
# nginx.conf
location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
    fastcgi_param PHP_VALUE "
        newrelic.appname=MyApp (Production)
        newrelic.enabled=1
        newrelic.browser_monitoring.auto_instrument=1
    ";
    include fastcgi_params;
}

Docker Compose での設定

yaml
version: '3.8'
services:
  web:
    build: .
    environment:
      - NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}
      - NEW_RELIC_APP_NAME=MyApp (Docker)
    volumes:
      - .:/var/www/html
    ports:
      - "80:80"
    
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: myapp

トラブルシューティング

エージェント状態の確認

php
<?php
// デバッグ用のエンドポイント
if ($_GET['debug'] === 'newrelic' && extension_loaded('newrelic')) {
    echo "<h2>New Relic Status</h2>";
    echo "<p>Extension loaded: " . (extension_loaded('newrelic') ? 'Yes' : 'No') . "</p>";
    echo "<p>Agent version: " . phpversion('newrelic') . "</p>";
    
    if (function_exists('newrelic_get_linking_metadata')) {
        echo "<p>Linking metadata: " . json_encode(newrelic_get_linking_metadata()) . "</p>";
    }
    
    echo "<h3>Configuration</h3>";
    $config = [
        'newrelic.license',
        'newrelic.appname', 
        'newrelic.enabled',
        'newrelic.daemon.address',
        'newrelic.transaction_tracer.enabled'
    ];
    
    foreach ($config as $key) {
        echo "<p>{$key}: " . ini_get($key) . "</p>";
    }
}

PHP アプリケーションでの New Relic APM 導入により、Webアプリケーションの包括的な監視と継続的な最適化を実現できます。フレームワーク固有の統合と詳細なカスタマイズにより、PHP エコシステムの多様なアプリケーションパターンに対応した効果的な監視システムを構築できます。


関連記事: Ruby APM設定関連記事: APM設定完全リファレンス