Elastic APM 接入实战

22 May 2026 – wusfe · 5 min read

Elastic APM:应用性能监控接入实战

ELK 全家桶不只能查日志,还能做 APM(Application Performance Monitoring)。Elastic APM 提供了从应用埋点到可视化分析的端到端方案,且与 Elastic Stack 原生集成。本文从架构讲起,手把手带你接入一个 Go 应用。


1. APM 架构总览

┌─────────────┐     ┌─────────────┐     ┌──────────────┐     ┌──────────┐
│  APM Agent  │ ──> │  APM Server │ ──> │ Elasticsearch │ ──> │  Kibana  │
│  (应用内置)  │     │  (接收+处理) │     │   (存储)      │     │ (APM UI) │
└─────────────┘     └─────────────┘     └──────────────┘     └──────────┘
组件 职责 部署方式
APM Agent 嵌入应用进程,自动采集性能数据 Go module、Java agent JAR、npm 包
APM Server 接收 Agent 数据、校验、转发到 ES 独立进程或 Fleet 管理
Elasticsearch 存储 trace、transaction、span 数据 同上
Kibana APM UI 服务地图、延迟分布、错误追踪 Kibana 内置 APM 应用

APM Server 从 7.16 开始集成到 Fleet 中管理;8.x 中用 Fleet + Elastic Agent 替代了独立 APM Server,但架构思路一致。


2. Go 应用接入

Step 1:安装 APM Module

go get go.elastic.co/apm/v2

Step 2:在 main.go 中初始化

package main

import (
    "net/http"

    "go.elastic.co/apm/v2"
)

func main() {
    // 默认从环境变量读取配置,也可以代码设置
    // 环境变量:ELASTIC_APM_SERVER_URL、ELASTIC_APM_SERVICE_NAME 等

    mux := http.NewServeMux()
    mux.HandleFunc("/api/orders", orderHandler)
    // apmhttp.Wrap 自动为每个请求创建 Transaction
    http.ListenAndServe(":8080", apmhttp.Wrap(mux))
}

环境变量配置:

export ELASTIC_APM_SERVER_URL=http://apm-server:8200
export ELASTIC_APM_SERVICE_NAME=order-service
export ELASTIC_APM_ENVIRONMENT=production
export ELASTIC_APM_TRANSACTION_SAMPLE_RATE=0.5

关键参数说明:

参数 含义 必填
ELASTIC_APM_SERVICE_NAME 服务名称(在 Kibana 中标识)
ELASTIC_APM_SERVER_URL APM Server 地址
ELASTIC_APM_ENVIRONMENT 环境标签(production/staging/dev) 推荐
ELASTIC_APM_TRANSACTION_SAMPLE_RATE 采样率(0~1,默认 1.0) 推荐(高 QPS 设 0.1~0.5)
ELASTIC_APM_CAPTURE_BODY 捕获请求体(off/errors/transactions/all) 可选

Step 3:代码配置方式(替代环境变量)

import (
    "go.elastic.co/apm/v2"
    "go.elastic.co/apm/v2/transport"
)

func init() {
    tracer, _ := apm.NewTracer(apm.TracerOptions{
        ServiceName:        "order-service",
        ServiceEnvironment: "production",
        ServerURL:          "http://apm-server:8200",
        TransactionSampleRate: 0.5,
        CaptureBody:        apm.CaptureBodyErrors,
    })
    tracer.SetDefaultTracer(tracer)
}

3. Trace 和 Span 的概念

APM 的核心理念来自分布式追踪(Distributed Tracing)。

概念 含义 示例
Trace 一个完整请求的端到端调用链 用户下单:Gateway -> Order -> Inventory -> DB
Transaction 一个服务内部的一次完整处理 Order 服务处理 POST /api/orders
Span Transaction 内部的一个子操作 执行 SQL 查询、调用 Redis、发送 HTTP 请求
Error 异常或错误事件 panic、HTTP 500

层级关系

Trace (整个下单流程)
  ├── Transaction: POST /api/orders (Order 服务)
  │     ├── Span: SELECT * FROM orders WHERE ... (MySQL, 45ms)
  │     ├── Span: GET inventory:8080/api/stock (HTTP, 120ms)
  │     └── Span: INCR order:counter (Redis, 3ms)
  └── Transaction: GET /api/stock (Inventory 服务)
        ├── Span: SELECT stock FROM products (MySQL, 30ms)
        └── Span: SETEX stock:lock (Redis, 5ms)

4. APM 自动捕获的能力

APM Agent 通过 apmhttpapmsqlapmgorm 等子包自动拦截以下调用,只需 import 对应 wrapper:

类别 自动支持模块
HTTP Server net/http(apmhttp.Wrap)、gin(apmgin)、echo(apmecho)、gorilla/mux(apmmux)
HTTP Client net/http(apmhttp.WrapClient)
数据库 database/sql(apmsql.Register)、GORM(apmgorm)、sqlx(apmsqlx)
Redis go-redis(apmgoredis)、redigo
消息队列 Kafka(sarama)、RabbitMQ(amqp)
缓存 go-redis Cache
gRPC google.golang.org/grpc(apmgrpc)

自动捕获的通用 Span 标签

标签 示例
db.type mysqlpostgresqlredis
db.statement SELECT * FROM users WHERE id = ?
http.status_code 200404500
http.method GETPOST
http.url http://inventory:8080/api/stock

5. 自定义 Transaction 和 Label

自动捕获无法覆盖所有业务场景。有些关键业务逻辑需要手动埋点。

自定义 Transaction

import (
    "go.elastic.co/apm/v2"
)

// 启动一个自定义 Transaction
tx := apm.DefaultTracer().StartTransaction("OrderProcessing", "business")
defer tx.End()

// 业务逻辑...
err := orderService.ProcessOrder(orderID)
if err != nil {
    tx.Result = "failed"
    apm.CaptureError(tx.Context, err).Send()
    return
}
tx.Result = "success"

自定义 Span

import (
    "go.elastic.co/apm/v2"
)

span := tx.StartSpan("VerifyCoupon", "db.mysql.query", nil)
defer span.End()

span.Context.SetLabel("coupon_code", couponCode)
span.Context.SetLabel("user_id", userID)

// 查询优惠券有效性
valid, err := couponMapper.IsValid(couponCode)
if err != nil {
    span.Result = "error"
    apm.CaptureError(span.Context, err).Send()
    return
}
if valid {
    span.Result = "valid"
} else {
    span.Result = "invalid"
}

添加业务 Label

// 在 Transaction 级别添加业务标签
tx.Context.SetLabel("order_id", orderID)
tx.Context.SetLabel("order_amount", amount)
tx.Context.SetLabel("channel", "wechat_pay")
tx.Context.SetLabel("user_level", "vip")

// User 信息也可以挂到 Transaction 上
tx.Context.SetUserID(userID)
tx.Context.SetUserEmail("user@test.com")
tx.Context.SetUsername("zhangsan")

6. APM 与日志关联(trace.id 注入日志)

这是 APM 最有价值的功能之一:把 trace ID 注入到应用日志中,这样从 Kibana APM UI 的一个 Trace 点进去,能直接跳转到关联日志。

使用 logrus + apmlogrus

import (
    "github.com/sirupsen/logrus"
    "go.elastic.co/apm/v2"
    "go.elastic.co/ecslogrus"
)

func orderHandler(w http.ResponseWriter, r *http.Request) {
    // 从 request context 获取 APM 注入的 trace ID
    ctx := r.Context()

    // logrus 配合 ECS 格式输出,自动带上 trace.id 和 transaction.id
    logrus.WithContext(ctx).
        WithField("order_id", "order_12345").
        Info("开始处理订单")

    // 业务逻辑...
    err := orderService.ProcessOrder(ctx, orderID)
    if err != nil {
        logrus.WithContext(ctx).
            WithError(err).
            Error("订单处理失败")
    }
}

日志输出效果(JSON / ECS 格式)

{
  "@timestamp": "2024-06-15T14:32:01.123Z",
  "log.level": "info",
  "message": "开始处理订单",
  "service.name": "order-service",
  "trace.id": "a3b2f6d8e1c9",
  "transaction.id": "4f7a9b1c2d3e",
  "labels": { "order_id": "order_12345" }
}

APM module 会自动把 trace.idtransaction.id 写入 contextecslogrus 在序列化时自动带上。如果用的是 zap,则配合 go.elastic.co/apm/module/apmzap/v2 使用。

在 Kibana APM UI 中打开 a3b2f6d8e1c9 这个 Trace,可以看到完整的调用链;点"关联日志"即可直接跳转到带 trace.id: a3b2f6d8e1c9 过滤条件的 Discover 页面。


7. 踩坑案例

案例 1:APM Agent 导致应用内存飙升

现象:接入 APM 后,应用 Heap 使用率从 60% 涨到 85%。

排查transaction_sample_rate 默认 1.0,高并发下每个请求都生成 Transaction 和 Span 对象,大量 Span 堆积在内存中等上报。

解决

export ELASTIC_APM_TRANSACTION_SAMPLE_RATE=0.1        # 高 QPS 场景设 10% 采样
export ELASTIC_APM_TRANSACTION_MAX_SPANS=500           # 限制单个 Transaction 最大 Span 数
export ELASTIC_APM_SPAN_MIN_DURATION=10ms              # 短于 10ms 的 Span 不上报(减少噪音)

案例 2:生产环境忘记关 debug 日志

现象:APM Server 的磁盘空间被日志打满。

排查:Agent 配置了 ELASTIC_APM_LOG_LEVEL=DEBUG,每个 Span 的详细信息打印到文件。

解决

export ELASTIC_APM_LOG_LEVEL=INFO                    # 生产必须用 INFO 以上
export ELASTIC_APM_LOG_FILE=/var/log/apm/elastic-apm.log
export ELASTIC_APM_LOG_FORMAT=json                   # JSON 格式便于分析

案例 3:APM Server 与 ES 版本不兼容

现象:APM Server 启动报错,日志显示 Index Template 创建失败。

排查:APM Server 7.17,ES 8.5,版本跨度太大导致 ILM Policy 结构不兼容。

解决

组件 版本要求
APM Agent 尽量和 APM Server 主版本一致
APM Server 和 ES 主版本保持一致(7.x 配 7.x,8.x 配 8.x)

8. Kibana APM UI 核心功能

接入数据后,Kibana 的 APM 应用提供以下视图:

页面 功能
Services 服务列表,显示延迟、吞吐量、错误率
Service Map 自动生成服务调用拓扑图
Transactions 单个服务的所有 Transaction 耗时分布
Dependencies 下游依赖(数据库、HTTP、缓存)延迟
Traces 单次请求的完整调用链瀑布图
Errors 错误分组、堆栈信息
Service Overview 综合仪表盘(P95 延迟、错误率、QPS)

总结

APM 接入是一个逐步深化的过程:

  1. 入门的价值(1 天):go get + 几行 apmhttp.Wrap,立刻看到服务地图、延迟分布、错误堆栈
  2. 深化的价值(1 周):自定义业务 Transaction、添加 Label、关联日志,把监控从"框架级"做到"业务级"
  3. 体系的价值(持续):APM + 日志 + 指标告警三位一体,形成一个完整的可观测性平台

不需要一开始就埋很多自定义点。先把自动捕获跑稳,然后在排查线上问题时,边排查边补埋点——这样做效率最高,也最贴近真实痛点。