mirror of
https://github.com/hanzoai/traces.git
synced 2026-11-03 04:05:45 +00:00
42028c9bba
Bumps the opentelemetry-contrib group with 1 update: [go.opentelemetry.io/contrib/exporters/autoexport](https://github.com/open-telemetry/opentelemetry-go-contrib). Updates `go.opentelemetry.io/contrib/exporters/autoexport` from 0.62.0 to 0.63.0 - [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.62.0...zpages/v0.63.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/contrib/exporters/autoexport dependency-version: 0.63.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: opentelemetry-contrib ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
300 lines
9.9 KiB
Go
300 lines
9.9 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package autoexport // import "go.opentelemetry.io/contrib/exporters/autoexport"
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
|
|
promexporter "go.opentelemetry.io/otel/exporters/prometheus"
|
|
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
|
|
"go.opentelemetry.io/otel/sdk/metric"
|
|
|
|
prometheusbridge "go.opentelemetry.io/contrib/bridges/prometheus"
|
|
)
|
|
|
|
const otelExporterOTLPMetricsProtoEnvKey = "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL"
|
|
|
|
// MetricOption applies an autoexport configuration option.
|
|
type MetricOption = option[metric.Reader]
|
|
|
|
// WithFallbackMetricReader sets the fallback exporter to use when no exporter
|
|
// is configured through the OTEL_METRICS_EXPORTER environment variable.
|
|
func WithFallbackMetricReader(metricReaderFactory func(ctx context.Context) (metric.Reader, error)) MetricOption {
|
|
return withFallbackFactory[metric.Reader](metricReaderFactory)
|
|
}
|
|
|
|
// NewMetricReader returns a configured [go.opentelemetry.io/otel/sdk/metric.Reader]
|
|
// defined using the environment variables described below.
|
|
//
|
|
// OTEL_METRICS_EXPORTER defines the metrics exporter; supported values:
|
|
// - "none" - "no operation" exporter
|
|
// - "otlp" (default) - OTLP exporter; see [go.opentelemetry.io/otel/exporters/otlp/otlpmetric]
|
|
// - "prometheus" - Prometheus exporter + HTTP server; see [go.opentelemetry.io/otel/exporters/prometheus]
|
|
// - "console" - Standard output exporter; see [go.opentelemetry.io/otel/exporters/stdout/stdoutmetric]
|
|
//
|
|
// OTEL_EXPORTER_OTLP_PROTOCOL defines OTLP exporter's transport protocol;
|
|
// supported values:
|
|
// - "grpc" - protobuf-encoded data using gRPC wire format over HTTP/2 connection;
|
|
// see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc]
|
|
// - "http/protobuf" (default) - protobuf-encoded data over HTTP connection;
|
|
// see: [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp]
|
|
//
|
|
// OTEL_EXPORTER_OTLP_METRICS_PROTOCOL defines OTLP exporter's transport protocol for the metrics signal;
|
|
// supported values are the same as OTEL_EXPORTER_OTLP_PROTOCOL.
|
|
//
|
|
// OTEL_EXPORTER_PROMETHEUS_HOST (defaulting to "localhost") and
|
|
// OTEL_EXPORTER_PROMETHEUS_PORT (defaulting to 9464) define the host and port for the
|
|
// Prometheus exporter's HTTP server.
|
|
//
|
|
// Experimental: OTEL_METRICS_PRODUCERS can be used to configure metric producers.
|
|
// supported values: prometheus, none. Multiple values can be specified separated by commas.
|
|
//
|
|
// An error is returned if an environment value is set to an unhandled value.
|
|
//
|
|
// Use [RegisterMetricReader] to handle more values of OTEL_METRICS_EXPORTER.
|
|
// Use [RegisterMetricProducer] to handle more values of OTEL_METRICS_PRODUCERS.
|
|
//
|
|
// Use [WithFallbackMetricReader] option to change the returned exporter
|
|
// when OTEL_METRICS_EXPORTER is unset or empty.
|
|
//
|
|
// Use [IsNoneMetricReader] to check if the returned exporter is a "no operation" exporter.
|
|
func NewMetricReader(ctx context.Context, opts ...MetricOption) (metric.Reader, error) {
|
|
return metricsSignal.create(ctx, opts...)
|
|
}
|
|
|
|
// RegisterMetricReader sets the MetricReader factory to be used when the
|
|
// OTEL_METRICS_EXPORTERS environment variable contains the exporter name. This
|
|
// will panic if name has already been registered.
|
|
func RegisterMetricReader(name string, factory func(context.Context) (metric.Reader, error)) {
|
|
must(metricsSignal.registry.store(name, factory))
|
|
}
|
|
|
|
// RegisterMetricProducer sets the MetricReader factory to be used when the
|
|
// OTEL_METRICS_PRODUCERS environment variable contains the producer name. This
|
|
// will panic if name has already been registered.
|
|
func RegisterMetricProducer(name string, factory func(context.Context) (metric.Producer, error)) {
|
|
must(metricsProducers.registry.store(name, factory))
|
|
}
|
|
|
|
// WithFallbackMetricProducer sets the fallback producer to use when no producer
|
|
// is configured through the OTEL_METRICS_PRODUCERS environment variable.
|
|
func WithFallbackMetricProducer(producerFactory func(ctx context.Context) (metric.Producer, error)) {
|
|
metricsProducers.fallbackProducer = producerFactory
|
|
}
|
|
|
|
var (
|
|
metricsSignal = newSignal[metric.Reader]("OTEL_METRICS_EXPORTER")
|
|
metricsProducers = newProducerRegistry("OTEL_METRICS_PRODUCERS")
|
|
)
|
|
|
|
func init() {
|
|
RegisterMetricReader("otlp", func(ctx context.Context) (metric.Reader, error) {
|
|
producers, err := metricsProducers.create(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
readerOpts := []metric.PeriodicReaderOption{}
|
|
for _, producer := range producers {
|
|
readerOpts = append(readerOpts, metric.WithProducer(producer))
|
|
}
|
|
|
|
proto := os.Getenv(otelExporterOTLPMetricsProtoEnvKey)
|
|
if proto == "" {
|
|
proto = os.Getenv(otelExporterOTLPProtoEnvKey)
|
|
}
|
|
|
|
// Fallback to default, http/protobuf.
|
|
if proto == "" {
|
|
proto = "http/protobuf"
|
|
}
|
|
|
|
switch proto {
|
|
case "grpc":
|
|
r, err := otlpmetricgrpc.New(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return metric.NewPeriodicReader(r, readerOpts...), nil
|
|
case "http/protobuf":
|
|
r, err := otlpmetrichttp.New(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return metric.NewPeriodicReader(r, readerOpts...), nil
|
|
default:
|
|
return nil, errInvalidOTLPProtocol
|
|
}
|
|
})
|
|
RegisterMetricReader("console", func(ctx context.Context) (metric.Reader, error) {
|
|
producers, err := metricsProducers.create(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
readerOpts := []metric.PeriodicReaderOption{}
|
|
for _, producer := range producers {
|
|
readerOpts = append(readerOpts, metric.WithProducer(producer))
|
|
}
|
|
|
|
r, err := stdoutmetric.New()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return metric.NewPeriodicReader(r, readerOpts...), nil
|
|
})
|
|
RegisterMetricReader("none", func(context.Context) (metric.Reader, error) {
|
|
return newNoopMetricReader(), nil
|
|
})
|
|
RegisterMetricReader("prometheus", func(ctx context.Context) (metric.Reader, error) {
|
|
// create an isolated registry instead of using the global registry --
|
|
// the user might not want to mix OTel with non-OTel metrics.
|
|
// Those that want to comingle metrics from global registry can use
|
|
// OTEL_METRICS_PRODUCERS=prometheus
|
|
reg := prometheus.NewRegistry()
|
|
|
|
exporterOpts := []promexporter.Option{promexporter.WithRegisterer(reg)}
|
|
|
|
producers, err := metricsProducers.create(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, producer := range producers {
|
|
exporterOpts = append(exporterOpts, promexporter.WithProducer(producer))
|
|
}
|
|
|
|
reader, err := promexporter.New(exporterOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
|
|
server := http.Server{
|
|
// Timeouts are necessary to make a server resilient to attacks, but ListenAndServe doesn't set any.
|
|
// We use values from this example: https://blog.cloudflare.com/exposing-go-on-the-internet/#:~:text=There%20are%20three%20main%20timeouts
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
Handler: mux,
|
|
}
|
|
|
|
// environment variable names and defaults specified at https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#prometheus-exporter
|
|
host := getenv("OTEL_EXPORTER_PROMETHEUS_HOST", "localhost")
|
|
port := getenv("OTEL_EXPORTER_PROMETHEUS_PORT", "9464")
|
|
addr := host + ":" + port
|
|
lis, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return nil, errors.Join(
|
|
fmt.Errorf("binding address %s for Prometheus exporter: %w", addr, err),
|
|
reader.Shutdown(ctx),
|
|
)
|
|
}
|
|
|
|
go func() {
|
|
if err := server.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
otel.Handle(fmt.Errorf("the Prometheus HTTP server exited unexpectedly: %w", err))
|
|
}
|
|
}()
|
|
|
|
return readerWithServer{lis.Addr(), reader, &server}, nil
|
|
})
|
|
|
|
RegisterMetricProducer("prometheus", func(context.Context) (metric.Producer, error) {
|
|
return prometheusbridge.NewMetricProducer(), nil
|
|
})
|
|
RegisterMetricProducer("none", func(context.Context) (metric.Producer, error) {
|
|
return newNoopMetricProducer(), nil
|
|
})
|
|
}
|
|
|
|
type readerWithServer struct {
|
|
addr net.Addr
|
|
metric.Reader
|
|
server *http.Server
|
|
}
|
|
|
|
func (rws readerWithServer) Shutdown(ctx context.Context) error {
|
|
return errors.Join(
|
|
rws.Reader.Shutdown(ctx),
|
|
rws.server.Shutdown(ctx),
|
|
)
|
|
}
|
|
|
|
func getenv(key, fallback string) string {
|
|
result, ok := os.LookupEnv(key)
|
|
if !ok {
|
|
return fallback
|
|
}
|
|
return result
|
|
}
|
|
|
|
type producerRegistry struct {
|
|
envKey string
|
|
fallbackProducer func(context.Context) (metric.Producer, error)
|
|
registry *registry[metric.Producer]
|
|
}
|
|
|
|
func newProducerRegistry(envKey string) producerRegistry {
|
|
return producerRegistry{
|
|
envKey: envKey,
|
|
registry: ®istry[metric.Producer]{
|
|
names: make(map[string]func(context.Context) (metric.Producer, error)),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (pr producerRegistry) create(ctx context.Context) ([]metric.Producer, error) {
|
|
expType := os.Getenv(pr.envKey)
|
|
if expType == "" {
|
|
if pr.fallbackProducer != nil {
|
|
producer, err := pr.fallbackProducer(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []metric.Producer{producer}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
producers := dedupedMetricProducers(expType)
|
|
metricProducers := make([]metric.Producer, 0, len(producers))
|
|
for _, producer := range producers {
|
|
producer, err := pr.registry.load(ctx, producer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
metricProducers = append(metricProducers, producer)
|
|
}
|
|
|
|
return metricProducers, nil
|
|
}
|
|
|
|
func dedupedMetricProducers(envValue string) []string {
|
|
producers := make(map[string]struct{})
|
|
for _, producer := range strings.Split(envValue, ",") {
|
|
producers[producer] = struct{}{}
|
|
}
|
|
|
|
result := make([]string, 0, len(producers))
|
|
for producer := range producers {
|
|
result = append(result, producer)
|
|
}
|
|
|
|
return result
|
|
}
|