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>"), // Replace <endpoint> with the reporting address 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>"}, // Replace <token> with the business system Token attribute.KeyValue{Key: "service.name", Value: "<servceName>"}, // Replace <serviceName> with the application name attribute.KeyValue{Key: "host.name", Value: "<hostName>"}, // Replace <hostName> with the IP address ), }...) 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>: Application name. Multiple application processes connecting with the same serviceName are displayed as multiple instances under the same application in APM. The application name can be up to 63 characters and can only contain lowercase letters, digits, and the separator (-), and it must start with a lowercase letter and end with a digit or lowercase letter.<token>: The business system Token obtained in the preliminary steps.<hostName>: The hostname of this instance, which is the unique identifier of the application instance. It can usually be set to the IP address of the application instance.<endpoint>: The connect point obtained in the preliminary steps.package mainimport ( "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() // Initialize SDK otelShutdown, err := setupOTelSDK(ctx) if err != nil { return } // Graceful shutdown defer func() { err = errors.Join(err, otelShutdown(context.Background())) }() // Start HTTP service 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 }
func newHTTPHandler() http.Handler { mux := http.NewServeMux() handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { // HTTP routes Event Tracking handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) mux.Handle(pattern, handler) } // Register APIs handleFunc("/simple", simpleIOHandler) // Enhanced Event Tracking for all APIs handler := otelhttp.NewHandler(mux, "/") return handler }func simpleIOHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "ok") }
https://localhost:8080/simple, the application reports HTTP request-related link data to APM. In normal traffic cases, the connected application will be displayed in Application Performance Monitoring > Application list. Click Application name/ID to enter the application details page, then select Instance Analysis to view the connected application instance. Since there is a certain latency in the processing of observable data, if the application or instance does not appear in the console after connecting, wait for about 30 seconds.import ("github.com/redis/go-redis/v9""github.com/redis/go-redis/extra/redisotel/v9")var rdb *redis.Client// InitRedis initializing Redis client.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)}
import ("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/schema""gorm.io/plugin/opentelemetry/tracing")var GormDB *gorm.DBtype TableDemo struct {ID intgorm:"column:id"Value stringgorm:"column:value"}func InitGorm() {var err errordsn := "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, // Use singular table names.},})if err != nil {panic(err)}// Add tracing reporting logic.//Fill in DBName based on actual conditions. In the APM topology diagram, identify the node using the DBName field. In this example, use 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 stringif 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)}
tracer.WithSpanKind(). Span Kind includes server, client, internal, consumer, and producer. Configure it according to specific business scenarios. This document provides sample code for internal method tracking and external resource access tracking.internal kind Span inside a server Type Span.func entryFunc(w http.ResponseWriter, r *http.Request) {_, span := tracer.Start(r.Context(), "entryFunc", trace.WithSpanKind(trace.SpanKindServer)) // server spandefer span.End()internalInvoke(r)io.WriteString(w, "ok")}func internalInvoke(r *http.Request) {create Internal Span_, span := tracer.Start(r.Context(), "internalInvoke", trace.WithSpanKind(trace.SpanKindInternal)) // internal spandefer span.End()// business logic omitted}
client is usually required to be appended in the current link context.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}}
func internalInvoke(ctx context.Context) {spanCtx := trace.SpanContextFromContext(ctx) // Retrieve current span contextif spanCtx.HasTraceID() {traceID := spanCtx.TraceID()fmt.Println(traceID.String())}if spanCtx.HasSpanID() {spanID := spanCtx.SpanID()fmt.Println(spanID.String())}// business logic omitted}
func gormRequest(ctx context.Context) {span := trace.SpanFromContext(ctx) // Retrieve current span objectif span == nil {fmt.Println("no active span detected")return}span.SetAttributes(attribute.String("key1", "value1"),attribute.Int64("key2", 123),)var val stringif 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)}
Feedback