Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ linters:
alias: digest
- pkg: github.com/opencontainers/image-spec/specs-go/v1
alias: ocispec
- pkg: go.opentelemetry.io/otel/semconv/v1.24.0
alias: semconv
- pkg: go.opentelemetry.io/otel/sdk/trace
alias: sdktrace
- pkg: k8s.io/apimachinery/pkg/util/version
alias: utilversion
- pkg: github.com/hashicorp/golang-lru/v2
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- [#1062](https://github.com/spegel-org/spegel/pull/1062) Add OpenTelemetry tracing support with OTEL configuration (endpoint, insecure, serviceName, sampler) and trace correlation (trace_id/span_id) in structured logs.
- [#1056](https://github.com/spegel-org/spegel/pull/1056) Send delete events for content by keeping content index.

### Changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Spegel is for you if you are looking to do any of the following.
* Avoid rate-limiting when pulling images from external registries (e.g. Docker Hub).
* Decrease egressing traffic outside of the clusters network.
* Increase image pull efficiency in edge node deployments.
* OpenTelemetry tracing.

## Getting Started

Expand Down
5 changes: 5 additions & 0 deletions charts/spegel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ Read the [getting started](https://spegel.dev/docs/getting-started/) guide to de
| spegel.mirrorResolveRetries | int | `3` | Max amount of mirrors to attempt. |
| spegel.mirrorResolveTimeout | string | `"20ms"` | Max duration spent finding a mirror. |
| spegel.mirroredRegistries | list | `[]` | Registries for which mirror configuration will be created. Empty means all registires will be mirrored. |
| spegel.otel | object | `{"endpoint":"","insecure":false,"sampler":"parentbased_always_off","serviceName":"spegel"}` | OTEL tracing configuration. |
| spegel.otel.endpoint | string | `""` | OTEL exporter endpoint (e.g., otel-collector:4318). |
| spegel.otel.insecure | bool | `false` | Use insecure connection for OTEL exporter. |
| spegel.otel.sampler | string | `"parentbased_always_off"` | Trace sampler (always_on, always_off, parentbased_always_on, parentbased_always_off, or a ratio 0.0-1.0). |
| spegel.otel.serviceName | string | `"spegel"` | Service name for OTEL traces. |
| spegel.prependExisting | bool | `false` | When true existing mirror configuration will be kept and Spegel will prepend it's configuration. |
| spegel.registryFilters | list | `[]` | Regular expressions to filter out tags/registries. If empty, all registries/tags are resolved. |
| spegel.resolveTags | bool | `true` | When true Spegel will resolve tags to digests. |
Expand Down
8 changes: 8 additions & 0 deletions charts/spegel/templates/daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ spec:
- --containerd-content-path={{ . }}
{{- end }}
- --debug-web-enabled={{ .Values.spegel.debugWebEnabled }}
{{- if .Values.spegel.otel.endpoint }}
- --otel-endpoint={{ .Values.spegel.otel.endpoint }}
{{- end }}
{{- if .Values.spegel.otel.insecure }}
- --otel-insecure
{{- end }}
- --otel-service-name={{ .Values.spegel.otel.serviceName }}
- --otel-sampler={{ .Values.spegel.otel.sampler }}
env:
- name: DATA_DIR
value: ""
Expand Down
10 changes: 10 additions & 0 deletions charts/spegel/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ spegel:
prependExisting: false
# -- When true enables debug web page.
debugWebEnabled: true
# -- OTEL tracing configuration.
otel:
# -- OTEL exporter endpoint (e.g., otel-collector:4318).
endpoint: ""
# -- Use insecure connection for OTEL exporter.
insecure: false
# -- Service name for OTEL traces.
serviceName: "spegel"
# -- Trace sampler (always_on, always_off, parentbased_always_on, parentbased_always_off, or a ratio 0.0-1.0).
sampler: "parentbased_always_off"

verticalPodAutoscaler:
# -- If true creates a Vertical Pod Autoscaler.
Expand Down
19 changes: 13 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4
github.com/prometheus/client_golang v1.23.2
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0
go.opentelemetry.io/otel v1.39.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0
go.opentelemetry.io/otel/sdk v1.39.0
go.opentelemetry.io/otel/trace v1.39.0
golang.org/x/sync v0.19.0
)

Expand All @@ -41,6 +46,7 @@ require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/cgroups/v3 v3.1.2 // indirect
github.com/containerd/continuity v0.4.5 // indirect
Expand All @@ -67,6 +73,7 @@ require (
github.com/google/gopacket v1.1.19 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/guillaumemichel/reservedpool v0.3.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
Expand Down Expand Up @@ -167,10 +174,9 @@ require (
github.com/wlynxg/anet v0.0.5 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/dig v1.19.0 // indirect
go.uber.org/fx v1.24.0 // indirect
go.uber.org/mock v0.5.2 // indirect
Expand All @@ -187,9 +193,10 @@ require (
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.40.0 // indirect
gonum.org/v1/gonum v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v3 v3.18.5 // indirect
Expand Down
44 changes: 28 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down Expand Up @@ -159,6 +161,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRid
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/guillaumemichel/reservedpool v0.3.0 h1:eqqO/QvTllLBrit7LVtVJBqw4cD0WdV9ajUe7WNTajw=
github.com/guillaumemichel/reservedpool v0.3.0/go.mod h1:sXSDIaef81TFdAJglsCFCMfgF5E5Z5xK1tFhjDhvbUc=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
Expand Down Expand Up @@ -447,18 +451,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
Expand Down Expand Up @@ -592,8 +602,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
Expand All @@ -610,8 +622,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
168 changes: 168 additions & 0 deletions internal/otelx/otelx_otel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package otelx

import (
"context"
"fmt"
"os"
"reflect"
"strconv"
"time"

"github.com/go-logr/logr"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"go.opentelemetry.io/otel/trace"
)

const (
defaultServiceName = "spegel"
defaultSampler = "parentbased_always_off"
defaultEndpoint = "localhost:4318"
)

// Shutdown is a function type for shutting down the OTEL SDK.
type Shutdown func(context.Context) error

// Config holds OTEL configuration.
type Config struct {
ServiceName string
Endpoint string
Sampler string
Insecure bool
}

// Setup initializes the OpenTelemetry SDK.
func Setup(ctx context.Context, cfg Config) (Shutdown, error) {
log := logr.FromContextOrDiscard(ctx)

explicitConfig := isExplicitConfig(cfg)
if !explicitConfig && !isNoopTracerProvider(otel.GetTracerProvider()) {
log.Info("skipping OTEL setup; tracer provider already configured")
return nil, nil
}
if explicitConfig && !isNoopTracerProvider(otel.GetTracerProvider()) {
log.Info("overriding existing OTEL tracer provider due to explicit config")
}

// Use cfg if provided, otherwise fall back to environment
if cfg.Endpoint == "" {
cfg.Endpoint = getEnv("OTEL_EXPORTER_OTLP_ENDPOINT", defaultEndpoint)
}
if cfg.ServiceName == "" {
cfg.ServiceName = getEnv("OTEL_SERVICE_NAME", defaultServiceName)
}
if cfg.Sampler == "" {
cfg.Sampler = getEnv("OTEL_TRACES_SAMPLER", defaultSampler)
}

log.Info("initializing OTEL", "service", cfg.ServiceName, "endpoint", cfg.Endpoint, "sampler", cfg.Sampler)

opts := []otlptracehttp.Option{
otlptracehttp.WithEndpoint(cfg.Endpoint),
}
if cfg.Insecure {
opts = append(opts, otlptracehttp.WithInsecure())
}

exporter, err := otlptracehttp.New(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("failed to create OTLP exporter: %w", err)
}

res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName(cfg.ServiceName),
),
)
if err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}

tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(newSampler(cfg.Sampler)),
)

otel.SetTracerProvider(tp)

defaultPropagator := propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
if reflect.DeepEqual(otel.GetTextMapPropagator(), defaultPropagator) {
otel.SetTextMapPropagator(defaultPropagator)
} else {
log.Info("keeping existing OTEL propagator")
}

shutdownFn := func(ctx context.Context) error {
if tp != nil {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return tp.Shutdown(ctx)
}
return nil
}

return shutdownFn, nil
}

func isNoopTracerProvider(tp trace.TracerProvider) bool {
tracer := tp.Tracer("spegel-otel-probe")
_, span := tracer.Start(context.Background(), "otel-probe")
spanCtx := span.SpanContext()
span.End()
return !spanCtx.IsValid()
}

func isExplicitConfig(cfg Config) bool {
if cfg.Endpoint != "" || cfg.Insecure {
return true
}
if cfg.ServiceName != "" && cfg.ServiceName != defaultServiceName {
return true
}
if cfg.Sampler != "" && cfg.Sampler != defaultSampler {
return true
}
return false
}

// SetupWithDefaults is a convenience function that accepts a service name.
func SetupWithDefaults(ctx context.Context, serviceName string) (Shutdown, error) {
return Setup(ctx, Config{ServiceName: serviceName})
}

// getEnv returns an environment variable value or a default.
func getEnv(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultValue
}

// newSampler creates a trace sampler based on the provided string.
func newSampler(samplerType string) sdktrace.Sampler {
switch samplerType {
case "always_on":
return sdktrace.AlwaysSample()
case "always_off":
return sdktrace.NeverSample()
case "parentbased_always_on":
return sdktrace.ParentBased(sdktrace.AlwaysSample())
case "parentbased_always_off":
return sdktrace.ParentBased(sdktrace.NeverSample())
default:
// Try to parse as a ratio
if ratio, err := strconv.ParseFloat(samplerType, 64); err == nil && ratio >= 0 && ratio <= 1 {
return sdktrace.TraceIDRatioBased(ratio)
}
// Default to parentbased_always_off
return sdktrace.ParentBased(sdktrace.NeverSample())
}
}
Loading
Loading