This walkthrough shows how to add observability to your Go application using the OpenTelemetry Go libraries and tools.
For details on how to assemble the base OTLP endpoint URL, see Export with OTLP. The URL should end in /api/v2/otlp
.
The access token for ingesting traces, logs, and metrics can be generated under Access Tokens.
Export with OTLP has more details on the format and the necessary access scopes.
OpenTelemetry supports on Go automatic and manual instrumentation, or a combination of both.
It's a good idea to start with automatic instrumentation and add manual instrumentation if the automatic approach doesn't work or doesn't provide enough information.
Add the following import statements.
import ("context""github.com/Dynatrace/OneAgent-SDK-for-Go/sdk""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/exporters/otlp/otlplog/otlploghttp""go.opentelemetry.io/otel/propagation""go.opentelemetry.io/otel/trace"sdkmetric "go.opentelemetry.io/otel/sdk/metric""go.opentelemetry.io/otel/sdk/metric/metricdata""go.opentelemetry.io/otel/sdk/resource"sdktrace "go.opentelemetry.io/otel/sdk/trace"semconv "go.opentelemetry.io/otel/semconv/v1.20.0""log""time""log/slog""go.opentelemetry.io/contrib/bridges/otelslog""go.opentelemetry.io/otel/log/global"sdklog "go.opentelemetry.io/otel/sdk/log")
Run Go's mod tiny
command to install the dependencies.
go mod tidy
Add the following code to your startup file and provide the respective values for DT_API_HOST
and DT_API_TOKEN
.
DT_API_HOST
should contain only the hostname of your Dynatrace URL (for example, XXXXX.live.dynatrace.com
); it is not a URL and must not contain any schemas or pathsDT_API_TOKEN
should contain the access tokenfunc InitOpenTelemetry() {// ===== GENERAL SETUP =====DT_API_HOST := "" // Only the host part of your Dynatrace URLDT_API_BASE_PATH := "/api/v2/otlp"DT_API_TOKEN := ""authHeader := map[string]string{"Authorization": "Api-Token " + DT_API_TOKEN}ctx := context.Background()oneagentsdk := sdk.CreateInstance()dtMetadata := oneagentsdk.GetEnrichmentMetadata()var attributes []attribute.KeyValuefor k, v := range dtMetadata {attributes = append(attributes, attribute.KeyValue{Key: attribute.Key(k), Value: attribute.StringValue(v)})}attributes = append(attributes,semconv.ServiceNameKey.String("go-quickstart"), //TODO Replace with the name of your applicationsemconv.ServiceVersionKey.String("1.0.1"), //TODO Replace with the version of your application)res, err := resource.New(ctx, resource.WithAttributes(attributes...))if err != nil {log.Fatalf("Failed to create resource: %v", err)}// ===== TRACING SETUP =====exporter, err := otlptracehttp.New(ctx,otlptracehttp.WithEndpoint(DT_API_HOST),otlptracehttp.WithURLPath(DT_API_BASE_PATH+"/v1/traces"),otlptracehttp.WithHeaders(authHeader),)if err != nil {log.Fatalf("Failed to create OTLP exporter: %v", err)}tp := sdktrace.NewTracerProvider(sdktrace.WithResource(res),sdktrace.WithSampler(sdktrace.AlwaysSample()),sdktrace.WithBatcher(exporter),)otel.SetTracerProvider(tp)otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))// ===== METRIC SETUP =====var deltaTemporalitySelector = func(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality }metricsExporter, err := otlpmetrichttp.New(ctx,otlpmetrichttp.WithEndpoint(DT_API_HOST),otlpmetrichttp.WithURLPath(DT_API_BASE_PATH+"/v1/metrics"),otlpmetrichttp.WithHeaders(authHeader),otlpmetrichttp.WithTemporalitySelector(deltaTemporalitySelector),)if err != nil {log.Fatalf("Failed to create OTLP exporter: %v", err)}mp := sdkmetric.NewMeterProvider(sdkmetric.WithResource(res),sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricsExporter, sdkmetric.WithInterval(2*time.Second))),)otel.SetMeterProvider(mp)// ===== LOG SETUP =====logExporter, err := otlploghttp.New(ctx,otlploghttp.WithEndpoint(DT_API_HOST),otlploghttp.WithURLPath(DT_API_BASE_PATH+"/v1/logs"),otlploghttp.WithHeaders(authHeader),)if err != nil {log.Fatalf("Failed to create OTLP exporter: %v", err)}lp := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),sdklog.WithResource(res),)global.SetLoggerProvider(lp)logger := otelslog.NewLogger("my-logger-scope", otelslog.WithLoggerProvider(lp))slog.SetDefault(logger) // here we are overwriting the sdtout to http logger exporter}
Make sure to call InitOpenTelemetry
as early as possible in your startup code to initialize OpenTelemetry.
Browse the OpenTelemetry registry and pick the instrumentation libraries matching your application libraries.
Add the relevant packages to your import statements.
import ("go.opentelemetry.io/[PACKAGE]")
Run Go's mod tiny
command to install the dependencies.
go mod tidy
Wrap your existing code with calls to the support libraries.
net/http
Install the instrumentation library for net/http
.
Add the package to your import statements.
import (// other packages"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp")
Wrap your HTTP handler function.
handler := http.HandlerFunc(httpHandler)wrappedHandler := otelhttp.NewHandler(handler, "my-span") //TODO Replace with the name of your span//Use the wrappedHandler with your handlehttp.Handle("/", wrappedHandler)
You first need to get a tracer object.
tracer := otel.Tracer("my-tracer")
With tracer
, you can now use a span builder to create and start new spans.
_, span := tracer.Start(r.Context(), "Call to /myendpoint")defer span.End()span.SetAttributes(attribute.String("http.method", "GET"), attribute.String("net.protocol.version", "1.1"))// TODO your code goes here
In the code above, we:
End()
, to ensure the span is properly closed when the function returnsTODO
in place of the eventual business logicObtain a meter object.
meter := otel.Meter("my-meter")
With meter
, we can now create individual instruments, such as a counter.
requestCounter, _ := meter.Int64Counter("request_counter")
Now we can invoke the Add()
method of requestCounter
to record new values with the counter.
requestCounter.Add(context.Background(), 1)
With OpenTelemetry logging initialized in InitOpenTelemetry()
and set as default logger for slog, we can now call any of slog's log functions (for example, Info()
) to send our log information to Dynatrace.
slog.Info("an info message")slog.Debug("a debug message")slog.Error("an error")
Context propagation is particularly important when network calls (for example, REST) are involved.
In the following example, we assume that we have received a network call via the net/http
library and its Request
type.
To obtain a handle to the original context (which was provided by the calling service), we pass the HTTP header object (r.Header
) to the Extract
function of the global propagator singleton, which instantiates that context and returns in parentCtx
. This allows us to continue the previous trace with our own spans.
func httpHandler(w http.ResponseWriter, r *http.Request) {parentCtx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))tracer := otel.Tracer("my-tracer")ctx, span := tracer.Start(parentCtx,"manual-server", //TODO Replace with the name of your spantrace.WithAttributes(attribute.String("my-key-1", "my-value-1"), //TODO Add attributes),)defer span.End()//TODO your code goes here}
In the following example, we set up a new instance of Request
and pass the object to the Inject
call of the global propagator singleton. This adds the necessary HTTP headers to the request object, which we eventually pass to Do
to execute the network request.
client := http.Client{}req, err := http.NewRequest("<method>", "<url>", <body>)if err != nil {// TODO handle error}//Method to inject the current context in the request headersotel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))client.Do(req) // Your call goes here
While Dynatrace automatically captures all OpenTelemetry attributes, only attribute values specified in the allowlist are stored and displayed in the Dynatrace web UI. This prevents accidental storage of personal data, so you can meet your privacy requirements and control the amount of monitoring data stored.
To view your custom attributes, you need to allow them in the Dynatrace web UI first. To learn how to configure attribute storage and masking, see Attribute redaction.
Once you have finished the instrumentation of your application, perform a couple of test actions to create and send demo traces, metrics, and logs and verify that they were correctly ingested into Dynatrace.
To do that for traces, go to Distributed Traces or Distributed Traces Classic (latest Dynatrace) and select the Ingested traces tab. If you use OneAgent, select PurePaths instead.
For metrics and logs, go to Metrics or Logs or Logs & Events (latest Dynatrace).