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

Go アプリケーションでの New Relic APM 導入は、高性能でスケーラブルなアプリケーションの監視において重要な価値を提供します。Go エージェントは、軽量な実装、ゴルーチンの追跡、標準的なフレームワークとの統合を通じて、Go 言語の特性を活かした効率的な監視を実現します。

Go エージェントの特徴

New Relic Go エージェントは、Go の並行処理モデルに最適化された設計により、ゴルーチン間のコンテキスト追跡とマルチプレクサ型のHTTPサーバー監視を提供します。手動計測ベースのアプローチにより、開発者が必要な部分のみを選択的に監視できる柔軟性を備えています。

Gin、Echo、Gorilla Mux、gRPC などの主要フレームワークとの統合により、Web アプリケーション、API サーバー、マイクロサービスの包括的な監視が可能です。また、データベースドライバーやHTTPクライアントの自動計測により、外部依存関係のパフォーマンスも詳細に追跡できます。

導入前の準備

環境要件の確認

Go エージェントは Go 1.19 以降をサポートしており、クロスプラットフォーム対応により Linux、macOS、Windows で動作します。モジュールシステム(Go Modules)を使用したプロジェクト管理が推奨されます。

プロジェクト構成の確認

既存の Go プロジェクトにエージェントを追加する場合、依存関係の競合がないことを確認します。

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

Go Modules を使用したインストール

bash
go get github.com/newrelic/go-agent/v3/newrelic

go.mod ファイルでの依存関係追加

go
module myapp

go 1.21

require (
    github.com/newrelic/go-agent/v3 v3.25.0
    github.com/newrelic/go-agent/v3/integrations/nrgin v1.2.1
    github.com/newrelic/go-agent/v3/integrations/nrgorilla v1.2.1
    github.com/newrelic/go-agent/v3/integrations/nrlogrus v1.0.1
)

基本設定とアプリケーション初期化

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

go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "time"

    "github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
    // New Relic アプリケーションの初期化
    app, err := newrelic.NewApplication(
        newrelic.ConfigAppName("My Go Application"),
        newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
        newrelic.ConfigDistributedTracerEnabled(true),
        newrelic.ConfigDebugLogger(os.Stdout),
    )
    if err != nil {
        log.Fatal("Failed to initialize New Relic application:", err)
    }

    // アプリケーションの接続を待機
    app.WaitForConnection(10 * time.Second)

    // HTTPサーバーの設定
    http.HandleFunc(newrelic.WrapHandleFunc(app, "/hello", helloHandler))
    http.HandleFunc(newrelic.WrapHandleFunc(app, "/api/users", usersHandler))
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, World!"))
}

環境変数による設定

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

フレームワーク別統合

Gin フレームワークとの統合

go
package main

import (
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
    "github.com/newrelic/go-agent/v3/integrations/nrgin"
    "github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
    // New Relic アプリケーションの初期化
    app, err := newrelic.NewApplication(
        newrelic.ConfigAppName("My Gin Application"),
        newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
        newrelic.ConfigDistributedTracerEnabled(true),
    )
    if err != nil {
        panic(err)
    }

    // Gin ルーターの作成
    router := gin.Default()
    
    // New Relic ミドルウェアの追加
    router.Use(nrgin.Middleware(app))

    // ルートの定義
    router.GET("/api/users", getUsersHandler)
    router.POST("/api/users", createUserHandler)
    router.GET("/api/users/:id", getUserHandler)

    router.Run(":8080")
}

func getUsersHandler(c *gin.Context) {
    // トランザクションの取得
    txn := newrelic.FromContext(c.Request.Context())
    defer txn.StartSegment("Database/GetUsers").End()

    // ビジネスロジック
    users := fetchUsersFromDatabase()
    
    // カスタムメトリクスの記録
    txn.RecordCustomMetric("Custom/Users/Count", float64(len(users)))
    
    c.JSON(http.StatusOK, users)
}

func createUserHandler(c *gin.Context) {
    txn := newrelic.FromContext(c.Request.Context())
    
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        txn.NoticeError(err)
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // ユーザー作成処理
    segment := txn.StartSegment("Database/CreateUser")
    createdUser, err := createUser(user)
    segment.End()

    if err != nil {
        txn.NoticeError(err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
        return
    }

    // カスタムイベントの記録
    txn.RecordCustomEvent("UserCreated", map[string]interface{}{
        "userId":   createdUser.ID,
        "userType": createdUser.Type,
    })

    c.JSON(http.StatusCreated, createdUser)
}

Echo フレームワークとの統合

go
package main

import (
    "net/http"
    "strconv"

    "github.com/labstack/echo/v4"
    "github.com/newrelic/go-agent/v3/integrations/nrecho"
    "github.com/newrelic/go-agent/v3/newrelic"
)

func main() {
    app, _ := newrelic.NewApplication(
        newrelic.ConfigAppName("My Echo Application"),
        newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
    )

    e := echo.New()
    
    // New Relic ミドルウェアの追加
    e.Use(nrecho.Middleware(app))

    e.GET("/api/orders/:id", getOrderHandler)
    e.POST("/api/orders", createOrderHandler)

    e.Logger.Fatal(e.Start(":8080"))
}

func getOrderHandler(c echo.Context) error {
    txn := newrelic.FromContext(c.Request().Context())
    
    orderID, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        txn.NoticeError(err)
        return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid order ID"})
    }

    // データベースアクセスの監視
    segment := txn.StartSegment("Database/GetOrder")
    order, err := getOrderFromDatabase(orderID)
    segment.End()

    if err != nil {
        txn.NoticeError(err)
        return c.JSON(http.StatusNotFound, map[string]string{"error": "Order not found"})
    }

    return c.JSON(http.StatusOK, order)
}

gRPC サーバーとの統合

go
package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    "github.com/newrelic/go-agent/v3/integrations/nrgrpc"
    "github.com/newrelic/go-agent/v3/newrelic"
)

type server struct {
    UnimplementedUserServiceServer
    app *newrelic.Application
}

func (s *server) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
    txn := newrelic.FromContext(ctx)
    
    // データベースアクセスの監視
    segment := txn.StartSegment("Database/GetUser")
    defer segment.End()

    user, err := getUserFromDatabase(req.UserId)
    if err != nil {
        txn.NoticeError(err)
        return nil, err
    }

    return user, nil
}

func main() {
    app, err := newrelic.NewApplication(
        newrelic.ConfigAppName("My gRPC Service"),
        newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
    )
    if err != nil {
        log.Fatal(err)
    }

    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatal(err)
    }

    s := grpc.NewServer(
        grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)),
        grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)),
    )

    RegisterUserServiceServer(s, &server{app: app})

    log.Println("gRPC server listening on :50051")
    if err := s.Serve(lis); err != nil {
        log.Fatal("Failed to serve:", err)
    }
}

データベース監視

SQL データベースの統合

go
package main

import (
    "database/sql"
    "context"

    _ "github.com/lib/pq"
    "github.com/newrelic/go-agent/v3/integrations/nrpq"
    "github.com/newrelic/go-agent/v3/newrelic"
)

type UserRepository struct {
    db  *sql.DB
    app *newrelic.Application
}

func NewUserRepository(app *newrelic.Application) (*UserRepository, error) {
    // PostgreSQL ドライバーの監視を有効化
    db, err := sql.Open("nrpostgres", "postgres://user:password@localhost/dbname")
    if err != nil {
        return nil, err
    }

    return &UserRepository{
        db:  db,
        app: app,
    }, nil
}

func (r *UserRepository) GetUser(ctx context.Context, userID int) (*User, error) {
    txn := newrelic.FromContext(ctx)
    
    // データベースクエリの実行(自動的に監視される)
    query := "SELECT id, name, email FROM users WHERE id = $1"
    row := r.db.QueryRowContext(ctx, query, userID)

    var user User
    err := row.Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        txn.NoticeError(err)
        return nil, err
    }

    return &user, nil
}

func (r *UserRepository) CreateUser(ctx context.Context, user *User) error {
    txn := newrelic.FromContext(ctx)
    
    // トランザクション内でのデータベース操作
    tx, err := r.db.BeginTx(ctx, nil)
    if err != nil {
        txn.NoticeError(err)
        return err
    }
    defer tx.Rollback()

    query := "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"
    err = tx.QueryRowContext(ctx, query, user.Name, user.Email).Scan(&user.ID)
    if err != nil {
        txn.NoticeError(err)
        return err
    }

    if err = tx.Commit(); err != nil {
        txn.NoticeError(err)
        return err
    }

    // カスタムメトリクスの記録
    txn.RecordCustomMetric("Custom/Users/Created", 1)

    return nil
}

Redis 統合

go
package main

import (
    "context"
    "time"

    "github.com/go-redis/redis/v8"
    "github.com/newrelic/go-agent/v3/integrations/nrredis"
    "github.com/newrelic/go-agent/v3/newrelic"
)

type CacheService struct {
    client *redis.Client
}

func NewCacheService() *CacheService {
    rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    // Redis クライアントにNew Relic フックを追加
    rdb.AddHook(nrredis.NewHook())

    return &CacheService{
        client: rdb,
    }
}

func (c *CacheService) GetUserProfile(ctx context.Context, userID string) (*UserProfile, error) {
    txn := newrelic.FromContext(ctx)
    
    // Redis GET操作(自動的に監視される)
    segment := txn.StartSegment("Cache/GetUserProfile")
    defer segment.End()

    val, err := c.client.Get(ctx, "user:"+userID).Result()
    if err == redis.Nil {
        return nil, nil // キャッシュミス
    } else if err != nil {
        txn.NoticeError(err)
        return nil, err
    }

    var profile UserProfile
    if err := json.Unmarshal([]byte(val), &profile); err != nil {
        txn.NoticeError(err)
        return nil, err
    }

    return &profile, nil
}

func (c *CacheService) SetUserProfile(ctx context.Context, userID string, profile *UserProfile) error {
    txn := newrelic.FromContext(ctx)
    
    data, err := json.Marshal(profile)
    if err != nil {
        txn.NoticeError(err)
        return err
    }

    // Redis SET操作
    segment := txn.StartSegment("Cache/SetUserProfile")
    defer segment.End()

    return c.client.Set(ctx, "user:"+userID, data, time.Hour).Err()
}

カスタム計測の実装

詳細なビジネスメトリクス

go
package main

import (
    "context"
    "time"

    "github.com/newrelic/go-agent/v3/newrelic"
)

type OrderService struct {
    app *newrelic.Application
}

func (s *OrderService) ProcessOrder(ctx context.Context, order *Order) error {
    txn := newrelic.FromContext(ctx)
    
    startTime := time.Now()
    defer func() {
        duration := time.Since(startTime)
        txn.RecordCustomMetric("Custom/OrderProcessing/Duration", duration.Seconds())
    }()

    // 注文検証
    if err := s.validateOrder(ctx, order); err != nil {
        txn.NoticeError(err)
        txn.RecordCustomMetric("Custom/Order/ValidationFailed", 1)
        return err
    }

    // 在庫確認
    segment := txn.StartSegment("Business/InventoryCheck")
    available, err := s.checkInventory(ctx, order.Items)
    segment.End()
    
    if err != nil {
        txn.NoticeError(err)
        return err
    }
    
    if !available {
        txn.RecordCustomMetric("Custom/Order/InsufficientInventory", 1)
        return ErrInsufficientInventory
    }

    // 支払い処理
    paymentSegment := txn.StartSegment("External/PaymentGateway/Process")
    paymentResult, err := s.processPayment(ctx, order.Payment)
    paymentSegment.End()
    
    if err != nil {
        txn.NoticeError(err)
        txn.RecordCustomMetric("Custom/Payment/Failed", 1)
        return err
    }

    // 注文完了
    txn.RecordCustomMetric("Custom/Order/Completed", 1)
    txn.RecordCustomMetric("Custom/Revenue", order.TotalAmount)
    
    // カスタムイベントの記録
    txn.RecordCustomEvent("OrderCompleted", map[string]interface{}{
        "orderId":     order.ID,
        "customerId":  order.CustomerID,
        "amount":      order.TotalAmount,
        "itemCount":   len(order.Items),
        "paymentType": order.Payment.Type,
    })

    return nil
}

func (s *OrderService) validateOrder(ctx context.Context, order *Order) error {
    txn := newrelic.FromContext(ctx)
    segment := txn.StartSegment("Business/OrderValidation")
    defer segment.End()

    // バリデーションロジック
    if order.TotalAmount <= 0 {
        return ErrInvalidAmount
    }

    if len(order.Items) == 0 {
        return ErrEmptyOrder
    }

    return nil
}

ゴルーチンの監視

go
package main

import (
    "context"
    "sync"

    "github.com/newrelic/go-agent/v3/newrelic"
)

func ProcessBatchJobs(ctx context.Context, jobs []Job) error {
    txn := newrelic.FromContext(ctx)
    
    var wg sync.WaitGroup
    resultChan := make(chan error, len(jobs))

    for i, job := range jobs {
        wg.Add(1)
        
        go func(jobIndex int, job Job) {
            defer wg.Done()
            
            // 新しいゴルーチンでのトランザクション作成
            asyncTxn := txn.NewGoroutine()
            defer asyncTxn.End()
            
            if err := processJob(job, asyncTxn); err != nil {
                asyncTxn.NoticeError(err)
                resultChan <- err
                return
            }
            
            asyncTxn.RecordCustomMetric("Custom/Job/Completed", 1)
            resultChan <- nil
        }(i, job)
    }

    // すべてのゴルーチンの完了を待機
    go func() {
        wg.Wait()
        close(resultChan)
    }()

    // 結果の集計
    var errors []error
    for err := range resultChan {
        if err != nil {
            errors = append(errors, err)
        }
    }

    if len(errors) > 0 {
        txn.RecordCustomMetric("Custom/BatchJob/FailureCount", float64(len(errors)))
        return errors[0] // 最初のエラーを返す
    }

    txn.RecordCustomMetric("Custom/BatchJob/SuccessCount", float64(len(jobs)))
    return nil
}

func processJob(job Job, txn *newrelic.Transaction) error {
    segment := txn.StartSegment("Job/Process")
    defer segment.End()

    // ジョブ処理ロジック
    time.Sleep(100 * time.Millisecond) // 模擬処理時間

    return nil
}

パフォーマンス最適化

コンフィグレーションの調整

go
package main

import (
    "github.com/newrelic/go-agent/v3/newrelic"
)

func createOptimizedApp() (*newrelic.Application, error) {
    return newrelic.NewApplication(
        newrelic.ConfigAppName("High Performance Go App"),
        newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY")),
        
        // 分散トレーシングの有効化
        newrelic.ConfigDistributedTracerEnabled(true),
        
        // スパン イベントの設定
        newrelic.ConfigSpanEventsEnabled(true),
        newrelic.ConfigSpanEventsMaxSamplesStored(2000),
        
        // トランザクション トレーサーの調整
        newrelic.ConfigTransactionTracerEnabled(true),
        newrelic.ConfigTransactionTracerThreshold(2.0), // 2秒以上のトランザクションをトレース
        
        // エラー コレクターの設定
        newrelic.ConfigErrorCollectorEnabled(true),
        
        // カスタム属性の制限
        newrelic.ConfigCustomAttributesMaxCount(64),
        
        // ログ レベルの設定(本番環境では info または warn を推奨)
        newrelic.ConfigDebugLogger(nil), // 本番環境ではデバッグログを無効化
    )
}

メモリ効率の最適化

go
package main

import (
    "runtime"
    "time"

    "github.com/newrelic/go-agent/v3/newrelic"
)

func startRuntimeMetricsCollector(app *newrelic.Application) {
    ticker := time.NewTicker(1 * time.Minute)
    
    go func() {
        for range ticker.C {
            var m runtime.MemStats
            runtime.ReadMemStats(&m)

            app.RecordCustomMetric("Custom/Runtime/Goroutines", float64(runtime.NumGoroutine()))
            app.RecordCustomMetric("Custom/Memory/Alloc", float64(m.Alloc))
            app.RecordCustomMetric("Custom/Memory/TotalAlloc", float64(m.TotalAlloc))
            app.RecordCustomMetric("Custom/Memory/Sys", float64(m.Sys))
            app.RecordCustomMetric("Custom/GC/NumGC", float64(m.NumGC))
        }
    }()
}

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

Docker での設定

dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/

COPY --from=builder /app/main .

ENV NEW_RELIC_APP_NAME="My Go App (Docker)"
ENV NEW_RELIC_ENABLED=true

CMD ["./main"]

Kubernetes での設定

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: mygoapp:latest
        env:
        - name: NEW_RELIC_LICENSE_KEY
          valueFrom:
            secretKeyRef:
              name: newrelic-secret
              key: license_key
        - name: NEW_RELIC_APP_NAME
          value: "MyGoApp (Kubernetes)"
        - name: NEW_RELIC_ENABLED
          value: "true"
        resources:
          requests:
            memory: "64Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "100m"

Go アプリケーションでの New Relic APM 導入により、高性能でスケーラブルなアプリケーションの詳細な監視を実現できます。Go の並行処理モデルとエージェントの軽量な実装により、最小限のオーバーヘッドで包括的なパフォーマンス分析を提供します。


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