Ingest Golang trace data using OpenTelemetry
Use the OpenTelemetry Golang SDK to ingest trace data from a Golang application into Simple Log Service.
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
-
Initialize the OpenTelemetry provider.
-
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.
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
-
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 ) -
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.
NoteWhen 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.
NoteWhen 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) }