{
  "slug": "Using-open-telemetry-in-Golang-ef282c236e77",
  "title": "Using open telemetry in Golang",
  "subtitle": "In such a way that traces and metrics are emitted to the console.",
  "excerpt": "In such a way that traces and metrics are emitted to the console.",
  "date": "2023-11-15",
  "tags": [
    "My Experience",
    "Go"
  ],
  "readingTime": "6 min",
  "url": "https://medium.com/@mobinshaterian/using-open-telemetry-in-golang-ef282c236e77",
  "hero": "https://cdn-images-1.medium.com/max/800/1*Wu0W1mNZ_0fhfeGekIrZNA.png",
  "content": [
    {
      "type": "heading",
      "level": 2,
      "text": "Using open telemetry in Golang"
    },
    {
      "type": "paragraph",
      "html": "In such a way that <a href=\"https://opentelemetry.io/docs/concepts/signals/traces/\" target=\"_blank\" rel=\"noreferrer noopener\">traces</a> and <a href=\"https://opentelemetry.io/docs/concepts/signals/metrics/\" target=\"_blank\" rel=\"noreferrer noopener\">metrics</a> are emitted to the console."
    },
    {
      "type": "paragraph",
      "html": "You can use in other framework too look like <strong>gin</strong>"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Connect open telemetry into gin"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*Wu0W1mNZ_0fhfeGekIrZNA.png",
      "alt": "https://signoz.io/blog/opentelemetry-backend/",
      "caption": "https://signoz.io/blog/opentelemetry-backend/",
      "width": 739,
      "height": 587
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Open telemetry examples"
    },
    {
      "type": "paragraph",
      "html": "<a href=\"https://github.com/open-telemetry/opentelemetry-go/tree/main/example\" target=\"_blank\" rel=\"noreferrer noopener\">https://github.com/open-telemetry/opentelemetry-go/tree/main/example</a>"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Get dependency of open telemetry in Golang"
    },
    {
      "type": "code",
      "lang": "bash",
      "code": "go get \"go.opentelemetry.io/otel\" \\  \"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric\" \\\n\"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\" \\  \"go.opentelemetry.io/otel/propagation\" \\\n\"go.opentelemetry.io/otel/sdk/metric\" \\  \"go.opentelemetry.io/otel/sdk/resource\" \\\n\"go.opentelemetry.io/otel/sdk/trace\" \\  \"go.opentelemetry.io/otel/semconv/v1.21.0\" \\\n\"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\""
    },
    {
      "type": "heading",
      "level": 2,
      "text": "make otel.go file"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "package main\nimport (\n\"context\"\n\"errors\"\n\"time\"\n\"go.opentelemetry.io/otel\"\n\"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric\"\n\"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\"\n\"go.opentelemetry.io/otel/propagation\"\n\"go.opentelemetry.io/otel/sdk/metric\"\n\"go.opentelemetry.io/otel/sdk/resource\"\n\"go.opentelemetry.io/otel/sdk/trace\" semconv \"go.opentelemetry.io/otel/semconv/v1.21.0\")//\nsetupOTelSDK bootstraps the OpenTelemetry pipeline.// If it does not\nreturn an error, make sure to call shutdown for proper cleanup.func setupOTelSDK(ctx\ncontext.Context, serviceName, serviceVersion string) (shutdown func(context.Context) error, err\nerror) {\n    var shutdownFuncs []func(context.Context) error // shutdown calls cleanup\n    functions registered via shutdownFuncs.// The errors from the calls are joined.// Each\n    registered cleanup will be invoked once.shutdown =\n    func(ctx context.Context) error {\n        var err error for _, fn := range shutdownFuncs {\n            err = errors.Join(err, fn(ctx))\n        }\n        shutdownFuncs = nil return err\n    }\n    // handleErr calls shutdown for cleanup and makes sure that all errors are returned.handleErr :=\n    func(inErr error) {\n        err = errors.Join(inErr, shutdown(ctx))\n    }\n    // Set up resource.res, err := newResource(serviceName, serviceVersion) if err != nil {\n        handleErr(err) return\n    }\n    // Set up propagator.prop := newPropagator() otel.SetTextMapPropagator(prop) // Set up trace\n    provider.tracerProvider, err := newTraceProvider(res) if err != nil {\n        handleErr(err) return\n    }\n    shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)\n    otel.SetTracerProvider(tracerProvider) // Set up meter provider.meterProvider, err :=\n    newMeterProvider(res) if err != nil {\n        handleErr(err) return\n    }\n    shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)\n    otel.SetMeterProvider(meterProvider) return\n}\nfunc newResource(serviceName, serviceVersion string) (*resource.Resource, error) {\n    return resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL,\n    semconv.ServiceName(serviceName), semconv.ServiceVersion(serviceVersion),))\n}\nfunc newPropagator() propagation.TextMapPropagator {\n    return propagation.NewCompositeTextMapPropagator(propagation.TraceContext{\n    }, propagation.Baggage{\n    },)\n}\nfunc newTraceProvider(res *resource.Resource) (*trace.TracerProvider, error) {\n    traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil {\n        return nil, err\n    }\n    traceProvider := trace.NewTracerProvider(trace.WithBatcher(traceExporter, // Default is 5s.Set\n    to 1s for demonstrative purposes.trace.WithBatchTimeout(time.Second)), trace.WithResource(res),)\n    return traceProvider, nil\n}\nfunc newMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) {\n    metricExporter, err := stdoutmetric.New() if err != nil {\n        return nil, err\n    }\n    meterProvider := metric.NewMeterProvider(metric.WithResource(res),\n    metric.WithReader(metric.NewPeriodicReader(metricExporter, // Default is 1m.Set to 3s for\n    demonstrative purposes.metric.WithInterval(3*time.Second))),)\n    return meterProvider, nil\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "add Gin in open telemetry"
    },
    {
      "type": "paragraph",
      "html": "<a href=\"https://signoz.io/blog/opentelemetry-gin/\" target=\"_blank\" rel=\"noreferrer noopener\">https://signoz.io/blog/opentelemetry-gin/</a>"
    },
    {
      "type": "paragraph",
      "html": "<a href=\"https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/gin-gonic/gin/otelgin/example/server.go\" target=\"_blank\" rel=\"noreferrer noopener\">https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/instrumentation/github.com/gin-gonic/gin/otelgin/example/server.go</a>"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "func InitTracer(serviceName string) (*sdktrace.TracerProvider, error) {\n    secureOption := otlptracegrpc.WithInsecure() exporter, err :=\n    otlptrace.New(context.Background(), otlptracegrpc.NewClient(secureOption,\n    otlptracegrpc.WithEndpoint(config.OtelEndpoint()),),) if err != nil {\n        log.Fatal(err)\n    }\n    resources, err := resource.New(context.Background(),\n    resource.WithAttributes(attribute.String(\"service.name\", ServiceName()),),) if err != nil {\n        log.Fatal(\"Could not set resources: \", err)\n    }\n    tp :=\n    sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.TraceIDRatioBased(config.OtelSampleRate())), //sdktrace.AlwaysSample() sdktrace.WithBatcher(\n    exporter), sdktrace.WithResource(resources),) otel.SetTracerProvider(tp) // Set up meter\n    provider.metricExporter, err := stdoutmetric.New() if err != nil {\n        return nil, err\n    }\n    meterProvider :=\n    metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(metricExporter, // Default is\n    1m.Set to 3s for demonstrative purposes.metric.WithInterval(3*time.Second))),\n    metric.WithResource(resources),) otel.SetMeterProvider(meterProvider) //propagator\n    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{\n    }, propagation.Baggage{\n    })) TracerMain = tp.Tracer(serviceName) return tp, nil\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Add metric with open telemetry"
    },
    {
      "type": "paragraph",
      "html": "<a href=\"https://github.com/open-telemetry/opentelemetry-go/blob/main/example/dice/rolldice.go\" target=\"_blank\" rel=\"noreferrer noopener\">https://github.com/open-telemetry/opentelemetry-go/blob/main/example/dice/rolldice.go</a>"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "ADD event"
    },
    {
      "type": "code",
      "lang": "javascript",
      "code": "var span trace.Span ctx, span = tracer.Start(ctx, \"operation\") defer span.End()\nspan.AddEvent(\"Nice operation!\", trace.WithAttributes(attribute.Int(\"bogons\", 100)))\nspan.SetAttributes(anotherKey.String(\"yes\")) if err := foo.SubOperation(ctx);\nerr != nil {\n  panic(err)\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Connect to prometeus"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Sampling"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "type key intconst sampleKey key = iotatype sampler struct {\n    delegate trace.Sampler\n}\nfunc New(delegate trace.Sampler) trace.Sampler {\n    return &sampler{\n        delegate: delegate\n    }\n}\nfunc (s *sampler) ShouldSample(p trace.SamplingParameters) trace.SamplingResult {\n    if _, ok := p.ParentContext.Value(sampleKey).(struct{\n    });\n    ok {\n        return trace.SamplingResult{\n            Decision: trace.RecordAndSample, Tracestate:\n            traceapi.SpanContextFromContext(p.ParentContext).TraceState(),\n        }\n    }\n    return s.delegate.ShouldSample(p)\n}\nfunc (s *sampler) Description() string {\n    return fmt.Sprintf(\"my-sampler{%s}\", s.delegate.Description())\n}\nfunc main() {\n    s := New(trace.ParentBased(trace.TraceIDRatioBased(0.5))) tracer :=\n    trace.NewTracerProvider(trace.WithSampler(s)).Tracer(\"my-app\") ctx :=\n    context.WithValue(context.Background(), sampleKey, struct{\n    }\n    {\n    }) _, span := tracer.Start(ctx, \"span name\") fmt.Println(span.SpanContext().IsSampled())\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Add graceful shutdown"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "// setupOTelSDK bootstraps the OpenTelemetry pipeline.// If it does not\nreturn an error, make sure to call shutdown for proper cleanup.func SetupOTelSDK(ctx\ncontext.Context, serviceName, serviceVersion string) (shutdown func(context.Context) error, err\nerror) {\n    var shutdownFuncs []func(context.Context) error // shutdown calls cleanup\n    functions registered via shutdownFuncs.// The errors from the calls are joined.// Each\n    registered cleanup will be invoked once.shutdown =\n    func(ctx context.Context) error {\n        var err error for _, fn := range shutdownFuncs {\n            err = errors.Join(err, fn(ctx))\n        }\n        shutdownFuncs = nil return err\n    }\n    // handleErr calls shutdown for cleanup and makes sure that all errors are returned.handleErr :=\n    func(inErr error) {\n        err = errors.Join(inErr, shutdown(ctx))\n    }\n    // Set up resource.res, err := newResource(serviceName, serviceVersion) if err != nil {\n        handleErr(err) return\n    }\n    // Set up propagator.prop := newPropagator() otel.SetTextMapPropagator(prop) // Set up trace\n    provider.tracerProvider, err := newTraceProvider(res) if err != nil {\n        handleErr(err) return\n    }\n    shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)\n    otel.SetTracerProvider(tracerProvider) // Set up meter provider.meterProvider, err :=\n    newMeterProvider(res) if err != nil {\n        handleErr(err) return\n    }\n    shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)\n    otel.SetMeterProvider(meterProvider) return\n}\nfunc newResource(serviceName, serviceVersion string) (*resource.Resource, error) {\n    return resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL,\n    semconv.ServiceName(serviceName), semconv.ServiceVersion(serviceVersion),))\n}\nfunc newPropagator() propagation.TextMapPropagator {\n    return propagation.NewCompositeTextMapPropagator(propagation.TraceContext{\n    }, propagation.Baggage{\n    },)\n}\nfunc newTraceProvider(res *resource.Resource) (*trace.TracerProvider, error) {\n    traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) if err != nil {\n        return nil, err\n    }\n    traceProvider := trace.NewTracerProvider(trace.WithBatcher(traceExporter, // Default is 5s.Set\n    to 1s for demonstrative purposes.trace.WithBatchTimeout(time.Second)), trace.WithResource(res),)\n    return traceProvider, nil\n}\nfunc newMeterProvider(res *resource.Resource) (*metric.MeterProvider, error) {\n    metricExporter, err := stdoutmetric.New() if err != nil {\n        return nil, err\n    }\n    meterProvider := metric.NewMeterProvider(metric.WithResource(res),\n    metric.WithReader(metric.NewPeriodicReader(metricExporter, // Default is 1m.Set to 3s for\n    demonstrative purposes.metric.WithInterval(3*time.Second))),)\n    return meterProvider, nil\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Add traceClientHttp"
    },
    {
      "type": "paragraph",
      "html": "Initialize OpenTelemetry SDK for Go. Replace Endpoint and URLPath with the endpoint obtained in the Prerequisites section."
    },
    {
      "type": "paragraph",
      "html": "You can submit data by using OpenTelemetry SDK for Go or forward data by using the open source OpenTelemetry Collector."
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "If you submit data by using OpenTelemetry SDK for Go, replace Endpoint and URLPath with the endpoint obtained in the Prerequisites section.",
        "If you forward data by using the open source OpenTelemetry Collector, replace Endpoint with the IP address of your on-premises service and delete the headers"
      ]
    },
    {
      "type": "code",
      "lang": "go",
      "code": "func initProvider(\n)\nfunc() {\n    ctx := context.Background()\n    traceClientHttp := otlptracehttp.NewClient(otlptracehttp.WithEndpoint(\"127.0.XX.XX:8080\"),\n    //Replace Endpoint with the endpoint obtained in the Prerequisites\n    section.otlptracehttp.WithURLPath(\"/adapt_xxxxx/api/otlp/traces\"), //Replace URLPath with the\n    endpoint obtained in the Prerequisites section.otlptracehttp.WithInsecure())\n    otlptracehttp.WithCompression(1)\n    traceExp, err := otlptrace.New(ctx, traceClientHttp)\n    handleErr(err, \"Failed to create the collector trace\nexporter\")\n    res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithProcess(),\n    resource.WithTelemetrySDK(), resource.WithHost(), resource.WithAttributes(// Specify the service\n    name displayed on the backend of Tracing\n    Analysis.semconv.ServiceNameKey.String(common.ClientServiceName),),\n    )\n    handleErr(err, \"failed to create resource\")\n    bsp := sdktrace.NewBatchSpanProcessor(traceExp)\n    tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample()),\n    sdktrace.WithResource(res), sdktrace.WithSpanProcessor(bsp),\n    )\n    // Set the global propagator to tracecontext.The global propagator is not specified by\n    default.otel.SetTextMapPropagator(propagation.TraceContext{\n    })\n    otel.SetTracerProvider(tracerProvider)\n    log.Println(\"OTEL init success\")\n    return func() {\n        cxt, cancel := context.WithTimeout(ctx, time.Second) defer cancel() if err :=\n        traceExp.Shutdown(cxt);\n        err != nil {\n            otel.Handle(err)\n        }\n    }\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Add console tracer"
    },
    {
      "type": "paragraph",
      "html": "<strong>For run locally</strong>"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())  if err != nil {  log.Fatal(err)}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "if you had a “ failed to build resolver: passthrough: received empty target in Build() ” error in tests, then put below code for your open telemetry in test cases:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "tp = SDKTrace.NewTracerProvider(  SDKTrace.WithSpanProcessor(sentryOtel.NewSentrySpanProcessor()), )\notel.SetTracerProvider(tp) otel.SetTextMapPropagator(sentryOtel.NewSentryPropagator()) Tracer =\ntp.Tracer(serviceName) return tp}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "OpenTelemetry Protocol Exporter"
    },
    {
      "type": "paragraph",
      "html": "<strong>Endpoint (OTLP/HTTP)</strong>:"
    },
    {
      "type": "paragraph",
      "html": "When using <code>OTEL_EXPORTER_OTLP_ENDPOINT</code>, exporters MUST construct per-signal URLs as <a href=\"https://opentelemetry.io/docs/specs/otel/protocol/exporter/#endpoint-urls-for-otlphttp\" target=\"_blank\" rel=\"noreferrer noopener\">described below</a>."
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Default: <code>http://localhost:4318</code> [1]",
        "Env vars: <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_TRACES_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_METRICS_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_LOGS_ENDPOINT</code>"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Endpoint (OTLP/gRPC)</strong>:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Default: <code>http://localhost:4317</code> [1]",
        "Env vars: <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_TRACES_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_METRICS_ENDPOINT</code> <code>OTEL_EXPORTER_OTLP_LOGS_ENDPOINT</code>"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Insecure</strong>:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Default: <code>false</code>",
        "Env vars: <code>OTEL_EXPORTER_OTLP_INSECURE</code> <code>OTEL_EXPORTER_OTLP_TRACES_INSECURE</code> <code>OTEL_EXPORTER_OTLP_METRICS_INSECURE</code> <code>OTEL_EXPORTER_OTLP_LOGS_INSECURE</code>"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Timeout</strong>: Maximum time the OTLP exporter will wait for each batch export."
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Default: 10s",
        "Env vars: <code>OTEL_EXPORTER_OTLP_TIMEOUT</code> <code>OTEL_EXPORTER_OTLP_TRACES_TIMEOUT</code> <code>OTEL_EXPORTER_OTLP_METRICS_TIMEOUT</code> <code>OTEL_EXPORTER_OTLP_LOGS_TIMEOUT</code>"
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>Protocol</strong>: The transport protocol. Options MUST be one of: <code>grpc</code>, <code>http/protobuf</code>, <code>http/json</code>. See <a href=\"https://opentelemetry.io/docs/specs/otel/protocol/exporter/#specify-protocol\" target=\"_blank\" rel=\"noreferrer noopener\">Specify Protocol</a> for more details."
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Default: <code>http/protobuf</code>",
        "Env vars: <code>OTEL_EXPORTER_OTLP_PROTOCOL</code> <code>OTEL_EXPORTER_OTLP_TRACES_PROTOCOL</code> <code>OTEL_EXPORTER_OTLP_METRICS_PROTOCOL</code> <code>OTEL_EXPORTER_OTLP_LOGS_PROTOCOL</code>"
      ]
    }
  ]
}
