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設定関連記事: 分散トレーシング設定