All Products
Search
Document Center

Log Service:Import trace data from Golang applications to Simple Log Service by using OpenTelemetry SDK for Golang

Last Updated:Aug 25, 2023

This topic describes how to import trace data from Golang applications to Simple Log Service by using OpenTelemetry SDK for Golang.

Prerequisites

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

  • A Golang development environment is set up. The Golang version is 1.13 or later.

Procedure

  1. Initialize an OpenTelemetry provider.

  2. Check whether the conditions for importing data in semi-automatic mode are met.

    • If the conditions are met, you can import trace data in semi-automatic mode.

      If the semi-automatic mode does not meet your requirements, you must manually import the trace data.

    • If the conditions are not met, you can import trace data in manual mode.

Step 1: Initialize an OpenTelemetry provider

Simple Log Service offers a provider that allows you to build dependencies and upload the dependencies to Simple Log Service. This provider helps simplify the use of an OpenTelemetry provider. For more information, see opentelemetry-go-provider-sls.

Important

You must initialize an OpenTelemetry provider before you create traces and register metrics.

You can run code or configure environment variables to initialize an OpenTelemetry provider.

  • Run code to initialize an OpenTelemetry provider.

    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. Write initialization code.

      Replace the variables in the following code with the actual values. For more information about the variables, see Variables.

      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}"))
          // Invoke the panic() function. If the initialization fails, the OpenTelemetry provider exits. 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 business logic code. 
          ...
      }
      Table 1. Variables

      Variable

      Description

      Example

      ${service}

      The name of the service. Specify the value based on your business requirements.

      payment

      ${service.namespace}

      The namespace to which the service belongs.

      order

      ${version}

      The version of the service. We recommend that you specify a version in the va.b.c format.

      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 Simple Log Service endpoint for the region where the project resides. You can access Simple Log Service by using an internal or public endpoint. An internal endpoint can be accessed over the classic network or a virtual private cloud (VPC). A public endpoint can be accessed over the Internet. For more information, see Endpoints.

      • Port: the port number. The value is fixed as 10010.

      Note
      • If you set the variable to stdout, data is printed to standard output. In this case, the code line is provider.WithTraceExporterEndpoint("stdout").

      • If you leave the variable empty, trace data is not uploaded to Simple Log Service.

      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.

      We recommend that you use the AccessKey pair of a RAM user that has only the write permissions on the Simple Log Service project. An AccessKey pair consists of an AccessKey ID and an AccessKey secret. For more information about how to grant the write permissions on a specified project to a RAM user, see Use custom policies to grant permissions to a RAM user. For more information about how to obtain an AccessKey pair, see AccessKey pair.

      None

      ${access-key-secret}

      The AccessKey secret of your Alibaba Cloud account.

      We recommend that you use the AccessKey pair of a RAM user that has only the write permissions on the Simple Log Service project.

      None

  • Configure environment variables to initialize an OpenTelemetry provider.

    Configuration method

    Environment variable

    Required

    Description

    Default value

    WithServiceName

    SLS_OTEL_SERVICE_NAME

    Yes

    The name of the service. Specify the value based on your business requirements.

    None

    WithServiceNamespace

    SLS_OTEL_SERVICE-NAMESPACE

    No

    The namespace to which the service belongs.

    order

    WithServiceVersion

    SLS_OTEL_SERVICE_VERSION

    Yes

    The version of the service. We recommend that you specify a version in the va.b.c format.

    v0.1.0

    WithSLSConfig

    SLS_OTEL_PROJECT, SLS_OTEL_INSTANCE_ID, SLS_OTEL_ACCESS_KEY_ID, and SLS_OTEL_ACCESS_KEY_SECRET

    No

    The information about Simple Log Service resources. The information includes the name of a project, name of a trace instance, AccessKey ID of an account that has only the write-permissions on the project, and AccessKey secret of the account. For more information about how to grant the write permissions on a specified project to a RAM user, see Use custom policies to grant permissions to a RAM user. For more information about how to obtain 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 Simple Log Service endpoint for the region where the project resides. You can access Simple Log Service by using an internal or public endpoint. An internal endpoint can be accessed over the classic network or a VPC. A public endpoint can be accessed over the Internet. For more information, see Endpoints.

    • Port: the port number. The value is fixed as 10010.

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

    • If you leave the variable empty, trace data is not uploaded to Simple Log Service.

    stdout

    WithTraceExporterInsecure

    SLS_OTEL_TRACE_INSECURE

    No

    Specifies whether to transfer data by using a method that is not secure. Valid values:

    • true

    • false

    Note

    If you want to directly transfer data to Simple Log Service, you must set the variable 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 Simple Log Service endpoint for the region where the project resides. You can access Simple Log Service by using an internal or public endpoint. An internal endpoint can be accessed over the classic network or a VPC. A public endpoint can be accessed over the Internet. For more information, see Endpoints.

    • Port: the port number. The value is fixed as 10010.

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

    • If you leave the variable empty, metric data is not uploaded to Simple Log Service.

    stdout

    WithMetricExporterInsecure

    SLS_OTEL_METRIC_INSECURE

    No

    Specifies whether to transfer data by using a method that is not secure. Valid values:

    • true

    • false

    Note

    If you want to directly transfer data to Simple Log Service, you must set the variable to false.

    false

    WithResourceAttributes

    None

    No

    The additional tag information, such as the environment and zone.

    None

    WithResource

    OTEL_RESOURCE_ATTRIBUTES

    No

    The additional tag information, such as the environment and zone. Format: key1=value1,key2=value2.

    None

    WithMetricReportingPeriod

    SLS_OTEL_METRIC_EXPORT_PERIOD

    No

    The interval of reporting metric data. We recommend that you set the interval to a value from 15s to 60s.

    30s

    WithErrorHandler

    None

    No

    The error handling function. If an internal SDK error occurs, the system calls this function. This function is equivalent to the WithErrorHandlerFunc function.

    None

    WithErrorHandlerFunc

    None

    No

    The error handling function.

    None

    None

    SLS_OTEL_ATTRIBUTES_ENV_KEYS

    No

    The additional tag information, such as the environment and zone. This variable is similar to OTEL_RESOURCE_ATTRIBUTES. However, the values of attribute keys that are defined in the SLS_OTEL_ATTRIBUTES_ENV_KEYS variable are read from other environment variables.

    SLS_OTEL_ATTRIBUTES_ENV_KEYS is commonly used in Kubernetes clusters to pad some template values to specified environment variables. Format: env-key-1|env-key-2|env-key-3.

    None

Step 2: Import data

  • Semi-automatic mode: recommended

    OpenTelemetry provides automatic instrumentation solutions for various basic libraries. If your business rely on these libraries, you can use the automatic instrumentation solutions to import data. For more information about basic libraries, see Instrumentation.

    • Use the .NET or HTTP framework to import data

      The following sample code is created 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 the actual values. For more information about the variables, see Variables.

      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}"))
          // Invoke the panic() function. If the initialization fails, the OpenTelemetry provider exits. 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)
      
          // If you want to analyze metric data in the application, you can register the 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 {
                  // If you want to record some events, you can obtain the span in the context and add events. 
                  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 instrumentation solution for otel net/http, you need to only 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)
          }
      }
                                  
    • Use the Gorilla Mux framework to import data

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

      Replace the variables in the following code with the actual values. For more information about the variables, see Variables.

      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}"))
          // Invoke the panic() function. If the initialization fails, the OpenTelemetry provider exits. 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)
      
          // If you want to analyze metric data in the application, you can register the 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"
          }
          // If you want to record some events, you can obtain the span in the context and add events. 
          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 mode

    Replace the variables in the following code with the actual values. For more information about the variables, see Variables.

    // 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"))
        // Invoke the panic() function. If the initialization fails, the OpenTelemetry provider exits. 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 the label 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
    // Obtain 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 value 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 result of the span and the error information. 
    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