通过OpenTelemetry上报Go应用数据

通过OpenTelemetry为应用埋点并上报链路数据至云监控2.0后,云监控2.0即可开始监控应用,您可以查看应用拓扑、调用链路、异常事务、慢事务和SQL分析等一系列监控数据。本文介绍如何使用OpenTelemetry Go SDK进行手动埋点并上报数据。

背景信息

OpenTelemetry Go SDK提供了Go语言的分布式链路追踪能力,您可以直接使用OTLP gRPC或者HTTP协议向链路追踪服务端上报数据。

OpenTelemetry提供了若干半自动埋点插件(无需手动创建Span,只需要在代码中使用这些插件提供的API),支持为常见的框架自动创建Span。支持的框架列表请参见OpenTelemetry官方文档

步骤一:获取接入点信息

  1. 登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择接入中心 > 接入中心

  2. 服务端应用区域单击Go卡片,然后选择接入协议类型Opentelemetry

  3. 参数配置区域单击LicenseKey右侧的点击获取,然后根据需求选择埋点方式连接方式上报方式,并输入应用名称版本号部署环境

    页面下方将会根据配置参数生成相应的接入代码,代码中包含了Endpoint、LicenseKey等接入点信息。

    image

步骤二:设置依赖库

自动埋点(推荐)

  1. 下载最新版本的Golang Agent,并将对应架构的二进制包重命名为otel。

  2. 使用Golang Agent二进制编译。

    参考文档,通过在go build指令之前添加编译前缀进行混合编译,例如:

    otel go build -o app cmd/app
  3. 启动应用。

    下述代码将直接运行您的应用并加载 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

手动埋点

  1. 在 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
    )
  2. 创建 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)
    		}
    	}
    }
    
  3. 创建 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()
    }
  4. 运行程序以生成并上报 Trace 数据。

    go run main.go

查看监控数据

  1. 登录云监控2.0控制台,选择目标工作空间,在左侧导航栏选择应用中心 > 运维监控 > 应用监控

  2. 应用列表页面单击目标应用名称,然后查看对应的监控详情。更多信息,请参见应用监控