本文介紹通過OpenTelemetry Golang SDK將Golang應用的Trace資料接入到Log Service的操作步驟。
前提條件
- 已建立Trace執行個體。更多資訊,請參見建立Trace執行個體。
已安裝Golang 1.13及以上版本的開發環境。
接入流程
初始化OpenTelemetry Provider。
判斷是否符合半自動接入條件。
如果符合,則您可以使用半自動方式接入Trace資料。
當半自動方式無法覆蓋您的所有情境時,餘下情境您需要使用手動方式接入Trace資料。
如果不符合,則您可以使用手動方式接入Trace資料。
步驟1:初始化OpenTelemetry Provider
為簡化OpenTelemetry Provider的使用,Log Service提供SLS Provider用於快速構建並上傳至Log Service的相關依賴項。
您需要在建立Traces、註冊Metrics之前,完成OpenTelemetry Provider的初始化。
您可以通過運行代碼或配置環境變數完成OpenTelemetry Provider的初始化,詳細說明如下:
通過運行程式碼完成初始化。
添加依賴項。
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 )
配置初始化代碼。
如下代碼中的變數需根據實際情況替換。關於變數的詳細說明,請參見變數說明。
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) }