tencent cloud

腾讯云可观测平台

动态与公告
产品动态
产品简介
产品概述
产品优势
基本功能
基本概念
应用场景
使用限制
购买指南
云产品监控
应用性能监控
终端性能监控
前端性能监控
云拨测
Prometheus 监控服务
Grafana 服务
事件总线
云压测
快速入门
监控概览
实例分组
云产品监控
应用性能监控
云拨测
云压测
Prometheus 监控服务
Grafana 服务
创建 Dashboard
事件总线
告警服务
云产品监控
云产品监控指标
控制台操作指南
云服务器监控组件
云产品监控对接 Grafana
故障处理
实践教程
应用性能监控
应用性能监控简介
接入指南
控制台操作指南
实践教程
参考信息
常见问题
终端性能监控
终端性能监控概述
控制台操作指南
接入指南
实践教程
前端性能监控
前端性能监控简介
控制台操作指南
接入指南
常见问题
云拨测
产品简介
控制台操作指南
常见问题
云压测
云压测概述
控制台操作指南
实践教程
JavaScript API 列表
常见问题
Prometheus 监控
Prometheus 监控简介
接入指南
控制台操作指南
实践教程
Terraform
常见问题
Grafana 服务
产品简介
控制台操作指南
Grafana 平台常用功能指引
常见问题
Dashboard
什么是 Dashboard
控制台操作指南
告警管理
控制台操作指南
故障处理
常见问题
事件总线
事件总线简介
控制台操作指南
实践教程
常见问题
报表管理
常见问题
腾讯云可观测平台常见问题
告警服务相关
一般性问题
监控图表相关
云服务器监控组件相关
动态阈值告警相关
云监控对接 Grafana 相关
文档阅读指南
相关协议
应用性能监控服务等级协议
APM 隐私协议
APM 数据处理和安全协议
前端性能监控服务等级协议
终端性能监控服务等级协议
云拨测服务等级协议
Prometheus 监控服务服务等级协议
Grafana 服务服务等级协议
云压测服务等级协议
云压测使用限制
Cloud Monitor Service Level Agreement
词汇表
文档腾讯云可观测平台应用性能监控接入指南接入 Go 应用通过 OpenTelemetry-Go 接入 Go 应用(推荐)

通过 OpenTelemetry-Go 接入 Go 应用(推荐)

PDF
聚焦模式
字号
最后更新时间: 2025-10-13 19:10:48
说明:
OpenTelemetry 是工具、API 和 SDK 的集合,用于检测、生成、收集和导出遥测数据(指标、日志和跟踪),帮助用户分析软件的性能和行为。关于 OpenTelemetry 的更多信息请参考 OpenTelemetry 官方网站
OpenTelemetry 社区活跃,技术更迭迅速,广泛兼容主流编程语言、组件与框架,为云原生微服务以及容器架构的链路追踪能力广受欢迎。
本文将通过相关操作介绍如何通过社区的 OpenTelemetry-Go 方案接入 Go 应用。OpenTelemetry-Go 提供了一系列 API,用户可以通过 SDK 将性能数据并发送到可观测平台的服务端。本文通过最常见的应用行为,例如 HTTP 服务、访问数据库等,介绍如何基于 OpenTelemetry-Go 接入腾讯云应用性能监控 APM,对于 OpenTelemetry-Go 的更多用法,请参考 项目主页

前提条件

此方案支持 Go 的官方支持版本,目前支持1.21到1.24的版本,对于更低的版本,理论上可以接入,但社区不保持完整的兼容性,具体信息请参考社区 兼容说明

前置步骤:获取接入点和 Token

1. 登录 腾讯云可观测平台 控制台。
2. 在左侧菜单栏中选择应用性能监控 > 应用列表,单击接入应用
3. 在右侧弹出的接入应用抽屉框中,单击 Go 语言。
4. 接入 Go 应用页面,选择您所要接入的地域以及业务系统
5. 选择接入协议类型OpenTelemetry
6. 选择您所想要的上报方式,获取您的接入点Token
说明:
内网上报:使用此上报方式,您的服务需运行在腾讯云 VPC。通过 VPC 直接联通,在避免外网通信的安全风险同时,可以节省上报流量开销。
外网上报:当您的服务部署在本地或非腾讯云 VPC 内,可以通过此方式上报数据。请注意外网通信存在安全风险,同时也会造成一定上报流量费用。

接入 Go 应用

步骤1:引入 OpenTelemetry 相关依赖,实现 SDK 初始化逻辑

package main import ( "context" "errors" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace" "log" )
func setupOTelSDK(ctx context.Context) (*trace.TracerProvider, error) { opts := []otlptracegrpc.Option{ otlptracegrpc.WithEndpoint("<endpoint>"), // <endpoint>替换为上报地址 otlptracegrpc.WithInsecure(), } exporter, err := otlptracegrpc.New(ctx, opts...) if err != nil { log.Fatal(err) } r, err := resource.New(ctx, []resource.Option{ resource.WithAttributes( attribute.KeyValue{Key: "token", Value: "<token>"}, // <token>替换为业务系统Token attribute.KeyValue{Key: "service.name", Value: "<servceName>"}, // <serviceName>替换为应用名 attribute.KeyValue{Key: "host.name", Value: "<hostName>"}, // <hostName>替换为IP地址 ), }...) if err != nil { log.Fatal(err) } tp := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithBatcher(exporter), sdktrace.WithResource(r), ) otel.SetTracerProvider(tp) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return tp, nil }
对应的字段说明如下,请根据实际情况进行替换。
<serviceName> :应用名,多个使用相同 serviceName 接入的应用进程,在 APM 中会表现为相同应用下的多个实例。应用名最长63个字符,只能包含小写字母、数字及分隔符“ - ”,且必须以小写字母开头,数字或小写字母结尾。
<token> :前置步骤中拿到业务系统 Token。
<hostName>:该实例的主机名,是应用实例的唯一标识,通常情况下可以设置为应用实例的 IP 地址。
<endpoint> :前置步骤中拿到的接入点。

步骤2:SDK 初始化,启动 HTTP 服务

package main

import ( "context" "errors" "fmt" "log" "net" "net/http" "os" "os/signal" "time" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" )
func main() { if err := run(); err != nil { log.Fatalln(err) } } func run() (err error) { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() // 初始化 SDK otelShutdown, err := setupOTelSDK(ctx) if err != nil { return } // 优雅关闭 defer func() { err = errors.Join(err, otelShutdown(context.Background())) }() // 启动HTTP服务 srv := &http.Server{ Addr: ":8080", BaseContext: func(_ net.Listener) context.Context { return ctx }, ReadTimeout: time.Second, WriteTimeout: 10 * time.Second, Handler: newHTTPHandler(), } srvErr := make(chan error, 1) go func() { srvErr <- srv.ListenAndServe() }()
select { case err = <-srvErr: return case <-ctx.Done(): stop() } err = srv.Shutdown(context.Background()) return }
如果通过 Gin 等框架实现 HTTP 服务,埋点方式会存在区别,具体请参考社区的 框架列表 查看其他框架的埋点方式。

步骤3: 对 HTTP 接口进行埋点增强

func newHTTPHandler() http.Handler { mux := http.NewServeMux() handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { // 对HTTP路由进行埋点 handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) mux.Handle(pattern, handler) } // 注册接口 handleFunc("/simple", simpleIOHandler) // 对所有接口进行埋点增强 handler := otelhttp.NewHandler(mux, "/") return handler }

func simpleIOHandler(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "ok") }

接入验证

启动 Go 应用后,通过8080端口访问对应的接口,例如 https://localhost:8080/simple,应用就会向 APM 上报处理 HTTP 请求相关的链路数据。在有正常流量的情况下,应用性能监控 > 应用列表 中将展示接入的应用。单击应用名称/ID 进入应用详情页,再选择实例分析,即可看到接入的应用实例。由于可观测数据的处理存在一定延时,如果接入后在控制台没有查询到应用或实例,请等待30秒左右。

更多埋点示例

访问 Redis

初始化。
import (
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/extra/redisotel/v9"
)

var rdb *redis.Client

// InitRedis 初始化Redis客户端
func InitRedis() *redis.Client {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // no password
})
if err := redisotel.InstrumentTracing(rdb); err != nil {
panic(err)
}
if err := redisotel.InstrumentMetrics(rdb); err != nil {
panic(err)
}
return rdb
}
数据访问。
func redisRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
rdb := InitRedis()
val, err := rdb.Get(ctx, "foo").Result()
if err != nil {
log.Printf("redis err......")
panic(err)
}
fmt.Println("redis res: ", val)
}

访问 MySQL

初始化。
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"gorm.io/plugin/opentelemetry/tracing"
)

var GormDB *gorm.DB

type TableDemo struct {
ID int `gorm:"column:id"`
Value string `gorm:"column:value"`
}

func InitGorm() {
var err error

dsn := "root:4T$er3deffYuD#9Q@tcp(127.0.0.1:3306)/db_demo?charset=utf8mb4&parseTime=True&loc=Local"
GormDB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 使用单数表名
},
})
if err != nil {
panic(err)
}
//加入tracing上报逻辑
//需要根据实际情况填入DBName,在APM的拓扑图中,通过DBName字段确认节点,在本示例中使用"mockdb-mysql"
if err = GormDB.Use(tracing.NewPlugin(tracing.WithoutMetrics(),tracing.WithDBName("mockdb-mysql"))); err != nil {
panic(err)
}
}
数据访问。
func gormRequest(ctx context.Context) {
var val string
if err := gormclient.GormDB.WithContext(ctx).Model(&gormclient.TableDemo{}).Where("id = ?", 1).Pluck("value", &val).Error; err != nil {
panic(err)
}
fmt.Println("MySQL query result: ", val)
}

自定义埋点

用户可以在当前链路上下文追加一个自定义 Span,以提升链路数据的丰富度。进行自定义埋点的时候,需要通过 tracer.WithSpanKind()设置 Span Kind。Span Kind 包括serverclientinternalconsumerproducer五种类型,请根据具体的业务场景进行设置。本文将提供内部方法埋点,以及访问外部资源埋点的代码编写示例。

内部方法

这里演示在一个 server 类型 Span 里面,追加一个类型为internal类型的 Span。
func entryFunc(w http.ResponseWriter, r *http.Request) {
_, span := tracer.Start(r.Context(), "entryFunc", trace.WithSpanKind(trace.SpanKindServer)) // server span
defer span.End()
internalInvoke(r)
io.WriteString(w, "ok")
}

func internalInvoke(r *http.Request) {
// 创建一个 Internal Span
_, span := tracer.Start(r.Context(), "internalInvoke", trace.WithSpanKind(trace.SpanKindInternal)) // internal span
defer span.End()
// 业务逻辑省略
}

访问外部资源

访问外部资源,一般需要在当前的链路上下文中追加一个类型为client的 Span。
func clientInvoke(ctx context.Context) {
_, span := tracer.Start(ctx, "child", trace.WithSpanKind(trace.SpanKindClient))
defer span.End()
_, err := http.Get("https://www.example.com")
if err != nil {
fmt.Println("err occurred when call extrnal api: ", err)
return
}
}

获取当前 Span 上下文

在代码中,可以获取当前 Span 的上下文信息,从而添加或修改 Span 属性,或者将获取到的 TraceID/SpanID 输出到日志中。

获取 TraceID/SpanID

func internalInvoke(ctx context.Context) {
spanCtx := trace.SpanContextFromContext(ctx) // 获取当前 span 的上下文
if spanCtx.HasTraceID() {
traceID := spanCtx.TraceID()
fmt.Println(traceID.String())
}
if spanCtx.HasSpanID() {
spanID := spanCtx.SpanID()
fmt.Println(spanID.String())
}
// 业务逻辑省略
}

设置 Span 属性

先获取 Span 对象,再设置属性。
func gormRequest(ctx context.Context) {
span := trace.SpanFromContext(ctx) // 获取当前的 span 对象
if span == nil {
fmt.Println("no active span detected")
return
}
span.SetAttributes(
attribute.String("key1", "value1"),
attribute.Int64("key2", 123),
)

var val string
if err := gormclient.GormDB.WithContext(ctx).Model(&gormclient.TableDemo{}).Where("id = ?", 1).Pluck("value", &val).Error; err != nil {
panic(err)
}
fmt.Println("MySQL query result: ", val)
}


帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈