tencent cloud

Connecting Go Applications Using OpenTelemetry-Go (Recommended)
Last updated: 2025-10-13 19:10:48
Connecting Go Applications Using OpenTelemetry-Go (Recommended)
Last updated: 2025-10-13 19:10:48
Note:
OpenTelemetry is a collection of tools, APIs, and SDKs for monitoring, generating, collecting, and exporting telemetry data (metrics, logs, and traces) to help users analyze the performance and behaviors of the software. For more information about OpenTelemetry, see the OpenTelemetry Official Website.
The OpenTelemetry community is active, with rapid technological changes, and widely compatible with mainstream programming languages, components, and frameworks, making its link-tracing capability highly popular for cloud-native microservices and container architectures.
This document will introduce how to connect Go applications with the community's OpenTelemetry-Go scheme. OpenTelemetry-Go provides a series of APIs so that users can send performance data to the observability platform's server. This document introduces how to connect Tencent Cloud APM based on OpenTelemetry Go through the most common application behaviors, such as HTTP services and database access. For more uses of OpenTelemetry-Go, see the Project Homepage.

Prerequisites

This scheme supports the officially supported versions of Go, currently supports version 1.21 to 1.24. For lower versions, the connection is theoretically possible, but the community does not maintain full compatibility. For specific information, see the community's Compatibility Description.

Preliminary steps: Get the connect point and Token.

1. Log in to the TCOP console.
2. In the left menu column, select Application Performance Management > Application list, then click Access application.
3. In the Access application drawer that pops up on the right, click the Go language.
4. On the Access Go application page, select the Region and Business System you want to connect.
5. Select Access protocol type as OpenTelemetry.
6. Choose your desired Reporting method, and obtain your Access Point and Token.
Note:
Private network reporting: Using this reporting method, your service needs to run in the Tencent Cloud VPC. Through VPC connecting directly, you can avoid the security risks of public network communication and save on reporting traffic overhead.
Public network reporting: If your service is deployed locally or in non-Tencent Cloud VPC, you can report data in this method. However, it involves security risks in public network communication and incurs reporting traffic fees.

Connecting Go Applications

Step 1: Introduce OpenTelemetry-related dependencies to implement the SDK initialization logic.

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 }
The corresponding field descriptions are as follows, replace them according to actual conditions.
<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.

Step 2: SDK initialization and start the HTTP service.

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() // 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 }
If implementing an HTTP service through frameworks like Gin, the Event Tracking method will differ. See the community's Framework List for details on other frameworks' Event Tracking methods.

Step 3: Enhanced Event Tracking for HTTP APIs.

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") }

Connection Verification

After you start the Go application, access the corresponding API through port 8080, for example, 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.

More Event Tracking Sample

Accessing Redis

Initialization
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
}
Data Access
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)
}

Accessing MySQL

Initialization
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, // 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)
}
}
Data Access
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)
}

Custom Event Tracking

Users can append a custom Span in the current link context to enhance link data richness. When performing custom event tracking, set Span Kind via 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 Method

Here demonstrates adding a 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 span
defer 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 span
defer span.End()
// business logic omitted
}

Access External Resources

To access external resources, a Span of type 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
}
}

Retrieve Current Span Context

In code, you can obtain the current Span context information to add or modify span attributes, or output the obtained TraceID/SpanID in logs.

Retrieving TraceID/SpanID

func internalInvoke(ctx context.Context) {
spanCtx := trace.SpanContextFromContext(ctx) // Retrieve current span context
if spanCtx.HasTraceID() {
traceID := spanCtx.TraceID()
fmt.Println(traceID.String())
}
if spanCtx.HasSpanID() {
spanID := spanCtx.SpanID()
fmt.Println(spanID.String())
}
// business logic omitted
}

Setting Span Attribute

First get the Span object, then set attributes.
func gormRequest(ctx context.Context) {
span := trace.SpanFromContext(ctx) // Retrieve current span object
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)
}


Was this page helpful?
You can also Contact Sales or Submit a Ticket for help.
Yes
No

Feedback