全部產品
Search
文件中心

Simple Log Service:通過OpenTelemetry接入Golang Trace資料

更新時間:Jul 21, 2024

本文介紹通過OpenTelemetry Golang SDK將Golang應用的Trace資料接入到Log Service的操作步驟。

前提條件

  • 已建立Trace執行個體。更多資訊,請參見建立Trace執行個體
  • 已安裝Golang 1.13及以上版本的開發環境。

接入流程

  1. 初始化OpenTelemetry Provider。

  2. 判斷是否符合半自動接入條件。

    • 如果符合,則您可以使用半自動方式接入Trace資料。

      當半自動方式無法覆蓋您的所有情境時,餘下情境您需要使用手動方式接入Trace資料。

    • 如果不符合,則您可以使用手動方式接入Trace資料。

步驟1:初始化OpenTelemetry Provider

為簡化OpenTelemetry Provider的使用,Log Service提供SLS Provider用於快速構建並上傳至Log Service的相關依賴項。

重要

您需要在建立Traces、註冊Metrics之前,完成OpenTelemetry Provider的初始化。

您可以通過運行代碼或配置環境變數完成OpenTelemetry Provider的初始化,詳細說明如下:

  • 通過運行程式碼完成初始化。

    1. 添加依賴項。

      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. 配置初始化代碼。

      如下代碼中的變數需根據實際情況替換。關於變數的詳細說明,請參見變數說明

      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}"))
          // 使用panic(),表示如果初始化失敗則程式直接異常退出,您也可以使用其他錯誤處理方式。
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
          
          // 添加商務邏輯代碼。
          ...
      }

      表 1. 變數說明

      變數

      說明

      樣本

      ${service}

      服務名。根據您的實際情境取值。

      payment

      ${service.namespace}

      服務歸屬的命名空間。

      order

      ${version}

      服務版本號碼。建議按照va.b.c格式定義。

      v0.1.2

      ${endpoint}

      Log ServiceProject的接入地址,格式為${project}.${region-endpoint}:Port,其中:

      • ${project}:Log ServiceProject名稱。

      • ${region-endpoint}:Log ServiceProject所在地區的訪問網域名稱,支援公網和阿里雲內網(傳統網路、VPC)。更多資訊,請參見服務入口

      • Port:網路連接埠,固定為10010。

      說明
      • 如果配置為stdout,即provider.WithTraceExporterEndpoint("stdout"),表示將資料列印到標準輸出。

      • 如果配置為空白值,表示不上傳Trace資料到Log Service。

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

      ${project}

      Log ServiceProject名稱。

      test-project

      ${instance}

      Trace服務執行個體ID。更多資訊,請參見建立Trace執行個體

      test-traces

      ${access-key-id}

      阿里雲帳號AccessKey ID。

      建議您使用只具備Log ServiceProject寫入許可權的RAM使用者的AccessKey(包括AccessKey ID和AccessKey Secret)。授予RAM使用者向指定Project寫入資料許可權的具體操作,請參見授權。如何擷取AccessKey的具體操作,請參見存取金鑰

      ${access-key-secret}

      阿里雲帳號AccessKey Secret。

      建議您使用只具備Log ServiceProject寫入許可權的RAM使用者的AccessKey。

  • 通過配置環境變數完成初始化。

    配置方法

    環境變數

    是否必選

    說明

    預設值

    WithServiceName

    SLS_OTEL_SERVICE_NAME

    服務名。根據您的實際情境取值即可。

    WithServiceNamespace

    SLS_OTEL_SERVICE-NAMESPACE

    服務歸屬的命名空間。

    order

    WithServiceVersion

    SLS_OTEL_SERVICE_VERSION

    服務版本號碼。建議按照va.b.c格式定義。

    v0.1.0

    WithSLSConfig

    SLS_OTEL_PROJECT、SLS_OTEL_INSTANCE_ID、SLS_OTEL_ACCESS_KEY_ID、SLS_OTEL_ACCESS_KEY_SECRET

    Log Service資源資訊,包括Project名稱、Trace執行個體名稱、具備Project唯寫許可權的AccessKey ID和AccessKey Secret。授予RAM使用者向指定Project寫入資料許可權的具體操作,請參見授權。如何擷取AccessKey的具體操作,請參見存取金鑰

    WithTraceExporterEndpoint

    SLS_OTEL_TRACE_ENDPOINT

    Log ServiceProject的接入地址,格式為${project}.${region-endpoint}:Port,其中:

    • ${project}:Log ServiceProject名稱。

    • ${region-endpoint}:Log ServiceProject所在地區的訪問網域名稱,支援內網和公網訪問。更多資訊,請參見服務入口

    • Port:網路連接埠,固定為10010。

    說明
    • 如果配置為stdout,表示將資料列印到標準輸出。

    • 如果配置為空白值,表示不上傳Trace資料到Log Service。

    stdout

    WithTraceExporterInsecure

    SLS_OTEL_TRACE_INSECURE

    是否使用非安全方式傳輸。

    • true:使用非安全方式傳輸。

    • false:使用安全方式傳輸。

    說明

    如果直接傳輸到Log Service,則必須配置為false

    false

    WithMetricExporterEndpoint

    SLS_OTEL_METRIC_ENDPOINT

    Log ServiceProject的接入地址,格式為${project}.${region-endpoint}:Port,其中:

    • ${project}:Log ServiceProject名稱。

    • ${region-endpoint}:Log ServiceProject所在地區的訪問網域名稱,支援內網和公網訪問。更多資訊,請參見服務入口

    • Port:網路連接埠,固定為10010。

    說明
    • 如果配置為stdout,表示將資料列印到標準輸出。

    • 如果配置為空白值,表示不上傳時序資料到Log Service。

    stdout

    WithMetricExporterInsecure

    SLS_OTEL_METRIC_INSECURE

    是否使用非安全方式傳輸。

    • true:使用非安全方式傳輸。

    • false:使用安全方式傳輸。

    說明

    如果直接傳輸到Log Service,則必須配置為false

    false

    WithResourceAttributes

    配置附加的Tag資訊,例如環境、可用性區域等資訊。

    WithResource

    OTEL_RESOURCE_ATTRIBUTES

    配置附加的Tag資訊,例如環境、可用性區域等資訊。配置格式為key1=value1,key2=value2

    WithMetricReportingPeriod

    SLS_OTEL_METRIC_EXPORT_PERIOD

    Metric輸出間隔,建議設定區間為15s~60s。

    30s

    WithErrorHandler

    錯誤處理函數。在SDK內部出錯時,會調用該函數,作用等同於WithErrorHandlerFunc。

    WithErrorHandlerFunc

    錯誤處理函數。

    SLS_OTEL_ATTRIBUTES_ENV_KEYS

    配置附加的Tag資訊,例如環境、可用性區域等資訊。類似於OTEL_RESOURCE_ATTRIBUTES。區別在於SLS_OTEL_ATTRIBUTES_ENV_KEYS定義的Attribute Key,其實際值從對應的環境變數中讀取。

    常用於K8s情境中將部分模板值填充到特定的環境變數中。配置格式為env-key-1|env-key-2|env-key-3

步驟2:接入資料

  • (推薦)半自動接入

    OpenTelemetry提供眾多基礎庫的自動埋點方案,如果您的業務依賴於這些基礎庫,則可以使用這些基礎庫的自動埋點方案來接入資料。關於基礎庫的更多資訊,請參見Golang自動埋點方案

    • net、http接入

      如下樣本基於go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.37.0版本建立。更多資訊,請參見otel-http-example

      如下代碼中的變數需根據實際情況替換。關於變數的詳細說明,請參見變數說明

      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}"))
          // 使用panic(),表示如果初始化失敗則程式直接異常退出,您也可以使用其他錯誤處理方式。
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
      
          // 如果您需要分析應用中的指標資料,可以註冊相關的Metric指標。
          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 {
                  // 如果您需要記錄一些事件,可以擷取Context中的Span並添加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...)
              }
          }
      
          // 使用otel net/http的自動注入方式,只需要使用otelhttp.NewHandler包裹http.Handler。
          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接入

      下述樣本基於go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.37.0 版本建立,新版本中介面可能出現一定的改動,最新樣本請參見otel-mux-example

      如下代碼中的變數需根據實際情況替換。關於變數的詳細說明,請參見變數說明

      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}"))
          // 使用panic(),表示如果初始化失敗則程式直接異常退出,您也可以使用其他錯誤處理方式。
          if err != nil {
              panic(err)
          }
          if err := provider.Start(slsConfig); err != nil {
              panic(err)
          }
          defer provider.Shutdown(slsConfig)
      
          // 如果您需要分析應用中的指標資料,可以註冊相關的Metric指標。
          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"
          }
          // 如果您需要記錄一些事件,可以擷取Context中的Span並添加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"
      }
                                  
  • 手動接入

    如下代碼中的變數需根據實際情況替換。關於變數的詳細說明,請參見變數說明

    // 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"))
        // 使用panic(),表示如果初始化失敗則程式直接異常退出,您也可以使用其他錯誤處理方式。
        if err != nil {
            panic(err)
        }
        if err := provider.Start(slsConfig); err != nil {
            panic(err)
        }
        defer provider.Shutdown(slsConfig)
    
        mockTrace()
        mockMetrics()
    }
    
    func mockMetrics() {
        // 附加標籤資訊。
        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
    // 擷取當前的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
    // 向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
    // 向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
    // 記錄Span結果以及錯誤資訊。
    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
    // 建立子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)
    }
                        

後續步驟