1package telemetry23import (4 "net/url"5 "os"6 "strconv"7 "strings"8 "time"9)1011// OTLPConfig says where OTLP goes. ConfigFromEnv reads it from the12// standard OTEL_* variables. Nothing is sent unless an endpoint is set.13type OTLPConfig struct {14 // Endpoint is the base URL, like http://localhost:4318, to which15 // /v1/logs, /v1/traces and /v1/metrics are appended16 // (OTEL_EXPORTER_OTLP_ENDPOINT, or 012 --otlp).17 Endpoint string18 // LogsEndpoint, TracesEndpoint and MetricsEndpoint are full URLs for19 // one signal each, used as they are20 // (OTEL_EXPORTER_OTLP_{LOGS,TRACES,METRICS}_ENDPOINT).21 LogsEndpoint, TracesEndpoint, MetricsEndpoint string22 // NoLogs, NoTraces and NoMetrics leave a signal out23 // (OTEL_{LOGS,TRACES,METRICS}_EXPORTER=none).24 NoLogs, NoTraces, NoMetrics bool25 // Headers go with every request, e.g. for authentication26 // (OTEL_EXPORTER_OTLP_HEADERS). They are never logged.27 Headers map[string]string28 // Resource adds resource attributes (OTEL_RESOURCE_ATTRIBUTES);29 // service.name stays 012.30 Resource map[string]string31 // Timeout bounds each request (OTEL_EXPORTER_OTLP_TIMEOUT, in32 // milliseconds); 3 s when zero.33 Timeout time.Duration34 // NoCompression sends plain JSON instead of gzip35 // (OTEL_EXPORTER_OTLP_COMPRESSION=none).36 NoCompression bool37 // Disabled turns OTLP off whatever else is set (OTEL_SDK_DISABLED).38 Disabled bool39}4041const defaultTimeout = 3 * time.Second4243// otlpFromEnv reads the OTEL_* variables.44func otlpFromEnv() OTLPConfig {45 env := func(signal string) string {46 return os.Getenv("OTEL_EXPORTER_OTLP_" + signal + "ENDPOINT")47 }48 none := func(signal string) bool {49 return strings.EqualFold(strings.TrimSpace(os.Getenv("OTEL_"+signal+"_EXPORTER")), "none")50 }51 c := OTLPConfig{52 Endpoint: env(""),53 LogsEndpoint: env("LOGS_"),54 TracesEndpoint: env("TRACES_"),55 MetricsEndpoint: env("METRICS_"),56 NoLogs: none("LOGS"),57 NoTraces: none("TRACES"),58 NoMetrics: none("METRICS"),59 Headers: keyValues(os.Getenv("OTEL_EXPORTER_OTLP_HEADERS")),60 Resource: keyValues(os.Getenv("OTEL_RESOURCE_ATTRIBUTES")),61 NoCompression: strings.EqualFold(os.Getenv("OTEL_EXPORTER_OTLP_COMPRESSION"), "none"),62 Disabled: strings.EqualFold(strings.TrimSpace(os.Getenv("OTEL_SDK_DISABLED")), "true"),63 }64 if ms, err := strconv.Atoi(os.Getenv("OTEL_EXPORTER_OTLP_TIMEOUT")); err == nil && ms > 0 {65 c.Timeout = time.Duration(ms) * time.Millisecond66 }67 return c68}6970// keyValues parses the "k1=v1,k2=v2" lists of OTEL_EXPORTER_OTLP_HEADERS71// and OTEL_RESOURCE_ATTRIBUTES, whose values are percent-encoded.72func keyValues(s string) map[string]string {73 var m map[string]string74 for kv := range strings.SplitSeq(s, ",") {75 k, v, ok := strings.Cut(kv, "=")76 k = strings.TrimSpace(k)77 if !ok || k == "" {78 continue79 }80 if d, err := url.PathUnescape(strings.TrimSpace(v)); err == nil {81 v = d82 }83 if m == nil {84 m = map[string]string{}85 }86 m[k] = strings.TrimSpace(v)87 }88 return m89}9091// urls are where each signal goes; empty leaves it out.92type urls struct{ logs, traces, metrics string }9394func (u urls) any() bool { return u.logs != "" || u.traces != "" || u.metrics != "" }9596func (c OTLPConfig) urls() urls {97 if c.Disabled {98 return urls{}99 }100 base := c.Endpoint101 if base != "" && !strings.Contains(base, "://") {102 base = "http://" + base103 }104 pick := func(own, path string, off bool) string {105 switch {106 case off:107 return ""108 case own != "":109 return own110 case base != "":111 return strings.TrimRight(base, "/") + path112 }113 return ""114 }115 return urls{116 logs: pick(c.LogsEndpoint, "/v1/logs", c.NoLogs),117 traces: pick(c.TracesEndpoint, "/v1/traces", c.NoTraces),118 metrics: pick(c.MetricsEndpoint, "/v1/metrics", c.NoMetrics),119 }120}