pkg/otel_setup.go (206 lines of code) (raw):
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// 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 pkg
import (
"context"
"errors"
"fmt"
"log"
http2 "net/http"
"os"
"runtime"
"strings"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/core/meter"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/db"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/experimental"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/http"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api-semconv/instrumenter/rpc"
testaccess "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/testaccess"
"github.com/prometheus/client_golang/prometheus/promhttp"
otelruntime "go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/otel"
_ "go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/exporters/zipkin"
otelmetric "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
// set the following environment variables based on https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables
// your service name: OTEL_SERVICE_NAME
// your otlp endpoint: OTEL_EXPORTER_OTLP_ENDPOINT OTEL_EXPORTER_OTLP_TRACES_ENDPOINT OTEL_EXPORTER_OTLP_METRICS_ENDPOINT OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
// your otlp header: OTEL_EXPORTER_OTLP_HEADERS
const exec_name = "otel"
const report_protocol = "OTEL_EXPORTER_OTLP_PROTOCOL"
const trace_report_protocol = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"
const metrics_exporter = "OTEL_METRICS_EXPORTER"
const trace_exporter = "OTEL_TRACES_EXPORTER"
const prometheus_exporter_port = "OTEL_EXPORTER_PROMETHEUS_PORT"
const default_prometheus_exporter_port = "9464"
var (
metricExporter metric.Exporter
spanExporter trace.SpanExporter
traceProvider *trace.TracerProvider
metricsProvider otelmetric.MeterProvider
batchSpanProcessor trace.SpanProcessor
)
func init() {
if testaccess.IsInTest() {
trace.GetTestSpans = testaccess.GetTestSpans
metric.GetTestMetrics = testaccess.GetTestMetrics
trace.ResetTestSpans = testaccess.ResetTestSpans
}
ctx := context.Background()
// graceful shutdown
runtime.ExitHook = func() {
gracefullyShutdown(ctx)
}
path, err := os.Executable()
if err != nil {
panic(err)
}
// skip when the executable is otel itself
if strings.HasSuffix(path, exec_name) {
return
}
if err = initOpenTelemetry(ctx); err != nil {
log.Fatalf("%s: %v", "Failed to initialize opentelemetry resource", err)
}
}
func newSpanProcessor(ctx context.Context) trace.SpanProcessor {
if testaccess.IsInTest() {
traceExporter := testaccess.GetSpanExporter()
// in test, we just send the span immediately
simpleProcessor := trace.NewSimpleSpanProcessor(traceExporter)
return simpleProcessor
} else {
var err error
if os.Getenv(trace_exporter) == "none" {
spanExporter = tracetest.NewNoopExporter()
} else if os.Getenv(trace_exporter) == "console" {
spanExporter, err = stdouttrace.New()
} else if os.Getenv(trace_exporter) == "zipkin" {
spanExporter, err = zipkin.New("")
} else {
if os.Getenv(report_protocol) == "grpc" || os.Getenv(trace_report_protocol) == "grpc" {
spanExporter, err = otlptrace.New(ctx, otlptracegrpc.NewClient())
} else {
spanExporter, err = otlptrace.New(ctx, otlptracehttp.NewClient())
}
}
if err != nil {
log.Fatalf("%s: %v", "Failed to create the OpenTelemetry trace exporter", err)
}
batchSpanProcessor = trace.NewBatchSpanProcessor(spanExporter)
return batchSpanProcessor
}
}
func initOpenTelemetry(ctx context.Context) error {
batchSpanProcessor = newSpanProcessor(ctx)
if batchSpanProcessor != nil {
traceProvider = trace.NewTracerProvider(
trace.WithSpanProcessor(batchSpanProcessor))
} else {
traceProvider = trace.NewTracerProvider()
}
otel.SetTracerProvider(traceProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return initMetrics()
}
func initMetrics() error {
ctx := context.Background()
// TODO: abstract the if-else
var err error
if testaccess.IsInTest() {
metricsProvider = metric.NewMeterProvider(
metric.WithReader(testaccess.ManualReader),
)
} else {
if os.Getenv(metrics_exporter) == "none" {
metricsProvider = noop.NewMeterProvider()
} else if os.Getenv(metrics_exporter) == "console" {
metricExporter, err = stdoutmetric.New()
metricsProvider = metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
)
} else if os.Getenv(metrics_exporter) == "prometheus" {
promExporter, err := prometheus.New()
if err != nil {
log.Fatalf("Failed to create prometheus metric exporter: %v", err)
}
metricsProvider = metric.NewMeterProvider(
metric.WithReader(promExporter),
)
go serveMetrics()
} else {
if os.Getenv(report_protocol) == "grpc" || os.Getenv(trace_report_protocol) == "grpc" {
metricExporter, err = otlpmetricgrpc.New(ctx)
metricsProvider = metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
)
} else {
metricExporter, err = otlpmetrichttp.New(ctx)
metricsProvider = metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter)),
)
}
}
}
if err != nil {
log.Fatalf("Failed to create metric exporter: %v", err)
}
if metricsProvider == nil {
return errors.New("No MeterProvider is provided")
}
otel.SetMeterProvider(metricsProvider)
m := metricsProvider.Meter("opentelemetry-global-meter")
meter.SetMeter(m)
// init http metrics
http.InitHttpMetrics(m)
// init rpc metrics
rpc.InitRpcMetrics(m)
// init db metrics
db.InitDbMetrics(m)
// nacos experimental metrics
experimental.InitNacosExperimentalMetrics(m)
// DefaultMinimumReadMemStatsInterval is 15 second
return otelruntime.Start(otelruntime.WithMeterProvider(metricsProvider))
}
func serveMetrics() {
http2.Handle("/metrics", promhttp.Handler())
port := os.Getenv(prometheus_exporter_port)
if port == "" {
port = default_prometheus_exporter_port
}
log.Printf("serving serveMetrics at localhost:%s/metrics", port)
err := http2.ListenAndServe(fmt.Sprintf(":%s", port), nil)
if err != nil {
fmt.Printf("error serving serveMetrics: %v", err)
return
}
}
func gracefullyShutdown(ctx context.Context) {
if metricsProvider != nil {
mp, ok := metricsProvider.(*metric.MeterProvider)
if ok {
if err := mp.Shutdown(ctx); err != nil {
log.Printf("%s: %v", "Failed to shutdown the OpenTelemetry metric provider", err)
}
}
}
if traceProvider != nil {
if err := traceProvider.Shutdown(ctx); err != nil {
log.Printf("%s: %v", "Failed to shutdown the OpenTelemetry trace provider", err)
}
}
if spanExporter != nil {
if err := spanExporter.Shutdown(ctx); err != nil {
log.Printf("%s: %v", "Failed to shutdown the OpenTelemetry span exporter", err)
}
}
if metricExporter != nil {
if err := metricExporter.Shutdown(ctx); err != nil {
log.Printf("%s: %v", "Failed to shutdown the OpenTelemetry metric exporter", err)
}
}
if batchSpanProcessor != nil {
if err := batchSpanProcessor.Shutdown(ctx); err != nil {
log.Printf("%s: %v", "Failed to shutdown the OpenTelemetry batch span processor", err)
}
}
}