Ingest Golang trace data using OpenTelemetry

Updated at:
Copy as MD

Use the OpenTelemetry Golang SDK to ingest trace data from a Golang application into Simple Log Service.

Note

Simple Log Service also provides a commercial, proprietary probe for Go that offers non-intrusive instrumentation, richer features, and greater stability. For more information, see Start monitoring Go applications.

Prerequisites

  • A Trace instance is created. For more information, see Create a Trace instance.

  • Golang 1.13 or later is installed.

Ingestion flow

  1. Initialize the OpenTelemetry provider.

  2. Check whether the conditions for semi-automatic ingestion are met.

    • If the conditions are met, use the semi-automatic method to ingest trace data.

      For scenarios not covered by the semi-automatic method, use the manual method instead.

    • If the conditions are not met, use the manual method to ingest trace data.

Step 1: Initialize the OpenTelemetry provider

To simplify using the OpenTelemetry provider, Simple Log Service provides the SLS Provider to help you quickly build the required dependencies and upload them to Simple Log Service.

Important

Initialize the OpenTelemetry provider before you create traces or register metrics.

Initialize the OpenTelemetry provider by running code or by configuring environment variables:

  • Initialize by running code

    1. Add dependencies.

      module opentelemetry-golang-sample
      
      go 1.13
      
      require (
          github.com/aliyun-sls/opentelemetry-go-provider-sls v0.10.0
          go.opentelemetry.io/otel v1.16.0
          go.opentelemetry.io/otel/metric v1.16.0
          go.opentelemetry.io/otel/trace v1.16.0
      )
      
    2. Configure the initialization code.

      Replace the variables in the following code with actual values. For variable descriptions, see Variable description.

      package main
      
      import (
          "github.com/aliyun-sls/opentelemetry-go-provider-sls/provider"
      )
      
      func main() {
      
          slsConfig, err := provider.NewConfig(provider.WithServiceName("${service}"),
              provider.WithServiceNamespace("${service.namespace}"),
              provider.WithServiceVersion("${version}"),
              provider.WithTraceExporterEndpoint("${endpoint}"),
              provider.WithMetricExporterEndpoint("${endpoint}"),
              provider.WithSLSConfig("${project}", "${instance}", "${access-key-id}", "${access-key-secret}"))
          // Use panic() to exit the program if initialization fails. You can also use other error handling methods.
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
          
          // Add your business logic code.
          ...
      }

      Table 1. Variable description

      Variable

      Description

      Example

      ${service}

      The service name. Specify a value based on your scenario.

      payment

      ${service.namespace}

      The namespace to which the service belongs.

      order

      ${version}

      The service version number. Format: va.b.c.

      v0.1.2

      ${endpoint}

      The endpoint of the Simple Log Service project. Format: ${project}.${region-endpoint}:Port.

      • ${project}: The name of the Simple Log Service project.

      • ${region-endpoint}: The endpoint of the region where the project resides. Public, classic, and VPC endpoints are supported. For more information, see Endpoints.

      • Port: The network port. Fixed value: 10010.

      Note
      • If you set this parameter to stdout, for example, provider.WithTraceExporterEndpoint("stdout"), data is printed to the standard output.

      • If you leave this parameter empty, trace data is not uploaded.

      test-project.cn-hangzhou.log.aliyuncs.com:10010

      ${project}

      The name of the Simple Log Service project.

      test-project

      ${instance}

      The ID of the Trace instance. For more information, see Create a Trace instance.

      test-traces

      ${access-key-id}

      The AccessKey ID of your Alibaba Cloud account.

      Use the AccessKey pair (AccessKey ID and AccessKey secret) of a RAM user who has only the write permissions on the Simple Log Service project. For more information about granting write permissions on a specified project to a RAM user, see Authorization. For more information about obtaining an AccessKey pair, see AccessKey pair.

      None

      ${access-key-secret}

      The AccessKey secret of your Alibaba Cloud account.

      Use the AccessKey pair of a RAM user who has only write permissions on the project.

      None

  • Initialize by configuring environment variables

    Configuration method

    Environment variable

    Required

    Description

    Default value

    WithServiceName

    SLS_OTEL_SERVICE_NAME

    Yes

    The service name. Specify a value based on your scenario.

    None

    WithServiceNamespace

    SLS_OTEL_SERVICE-NAMESPACE

    No

    The namespace to which the service belongs.

    order

    WithServiceVersion

    SLS_OTEL_SERVICE_VERSION

    Yes

    The service version number. Format: va.b.c.

    v0.1.0

    WithSLSConfig

    SLS_OTEL_PROJECT, SLS_OTEL_INSTANCE_ID, SLS_OTEL_ACCESS_KEY_ID, SLS_OTEL_ACCESS_KEY_SECRET

    No

    The Simple Log Service resource information, including the project name, Trace instance name, and the AccessKey ID and AccessKey secret of a user who has write-only permissions on the project. For more information about granting write permissions on a specified project to a RAM user, see Authorization. For more information about obtaining an AccessKey pair, see AccessKey pair.

    None

    WithTraceExporterEndpoint

    SLS_OTEL_TRACE_ENDPOINT

    No

    The endpoint of the Simple Log Service project. Format: ${project}.${region-endpoint}:Port.

    • ${project}: The name of the Simple Log Service project.

    • ${region-endpoint}: The endpoint of the region where the project resides. Public, classic, and VPC endpoints are supported. For more information, see Endpoints.

    • Port: The network port. Fixed value: 10010.

    Note
    • If you set this parameter to stdout, data is printed to the standard output.

    • If you leave this parameter empty, trace data is not uploaded.

    stdout

    WithTraceExporterInsecure

    SLS_OTEL_TRACE_INSECURE

    No

    Specifies whether to transmit data without encryption.

    • true: Transmit data without encryption.

    • false: Transmit data with encryption.

    Note

    When sending data directly to Simple Log Service, set this parameter to false.

    false

    WithMetricExporterEndpoint

    SLS_OTEL_METRIC_ENDPOINT

    No

    The endpoint of the Simple Log Service project. Format: ${project}.${region-endpoint}:Port.

    • ${project}: The name of the Simple Log Service project.

    • ${region-endpoint}: The endpoint of the region where the project resides. Public, classic, and VPC endpoints are supported. For more information, see Endpoints.

    • Port: The network port. Fixed value: 10010.

    Note
    • If you set this parameter to stdout, data is printed to the standard output.

    • If you leave this parameter empty, metric data is not uploaded.

    stdout

    WithMetricExporterInsecure

    SLS_OTEL_METRIC_INSECURE

    No

    Specifies whether to transmit data without encryption.

    • true: Transmit data without encryption.

    • false: Transmit data with encryption.

    Note

    When sending data directly to Simple Log Service, set this parameter to false.

    false

    WithResourceAttributes

    None

    No

    Additional tags, such as the environment and zone.

    None

    WithResource

    OTEL_RESOURCE_ATTRIBUTES

    No

    Additional tags, such as the environment and zone. Format: key1=value1,key2=value2.

    None

    WithMetricReportingPeriod

    SLS_OTEL_METRIC_EXPORT_PERIOD

    No

    The metric export interval. Valid values: 15s to 60s.

    30s

    WithErrorHandler

    None

    No

    The error handling function. Called when an internal error occurs in the SDK. Equivalent to WithErrorHandlerFunc.

    None

    WithErrorHandlerFunc

    None

    No

    The error handling function.

    None

    None

    SLS_OTEL_ATTRIBUTES_ENV_KEYS

    No

    Additional tags, such as the environment and zone. Similar to OTEL_RESOURCE_ATTRIBUTES, but the attribute keys defined by SLS_OTEL_ATTRIBUTES_ENV_KEYS read their actual values from the corresponding environment variables.

    This is commonly used in Kubernetes scenarios to fill template values into specific environment variables. Format: env-key-1|env-key-2|env-key-3.

    None

Step 2: Ingest data

  • (Recommended) Semi-automatic ingestion

    OpenTelemetry provides automatic instrumentation for many common libraries. If your application uses these libraries, use the corresponding automatic instrumentation solutions to ingest data. For a list of supported libraries, see Golang automatic instrumentation solutions.

    • net/http ingestion

      The following example is based on go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0. For more information, see otel-http-example.

      Replace the variables in the following code with actual values. For variable descriptions, see Variable description.

      package main
      
      import (
          "fmt"
          "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
          "go.opentelemetry.io/otel/attribute"
          "go.opentelemetry.io/otel/metric/global"
          "io"
          "net/http"
          "time"
      
          "github.com/aliyun-sls/opentelemetry-go-provider-sls/provider"
      
          "go.opentelemetry.io/otel/trace"
      )
      
      func main() {
      
          slsConfig, err := provider.NewConfig(provider.WithServiceName("${service}"),
              provider.WithServiceNamespace("${service.namespace}"),
              provider.WithServiceVersion("${version}"),
              provider.WithTraceExporterEndpoint("${endpoint}"),
              provider.WithMetricExporterEndpoint("${endpoint}"),
              provider.WithSLSConfig("${project}", "${instance}", "${access-key-id}", "${access-key-secret}"))
          // Use panic() to exit the program if initialization fails. You can also use other error handling methods.
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
      
          // To analyze metric data in your application, you can register the relevant metrics.
          labels := []attribute.KeyValue{
              attribute.String("label1", "value1"),
          }
          meter := global.Meter("aliyun.sls")
          sayDavidCount, _ := meter.Int64Counter("say_david_count")
      
          helloHandler := func(w http.ResponseWriter, req *http.Request) {
              if time.Now().Unix()%10 == 0 {
                  _, _ = io.WriteString(w, "Hello, world!\n")
              } else {
                  // To record events, you can get the Span from the Context and add an Event.
                  ctx := req.Context()
                  span := trace.SpanFromContext(ctx)
                  span.AddEvent("say : Hello, I am david", trace.WithAttributes(attribute.KeyValue{
                      Key:   "label-key-1",
                      Value: attribute.StringValue("label-value-1"),
                  }))
      
                  _, _ = io.WriteString(w, "Hello, I am david!\n")
                  sayDavidCount.Add(req.Context(), 1, labels...)
              }
          }
      
          // To use the automatic injection of otel net/http, just wrap http.Handler with otelhttp.NewHandler.
          otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello")
      
          http.Handle("/hello", otelHandler)
          fmt.Println("Now listen port 8080, you can visit 127.0.0.1:8080/hello .")
          err = http.ListenAndServe(":8080", nil)
          if err != nil {
              panic(err)
          }
      }
                                  
    • mux ingestion

      The following example is based on go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.37.0. The API may change in later versions. For the latest example, see otel-mux-example.

      Replace the variables in the following code with actual values. For variable descriptions, see Variable description.

      package main
      
      import (
          "context"
          "fmt"
          "go.opentelemetry.io/otel/attribute"
          "go.opentelemetry.io/otel/metric/global"
          "net/http"
      
          "github.com/aliyun-sls/opentelemetry-go-provider-sls/provider"
      
          "github.com/gorilla/mux"
          "go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux"
          "go.opentelemetry.io/otel/trace"
      )
      
      func main() {
      
          slsConfig, err := provider.NewConfig(provider.WithServiceName("${service}"),
              provider.WithServiceNamespace("${service.namespace}"),
              provider.WithServiceVersion("${version}"),
              provider.WithTraceExporterEndpoint("${endpoint}"),
              provider.WithMetricExporterEndpoint("${endpoint}"),
              provider.WithSLSConfig("${project}", "${instance}", "${access-key-id}", "${access-key-secret}"))
          // Use panic() to exit the program if initialization fails. You can also use other error handling methods.
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
      
          // To analyze metric data in your application, you can register the relevant metrics.
          labels := []attribute.KeyValue{
              attribute.String("label1", "value1"),
          }
          meter := global.Meter("aliyun.sls")
          callUsersCount, _ := meter.Int64Counter("call_users_count")
      
          r := mux.NewRouter()
          r.Use(otelmux.Middleware("my-server"))
          r.HandleFunc("/users/{id:[0-9]+}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
              vars := mux.Vars(r)
              id := vars["id"]
              callUsersCount.Add(r.Context(), 1, labels...)
              name := getUser(r.Context(), id)
              reply := fmt.Sprintf("user %s (id %s)\n", name, id)
              _, _ = w.Write(([]byte)(reply))
          }))
          http.Handle("/", r)
          fmt.Println("Now listen port 8080, you can visit 127.0.0.1:8080/users/xxx .")
          _ = http.ListenAndServe(":8080", nil)
      }
      
      func getUser(ctx context.Context, id string) string {
          if id == "123" {
              return "otelmux tester"
          }
          // To record events, you can get the Span from the Context and add an Event.
          span := trace.SpanFromContext(ctx)
          span.AddEvent("unknown user id : "+id, trace.WithAttributes(attribute.KeyValue{
              Key:   "label-key-1",
              Value: attribute.StringValue("label-value-1"),
          }))
          return "unknown"
      }
                                  
  • Manual ingestion

    Replace the variables in the following code with actual values. For variable descriptions, see Variable description.

    // Copyright The AliyunSLS Authors
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //     http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    
    package main
    
    import (
        "context"
        "errors"
        "fmt"
        "go.opentelemetry.io/otel/attribute"
        "go.opentelemetry.io/otel/metric/global"
        "go.opentelemetry.io/otel/metric/instrument"
        "math/rand"
        "time"
    
        "github.com/aliyun-sls/opentelemetry-go-provider-sls/provider"
    
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/codes"
        "go.opentelemetry.io/otel/trace"
    )
    
    func main() {
        slsConfig, err := provider.NewConfig(provider.WithServiceName("payment"),
            provider.WithServiceVersion("v0.1.0"),
            provider.WithTraceExporterEndpoint("stdout"),
            provider.WithMetricExporterEndpoint("stdout"),
            provider.WithSLSConfig("test-project", "test-otel", "access-key-id", "access-key-secret"))
        // Use panic() to exit the program if initialization fails. You can also use other error handling methods.
        if err != nil {
            panic(err)
        }
        if err := provider.Start(slsConfig); err != nil {
            panic(err)
        }
        defer provider.Shutdown(slsConfig)
    
        mockTrace()
        mockMetrics()
    }
    
    func mockMetrics() {
        // Add tag information.
        labels := []attribute.KeyValue{
            attribute.String("label1", "value1"),
        }
    
        meter := global.Meter("ex.com/basic")
    
        meter.Float64ObservableCounter("randval", instrument.WithFloat64Callback(func(ctx context.Context, observer instrument.Float64Observer) error {
            observer.Observe(rand.Float64(), labels...)
            return nil
        }))
    
        temperature, _ := meter.Float64Counter("temperature")
        interrupts, _ := meter.Int64Counter("interrupts")
    
        ctx := context.Background()
    
        for {
            temperature.Add(ctx, 100+10*rand.NormFloat64(), labels...)
            interrupts.Add(ctx, int64(rand.Intn(100)), labels...)
    
            time.Sleep(time.Second * time.Duration(rand.Intn(10)))
        }
    }
    
    func mockTrace() {
    
        tracer := otel.Tracer("ex.com/basic")
    
        ctx0 := context.Background()
    
        ctx1, finish1 := tracer.Start(ctx0, "foo")
        defer finish1.End()
    
        ctx2, finish2 := tracer.Start(ctx1, "bar")
        defer finish2.End()
    
        ctx3, finish3 := tracer.Start(ctx2, "baz")
        defer finish3.End()
    
        ctx := ctx3
        getSpan(ctx)
        addAttribute(ctx)
        addEvent(ctx)
        recordException(ctx)
        createChild(ctx, tracer)
    }
    
    // example of getting the current span
    // Get the current Span.
    func getSpan(ctx context.Context) {
        span := trace.SpanFromContext(ctx)
        fmt.Printf("current span: %v\n", span)
    }
    
    // example of adding an attribute to a span
    // Add an attribute to the Span.
    func addAttribute(ctx context.Context) {
        span := trace.SpanFromContext(ctx)
        span.SetAttributes(attribute.KeyValue{
            Key:   "label-key-1",
            Value: attribute.StringValue("label-value-1")})
    }
    
    // example of adding an event to a span
    // Add an event to the Span.
    func addEvent(ctx context.Context) {
        span := trace.SpanFromContext(ctx)
        span.AddEvent("event1", trace.WithAttributes(
            attribute.String("event-attr1", "event-string1"),
            attribute.Int64("event-attr2", 10)))
    }
    
    // example of recording an exception
    // Record the Span result and error message.
    func recordException(ctx context.Context) {
        span := trace.SpanFromContext(ctx)
        span.RecordError(errors.New("exception has occurred"))
        span.SetStatus(codes.Error, "internal error")
    }
    
    // example of creating a child span
    // Create a child Span.
    func createChild(ctx context.Context, tracer trace.Tracer) {
        // span := trace.SpanFromContext(ctx)
        _, childSpan := tracer.Start(ctx, "child")
        defer childSpan.End()
        fmt.Printf("child span: %v\n", childSpan)
    }
                        

What to do next