package telemetry import ( "context" "fmt" "log/slog" "os" "runtime" "git.kmsign.ru/royalcat/tstor/pkg/rlog" "github.com/agoda-com/opentelemetry-go/otelslog" "github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs" "github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs/otlplogshttp" logsdk "github.com/agoda-com/opentelemetry-logs-go/sdk/logs" otelpyroscope "github.com/grafana/otel-profiling-go" "github.com/grafana/pyroscope-go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" ) type Client struct { log *slog.Logger tracerProvider *trace.TracerProvider metricProvider *metric.MeterProvider loggerProvider *logsdk.LoggerProvider } func (client *Client) Shutdown(ctx context.Context) { log := rlog.FunctionLog(client.log, "Shutdown") if client.metricProvider == nil { err := client.metricProvider.Shutdown(ctx) if err != nil { log.Error("error shutting down metric provider", rlog.Err(err)) } } if client.tracerProvider == nil { err := client.tracerProvider.Shutdown(ctx) if err != nil { log.Error("error shutting down tracer provider", rlog.Err(err)) } } if client.loggerProvider == nil { err := client.loggerProvider.Shutdown(ctx) if err != nil { log.Error("error shutting down logger provider", rlog.Err(err)) } } } const appName = "tstor" func Setup(ctx context.Context, endpoint string) (*Client, error) { log := rlog.ComponentLog("telemetry") client := &Client{ log: log, } otel.SetErrorHandler(otel.ErrorHandlerFunc(func(cause error) { log.Error("otel error", rlog.Err(cause)) })) hostName, _ := os.Hostname() r, err := resource.Merge( resource.Default(), resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceName(appName), semconv.HostName(hostName), ), ) if err != nil { return nil, err } metricExporter, err := prometheus.New(prometheus.WithNamespace(appName)) if err != nil { return nil, err } client.metricProvider = metric.NewMeterProvider( metric.WithReader(metricExporter), metric.WithResource(r), ) otel.SetMeterProvider(client.metricProvider) log.Info("prometheus metrics provider initialized") traceExporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithRetry(otlptracehttp.RetryConfig{ Enabled: false, }), ) if err != nil { return nil, err } client.tracerProvider = trace.NewTracerProvider( trace.WithBatcher(traceExporter), trace.WithResource(r), ) // otel.SetTracerProvider(client.tracerProvider) otel.SetTracerProvider(otelpyroscope.NewTracerProvider(client.tracerProvider)) log.Info("otel tracing provider initialized") logExporter, err := otlplogs.NewExporter(ctx, otlplogs.WithClient( otlplogshttp.NewClient(otlplogshttp.WithEndpoint(endpoint)), ), ) if err != nil { return nil, err } client.loggerProvider = logsdk.NewLoggerProvider( logsdk.WithBatcher(logExporter), logsdk.WithResource(r), ) rlog.AddHandler(otelslog.NewOtelHandler(client.loggerProvider, &otelslog.HandlerOptions{ Level: slog.LevelDebug, }), ) client.log = slog.Default() runtime.SetMutexProfileFraction(5) runtime.SetBlockProfileRate(5) _, err = pyroscope.Start(pyroscope.Config{ ApplicationName: appName, // replace this with the address of pyroscope server ServerAddress: "https://pyroscope.kmsign.ru", // you can disable logging by setting this to nil Logger: &pyroscopeLogger{ log: rlog.ComponentLog("metrics.pyroscope"), }, ProfileTypes: []pyroscope.ProfileType{ // these profile types are enabled by default: pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, pyroscope.ProfileAllocSpace, pyroscope.ProfileInuseObjects, pyroscope.ProfileInuseSpace, // these profile types are optional: // pyroscope.ProfileGoroutines, // pyroscope.ProfileMutexCount, // pyroscope.ProfileMutexDuration, // pyroscope.ProfileBlockCount, // pyroscope.ProfileBlockDuration, }, }) if err != nil { return client, nil } return client, nil } type pyroscopeLogger struct { log *slog.Logger } var _ pyroscope.Logger = (*pyroscopeLogger)(nil) // Debugf implements pyroscope.Logger. func (p *pyroscopeLogger) Debugf(msg string, args ...any) { p.log.Debug(fmt.Sprintf(msg, args...)) } // Errorf implements pyroscope.Logger. func (p *pyroscopeLogger) Errorf(msg string, args ...any) { p.log.Error(fmt.Sprintf(msg, args...)) } // Infof implements pyroscope.Logger. func (p *pyroscopeLogger) Infof(msg string, args ...any) { p.log.Info(fmt.Sprintf(msg, args...)) }