OpenTelemetry Go SDK¶
The OpenTelemetry Go SDK collects telemetry from Go applications through APIs, SDKs, and framework instrumentation libraries. Unlike runtime auto-instrumentation solutions such as the Java Agent, Go applications usually need to initialize the SDK in code and wrap web frameworks, HTTP clients, databases, or message queues with the corresponding instrumentation libraries.
This article uses DataKit's OpenTelemetry collector to receive OTLP data and forward it to Guance:
The example in this document reports Trace and Metric data through OTLP/HTTP + Protobuf. Trace and Metric in OpenTelemetry Go are stable; the maturity and ecosystem support of the Log SDK may still change, so verify the current official status before using it in production.
Prerequisites¶
- Go 1.23 or higher;
- DataKit is installed and DataKit is connected to the target Guance workspace;
- The network reachability of Go applications to DataKit: OTLP/HTTP uses DataKit HTTP port
9529, OTLP/gRPC uses4317by default; - Confirm that the framework or component used by the application has a matching OpenTelemetry Go instrumentation library, or plan to create spans and metrics manually through the OpenTelemetry API.
1. Enable the OpenTelemetry collector¶
Enter conf.d/opentelemetry in the DataKit installation directory. If the collector configuration has not been created yet, copy the sample file:
Verify that opentelemetry.conf contains at least the following receive configuration:
[[inputs.opentelemetry]]
# Add custom attributes to this whitelist if you want to keep them as tags in Guance.
# Dots in attribute names will be converted to underscores, for example team.name -> team_name.
customer_tags = ["team", "project"]
[inputs.opentelemetry.http]
http_status_ok = 200
trace_api = "/otel/v1/traces"
metric_api = "/otel/v1/metrics"
logs_api = "/otel/v1/logs"
[inputs.opentelemetry.grpc]
addr = "127.0.0.1:4317"
max_payload = 16777216
The above configuration enables the following receiving addresses:
| Protocol | Data type | DataKit receiving address |
|---|---|---|
| OTLP/HTTP + Protobuf | Trace | http://<DataKit-IP>:9529/otel/v1/traces |
| OTLP/HTTP + Protobuf | Metric | http://<DataKit-IP>:9529/otel/v1/metrics |
| OTLP/HTTP + Protobuf | Log | http://<DataKit-IP>:9529/otel/v1/logs |
| OTLP/gRPC | Trace, Metric, Log | http://<DataKit-IP>:4317 |
If the application and DataKit are not on the same host, the DataKit HTTP listening address, firewall, or other network access controls need to be adjusted according to the actual deployment. When using OTLP/gRPC, you also need to change addr to a listening address accessible to the application, such as 0.0.0.0:4317. Do not expose the OTLP receiving port directly to the public network.
Restart DataKit for the configuration to take effect:
Check if the DataKit HTTP service is reachable:
2. Application access to OpenTelemetry¶
Install the Go SDK and instrumentation libraries¶
Install the official SDK, OTLP/HTTP Exporter and net/http instrumentation library in the Go project directory:
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go mod tidy
go.mod and go.sum record dependency versions. In production, commit both files and run compilation, unit tests, and trace regression tests after upgrading OpenTelemetry dependencies.
Initialize the SDK¶
The following example completes both:
- Read resource attributes from
OTEL_SERVICE_NAMEandOTEL_RESOURCE_ATTRIBUTES; - Create OTLP/HTTP Trace Exporter and Metric Exporter;
- Register global
TracerProvider,MeterProviderand W3C context propagator; - Use
otelhttpto wrap HTTP Handler, and create a business sub-Span and custom Counter; - Refresh and close the Provider when the process exits to avoid data loss in the buffer.
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
res, err := resource.New(
ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithOS(),
resource.WithProcess(),
)
if err != nil {
return nil, fmt.Errorf("create resource: %w", err)
}
traceExporter, err := otlptracehttp.New(ctx)
if err != nil {
return nil, fmt.Errorf("create trace exporter: %w", err)
}
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithResource(res),
sdktrace.WithSampler(
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)),
),
sdktrace.WithBatcher(traceExporter),
)
metricExporter, err := otlpmetrichttp.New(ctx)
if err != nil {
_ = tracerProvider.Shutdown(ctx)
return nil, fmt.Errorf("create metric exporter: %w", err)
}
meterProvider := sdkmetric.NewMeterProvider(
sdkmetric.WithResource(res),
sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(
metricExporter,
sdkmetric.WithInterval(30*time.Second),
),
),
)
otel.SetTracerProvider(tracerProvider)
otel.SetMeterProvider(meterProvider)
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
),
)
shutdown := func(ctx context.Context) error {
return errors.Join(
meterProvider.Shutdown(ctx),
tracerProvider.Shutdown(ctx),
)
}
return shutdown, nil
}
func main() {
ctx, stop := signal.NotifyContext(
context.Background(),
os.Interrupt,
syscall.SIGTERM,
)
defer stop()
shutdown, err := setupOTelSDK(ctx)
if err != nil {
log.Fatal(err)
}
defer func() {
shutdownCtx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
if err := shutdown(shutdownCtx); err != nil {
log.Printf("shutdown OpenTelemetry: %v", err)
}
}()
meter := otel.Meter("example/order-service")
requestCounter, err := meter.Int64Counter("app.request.count")
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.Handle("/hello", otelhttp.NewHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestCtx, span := otel.Tracer("example/order-service").Start(
r.Context(),
"prepare-response",
)
defer span.End()
span.SetAttributes(attribute.String("app.route", "/hello"))
requestCounter.Add(requestCtx, 1)
_, _ = fmt.Fprintln(w, "hello from OpenTelemetry Go SDK")
}),
"GET /hello",
))
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(
context.Background(),
5*time.Second,
)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown HTTP server: %v", err)
}
}()
log.Println("listening on http://127.0.0.1:8080")
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
In a real project, install the instrumentation library required by each component you use. For example, the standard library net/http uses otelhttp; other web frameworks, databases, or message queues should use the matching package from the OpenTelemetry Registry and wrap the corresponding Handler, Transport, Client, or Driver as instructed.
Configure the reported address and start¶
Next, use OTLP/HTTP + Protobuf to report to the local DataKit. OTEL_EXPORTER_OTLP_ENDPOINT is the base endpoint. The Trace and Metric exporters append /v1/traces and /v1/metrics respectively, which map to DataKit's /otel/v1/* routes.
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment.name=prod,service.version=1.0.0,team=backend"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
export OTEL_EXPORTER_OTLP_INSECURE="true"
export OTEL_EXPORTER_OTLP_COMPRESSION="gzip"
go run .
Make a request to generate traces and metrics:
Metrics are exported every 30 seconds by default in this example through sdkmetric.WithInterval(30*time.Second). After waiting for one export cycle, you can view traces for service=order-service in Guance and query the app.request.count metric.
Using OTLP/gRPC¶
The Go SDK's OTLP transport is determined by the Exporter package used in the code. This example uses otlptracehttp and otlpmetrichttp directly, just setting OTEL_EXPORTER_OTLP_PROTOCOL=grpc will not switch it to gRPC.
To use OTLP/gRPC, install gRPC Exporter:
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go get go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
go mod tidy
Then replace the exporter package and initialization function in the code with:
And use gRPC address without /v1/traces, /v1/metrics paths:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
export OTEL_EXPORTER_OTLP_INSECURE="true"
3. Data reporting parameters¶
Resource parameters¶
This example reads the following standard environment variables via resource.WithFromEnv():
| Environment variables | Description | Suggested values or examples |
|---|---|---|
OTEL_SERVICE_NAME |
Service name, corresponding to resource attribute service.name. |
order-service; must be set explicitly for production environments. |
OTEL_RESOURCE_ATTRIBUTES |
Resource attributes in the format of comma separated key=value. |
deployment.environment.name=prod,service.version=1.0.0,team=backend |
Set at least service.name, deployment.environment.name, and service.version so Guance can perform service attribution, environment filtering, and version analysis. Custom resource attributes must be added to the DataKit customer_tags whitelist before they can be retained as tags. . in attribute names is converted to _.
OTLP/HTTP parameters¶
otlptracehttp.New() and otlpmetrichttp.New() will read the following environment variables directly. Signal-specific parameters take precedence over general parameters:
| General environment variables | Signal-specific environment variables | Description | DataKit examples |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
The general parameter is the base URL and the exporter appends the signal path automatically; the signal-specific parameter is the full URL and is used as-is. | General: http://127.0.0.1:9529/otel; Trace: http://127.0.0.1:9529/otel/v1/traces; Metric: http://127.0.0.1:9529/otel/v1/metrics |
OTEL_EXPORTER_OTLP_INSECURE |
OTEL_EXPORTER_OTLP_TRACES_INSECURE, OTEL_EXPORTER_OTLP_METRICS_INSECURE |
Whether to turn off transport layer TLS. | Set to true when DataKit uses clear text HTTP. |
OTEL_EXPORTER_OTLP_HEADERS |
OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_METRICS_HEADERS |
Request headers in the format of comma separated key=value. |
Set the corresponding value when configuring expected_headers in DataKit. |
OTEL_EXPORTER_OTLP_TIMEOUT |
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT |
Single export timeout, value is milliseconds. | Set according to network conditions, such as 10000. |
OTEL_EXPORTER_OTLP_COMPRESSION |
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION |
OTLP request compression method. | Can be set to gzip; leave blank to not compress. |
OTEL_EXPORTER_OTLP_CERTIFICATE |
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE |
The path to the PEM CA file used to verify the server certificate. | Deploy settings by certificate when receiving OTLP over HTTPS. |
If your app and DataKit are not on the same host, you should replace 127.0.0.1 in the example with a DataKit address that your app can access.
SDK code parameters¶
The following parameters are controlled by the Go SDK initialization code and will not automatically take effect by setting a common environment variable with the same name:
| Configuration items | Sample code | Description |
|---|---|---|
| Trace Sampling | sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0)) |
1.0 means root Trace full sampling; the production environment can be adjusted to a ratio such as 0.1 according to capacity, and follow the upstream sampling decision through ParentBased. |
| Span batch export | sdktrace.WithBatcher(traceExporter) |
Batch export is recommended for production environments; it can be further adjusted through WithMaxQueueSize, WithMaxExportBatchSize, WithBatchTimeout and WithExportTimeout. |
| Metric export period | sdkmetric.WithInterval(30*time.Second) |
Control the periodic export interval; too short will increase application, network and storage overhead. |
| Context propagation | TraceContext{}, Baggage{} |
Use W3C traceparent, tracestate and baggage. Services on the call chain should maintain propagation format compatibility. |
| Resource detection | resource.WithHost(), WithOS(), WithProcess() |
Automatically supplement host, operating system and process properties. Avoid storing sensitive information such as keys in process parameters and resource attributes. |
This example creates the OTLP exporter directly, so pay special attention to the following:
OTEL_EXPORTER_OTLP_PROTOCOLdoes not change the HTTP or gRPC exporter already selected in code;OTEL_TRACES_EXPORTER,OTEL_METRICS_EXPORTERwill not close the Exporter created directly in this example;- The OpenTelemetry Go core SDK currently does not automatically apply all common SDK environment variables, especially do not assume that
OTEL_SDK_DISABLED,OTEL_TRACES_SAMPLERorOTEL_PROPAGATORSwill automatically take effect in custom initialization code; - If you need to dynamically select and initialize the Exporter through standard environment variables, you can evaluate the official Contrib's
autoexportpackage and verify its behavior in the test environment.
Verification and troubleshooting¶
- Execute
curl http://127.0.0.1:9529/v1/pingto confirm that the application can reach DataKit; - Start the application and access
/hello, confirm that there is nocreate trace exporter,create metric exporteror OTLP export error in the application log; - Wait for at least one metric export cycle;
- Query traces for
order-servicein the APM service list of Guance; - When the data cannot be queried, check the DataKit
opentelemetrycollector configuration, the network applied to DataKit, the reporting URL and the DataKit log; - When duplicate Span occurs, check whether the same Handler, Transport, Database Client or Driver is packaged repeatedly.