通过OpenTelemetry为应用埋点并上报链路数据至云监控2.0后,云监控2.0即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetry Go SDK进行手动埋点并上报数据。
背景信息
OpenTelemetry Go SDK提供了Go语言的分布式链路追踪能力,您可以直接使用OTLP gRPC或者HTTP协议向链路追踪服务端上报数据。
OpenTelemetry提供了若干半自动埋点插件(无需手动创建Span,只需要在代码中使用这些插件提供的API),支持为常见的框架自动创建Span。支持的框架列表请参见OpenTelemetry官方文档。
步骤一:获取接入点信息
登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择 。
在服务端应用区域单击Go卡片,然后选择接入协议类型为Opentelemetry。
在参数配置区域单击LicenseKey右侧的点击获取,然后根据需求选择埋点方式、连接方式、上报方式,并输入应用名称、版本号和部署环境。
页面下方将会根据配置参数生成相应的接入代码,代码中包含了Endpoint、LicenseKey等接入点信息。
步骤二:设置依赖库
自动埋点(推荐)
下载最新版本的Golang Agent,并将对应架构的二进制包重命名为otel。
使用Golang Agent二进制编译。
参考文档,通过在go build指令之前添加编译前缀进行混合编译,例如:
otel go build -o app cmd/app
启动应用。
下述代码将直接运行您的应用并加载 OpenTelemetry Golang Agent,请将步骤一获取到的接入点信息替换到以下代码中,并将
/path/to/your/app
替换为上一步使用Golang Agent编译出的二进制文件的实际路径。export OTEL_LOGS_EXPORTER=none export OTEL_RESOURCE_ATTRIBUTES=service.name=<service name>,service.version=<service version>,deployment.environment=<environment> export OTEL_EXPORTER_OTLP_HEADERS="x-arms-license-key=<license-key>,x-arms-project=<arms-project>,x-cms-workspace=<workspace>" export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=<traces.endpoint> export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=<metrics.endpoint> /path/to/your/app
手动埋点
在 go mod 中添加如下依赖。
require ( go.opentelemetry.io/otel v1.20.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.20.0 go.opentelemetry.io/otel/sdk v1.20.0 )
创建 OpenTelemetry 初始化工具代码.
新建 opentelemetry_util.go 文件,并添加以下代码。
package otel_util import ( "context" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.15.0" "google.golang.org/grpc" "google.golang.org/grpc/encoding/gzip" "log" "os" "time" ) const ( SERVICE_NAME = "<service name>" // 应用名 SERVICE_VERSION = "<service version>" // 应用版本 DEPLOY_ENVIRONMENT = "<environment>" // 部署环境 HTTP_ENDPOINT = "<endpoint>" // 请将<endpoint>替换为步骤一中获取的接入点信息 HTTP_URL_PATH = "<url path>" // 请将<url path>替换为步骤一中获取的接入点信息 ) // 设置应用资源 func newResource(ctx context.Context) *resource.Resource { hostName, _ := os.Hostname() r, err := resource.New( ctx, resource.WithFromEnv(), resource.WithProcess(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes( semconv.ServiceNameKey.String(SERVICE_NAME), semconv.ServiceVersionKey.String(SERVICE_VERSION), semconv.DeploymentEnvironmentKey.String(DEPLOY_ENVIRONMENT), semconv.HostNameKey.String(hostName), // 主机名 attribute.String("acs.cms.workspace", WORK_SPACE), // Workspace名称 ), ) if err != nil { log.Fatalf("%s: %v", "Failed to create OpenTelemetry resource", err) } return r } func newHTTPExporterAndSpanProcessor(ctx context.Context) (*otlptrace.Exporter, sdktrace.SpanProcessor) { headers := map[string]string{ "x-arms-license-key": "<license-key>", // 请将 <license-key> 替换为步骤一中获取的接入点信息 "x-arms-project": "<arms-project>", // 请将 <arms-project> 替换为步骤一中获取的接入点信息 "x-cms-workspace": "<workspace>", // 请将 <workspace> 替换为您的workspace名称 } traceExporter, err := otlptrace.New(ctx, otlptracehttp.NewClient( otlptracehttp.WithEndpoint(HTTP_ENDPOINT), otlptracehttp.WithURLPath(HTTP_URL_PATH), otlptracehttp.WithHeaders(headers), otlptracehttp.WithCompression(1))) if err != nil { log.Fatalf("%s: %v", "Failed to create the OpenTelemetry trace exporter", err) } batchSpanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter) return traceExporter, batchSpanProcessor } // InitOpenTelemetry OpenTelemetry 初始化方法 func InitOpenTelemetry() func() { ctx := context.Background() var traceExporter *otlptrace.Exporter var batchSpanProcessor sdktrace.SpanProcessor traceExporter, batchSpanProcessor = newHTTPExporterAndSpanProcessor(ctx) otelResource := newResource(ctx) traceProvider := sdktrace.NewTracerProvider( sdktrace.WithSampler(sdktrace.AlwaysSample()), sdktrace.WithResource(otelResource), sdktrace.WithSpanProcessor(batchSpanProcessor)) otel.SetTracerProvider(traceProvider) otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) return func() { cxt, cancel := context.WithTimeout(ctx, time.Second) defer cancel() if err := traceExporter.Shutdown(cxt); err != nil { otel.Handle(err) } } }
创建 Span。
新建 main.go 文件,并添加以下代码。以下代码包含三个方法(除去main方法),每个方法均创建了一个 Span。
package main import ( "context" "fmt" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" otel_util "otel_go_demo/util" "time" ) func main() { shutdown := otel_util.InitOpenTelemetry() defer shutdown() for i:= 0; i < 10; i++ { ctx := context.Background() parentMethod(ctx) } time.Sleep(10 * time.Second) } func parentMethod(ctx context.Context) { tracer := otel.Tracer("otel-go-tracer") ctx, span := tracer.Start(ctx, "parent span") fmt.Println(span.SpanContext().TraceID()) // 打印 TraceId span.SetAttributes(attribute.String("key", "value")) span.SetStatus(codes.Ok, "Success") childMethod(ctx) span.End() } func childMethod(ctx context.Context) { tracer := otel.Tracer("otel-go-tracer") ctx, span := tracer.Start(ctx, "child span") span.SetStatus(codes.Ok, "Success") grandChildMethod(ctx) span.End() } func grandChildMethod(ctx context.Context) { tracer := otel.Tracer("otel-go-tracer") ctx, span := tracer.Start(ctx, "grandchild span") span.SetStatus(codes.Error, "error") // 业务代码... span.End() }
运行程序以生成并上报 Trace 数据。
go run main.go