From 7d78368974e11eed920cb079f61d12662d2903e4 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Fri, 29 May 2026 14:32:09 +0200 Subject: [PATCH 01/10] chore: simplify, modernize (Go 1.26), update deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix discarded NewTracingInterceptor error (nil → panic); eliminate func-type indirection by building the interceptor once in Init - Rename local `resource` variable to `res` to remove package shadow - Drop explicit `string` type on `pluginName` const - Inline `Stop` two-if pattern to single return - Inline `HTTPHandler` call site (p.httpMiddleware(next)) - Unify semconv import to v1.40.0 across config.go and plugin.go - Replace `make([]Option, 0, 5)` with `var options` in grpcOptions/httpOptions - Update otel, contrib, and grpc dependencies to latest compatible versions --- config.go | 2 +- go.mod | 30 +++++++++---------- go.sum | 64 ++++++++++++++++++++--------------------- plugin.go | 32 +++++++++------------ temporal_interceptor.go | 24 +++++----------- 5 files changed, 69 insertions(+), 83 deletions(-) diff --git a/config.go b/config.go index f375e24..cafeebf 100644 --- a/config.go +++ b/config.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.39.0" + semconv "go.opentelemetry.io/otel/semconv/v1.40.0" ) type Exporter string diff --git a/go.mod b/go.mod index 54e1a28..fa35de0 100644 --- a/go.mod +++ b/go.mod @@ -8,18 +8,18 @@ require ( github.com/google/uuid v1.6.0 github.com/roadrunner-server/context v1.3.0 github.com/roadrunner-server/errors v1.5.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 - go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 - go.opentelemetry.io/otel v1.43.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 - go.opentelemetry.io/otel/sdk v1.43.0 - go.opentelemetry.io/otel/trace v1.43.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 + go.opentelemetry.io/contrib/propagators/jaeger v1.44.0 + go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 + go.opentelemetry.io/otel/sdk v1.44.0 + go.opentelemetry.io/otel/trace v1.44.0 go.temporal.io/sdk v1.43.0 go.temporal.io/sdk/contrib/opentelemetry v0.7.0 - google.golang.org/grpc v1.81.0 + google.golang.org/grpc v1.81.1 ) require ( @@ -40,16 +40,16 @@ require ( github.com/stretchr/objx v0.5.3 // indirect github.com/stretchr/testify v1.11.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.temporal.io/api v1.62.11 // indirect - golang.org/x/net v0.54.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.15.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ae131ea..53280ad 100644 --- a/go.sum +++ b/go.sum @@ -54,28 +54,28 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 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.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= -go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 h1:peiLMz1+aqJE+3L4mOVtR9wlmv+yh/JVYXCBjqmzJJE= -go.opentelemetry.io/contrib/propagators/jaeger v1.43.0/go.mod h1:Agvif+4A8p/3UtZzJ0MCcDEuQwgtrzM71DueU41DCs8= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/contrib/propagators/jaeger v1.44.0 h1:OyzvsAMc/zHt0DRPcfstn0wgfq8ApDkeY0ABMcueweM= +go.opentelemetry.io/contrib/propagators/jaeger v1.44.0/go.mod h1:44kghcGX+BNxy9UTiWtd6VDt8Nd4EypGBkH2+v2Dqrc= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.temporal.io/api v1.62.11 h1:MWDaooDvOJCIRb1atqeZX2ErDPNTsNc3/mMEVEvvaVU= @@ -97,8 +97,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -111,8 +111,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -131,12 +131,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60 h1:3WsB1FAbiRIf2tOxscWKs3pQBD9he1NsrnbhMuWfekc= -google.golang.org/genproto/googleapis/api v0.0.0-20260511170946-3700d4141b60/go.mod h1:7yoXV7RIh5gblj/xVYoogxAWvA9wUeVbpsK/M694l00= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= -google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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= diff --git a/plugin.go b/plugin.go index 82ddb55..d391241 100644 --- a/plugin.go +++ b/plugin.go @@ -26,7 +26,7 @@ import ( ) const ( - pluginName string = "otel" + pluginName = "otel" ) type Logger interface { @@ -48,7 +48,7 @@ type Plugin struct { tracer *sdktrace.TracerProvider propagators propagation.TextMapPropagator httpMiddleware httpMiddleware - temporalInterceptor temporalInterceptor + temporalInterceptor interceptor.WorkerInterceptor } func (p *Plugin) Init(cfg Configurer, log Logger) error { //nolint:gocyclo @@ -108,29 +108,32 @@ func (p *Plugin) Init(cfg Configurer, log Logger) error { //nolint:gocyclo return errors.Errorf("unknown exporter: %s", p.cfg.Exporter) } - resource, err := newResource(p.cfg.Resource, cfg.RRVersion()) + res, err := newResource(p.cfg.Resource, cfg.RRVersion()) if err != nil { return errors.E(op, err) } p.tracer = sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), - sdktrace.WithResource(resource), + sdktrace.WithResource(res), ) p.propagators = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}, jprop.Jaeger{}) p.httpMiddleware = httpWrapper(p.propagators, p.tracer, p.cfg.ServiceName) - p.temporalInterceptor = temporalWrapper(p.propagators, p.tracer) + p.temporalInterceptor, err = newTemporalInterceptor(p.propagators, p.tracer) + if err != nil { + return errors.E(op, err) + } otel.SetTracerProvider(p.tracer) return nil } func (p *Plugin) Middleware(next http.Handler) http.Handler { - return HTTPHandler(next, p.httpMiddleware) + return p.httpMiddleware(next) } func (p *Plugin) WorkerInterceptor() interceptor.WorkerInterceptor { - return TemporalHandler(p.temporalInterceptor) + return p.temporalInterceptor } func (p *Plugin) Serve() chan error { @@ -139,18 +142,11 @@ func (p *Plugin) Serve() chan error { func (p *Plugin) Stop(ctx context.Context) error { // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush - err := p.tracer.ForceFlush(ctx) - if err != nil { + if err := p.tracer.ForceFlush(ctx); err != nil { return err } - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown - err = p.tracer.Shutdown(ctx) - if err != nil { - return err - } - - return nil + return p.tracer.Shutdown(ctx) } func (p *Plugin) Tracer() *sdktrace.TracerProvider { @@ -180,7 +176,7 @@ func newResource(res *Resource, rrVersion string) (*resource.Resource, error) { } func grpcOptions(cfg *Config) []otlptracegrpc.Option { - options := make([]otlptracegrpc.Option, 0, 5) + var options []otlptracegrpc.Option if cfg.Insecure { options = append(options, otlptracegrpc.WithInsecure()) } @@ -201,7 +197,7 @@ func grpcOptions(cfg *Config) []otlptracegrpc.Option { } func httpOptions(cfg *Config) []otlptracehttp.Option { - options := make([]otlptracehttp.Option, 0, 5) + var options []otlptracehttp.Option if cfg.Insecure { options = append(options, otlptracehttp.WithInsecure()) } diff --git a/temporal_interceptor.go b/temporal_interceptor.go index af37577..af7cbea 100644 --- a/temporal_interceptor.go +++ b/temporal_interceptor.go @@ -8,21 +8,11 @@ import ( "go.temporal.io/sdk/interceptor" ) -// type alias for the interceptors -type temporalInterceptor func() interceptor.WorkerInterceptor - -func TemporalHandler(interceptor temporalInterceptor) interceptor.WorkerInterceptor { - return interceptor() -} - -func temporalWrapper(prop propagation.TextMapPropagator, tr trace.TracerProvider) temporalInterceptor { - return func() interceptor.WorkerInterceptor { - traceInterceptor, _ := opentelemetry.NewTracingInterceptor( - opentelemetry.TracerOptions{ - Tracer: tr.Tracer("WorkflowWorker"), - TextMapPropagator: prop, - SpanContextKey: rrcontext.OtelTracerNameKey, - }) - return traceInterceptor - } +func newTemporalInterceptor(prop propagation.TextMapPropagator, tr trace.TracerProvider) (interceptor.WorkerInterceptor, error) { + return opentelemetry.NewTracingInterceptor( + opentelemetry.TracerOptions{ + Tracer: tr.Tracer("WorkflowWorker"), + TextMapPropagator: prop, + SpanContextKey: rrcontext.OtelTracerNameKey, + }) } From 88a0ce8ddf4ebf4d4cbf111bfbd40d474007a8cd Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Fri, 29 May 2026 23:54:18 +0200 Subject: [PATCH 02/10] chore: remove unused HTTPHandler helper Middleware now calls p.httpMiddleware(next) directly, so the HTTPHandler wrapper is dead code. Its only argument is the unexported httpMiddleware type, so it was never usable as public API. Drop it for symmetry with the already-removed TemporalHandler helper. --- http_middleware.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/http_middleware.go b/http_middleware.go index c9fcd1d..c3e4ade 100644 --- a/http_middleware.go +++ b/http_middleware.go @@ -13,10 +13,6 @@ import ( // type alias for the middleware type httpMiddleware func(http.Handler) http.Handler -func HTTPHandler(next http.Handler, middleware httpMiddleware) http.Handler { - return middleware(next) -} - func httpWrapper(prop propagation.TextMapPropagator, tr trace.TracerProvider, sn string) httpMiddleware { return func(h http.Handler) http.Handler { // init otelhttp handler only once From ae61c8f31cf2e5f83e9877d58c465b6508e96ae7 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:16:36 +0200 Subject: [PATCH 03/10] chore: update deps Signed-off-by: Valery Piashchynski --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index fa35de0..2e35162 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 go.opentelemetry.io/otel/sdk v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 - go.temporal.io/sdk v1.43.0 + go.temporal.io/sdk v1.44.1 go.temporal.io/sdk/contrib/opentelemetry v0.7.0 google.golang.org/grpc v1.81.1 ) @@ -42,7 +42,7 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.temporal.io/api v1.62.11 // indirect + go.temporal.io/api v1.62.12 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.45.0 // indirect diff --git a/go.sum b/go.sum index 53280ad..ac814df 100644 --- a/go.sum +++ b/go.sum @@ -78,10 +78,10 @@ go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/ go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -go.temporal.io/api v1.62.11 h1:MWDaooDvOJCIRb1atqeZX2ErDPNTsNc3/mMEVEvvaVU= -go.temporal.io/api v1.62.11/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.43.0 h1:jHX/T2ZyBVjAtpQ/69NoMS6a+J0CpJAe+naqSB1gkvY= -go.temporal.io/sdk v1.43.0/go.mod h1:w9XuJzV25JhnJqUzxJWJISpp5q/EyeCtRKHvhW3lIoQ= +go.temporal.io/api v1.62.12 h1:627rVnItegQmrszg1bH4vfyc/1uNo5qCereCNkvZefw= +go.temporal.io/api v1.62.12/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= +go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= go.temporal.io/sdk/contrib/opentelemetry v0.7.0 h1:GSna1HP+1ibNXZ9xlVdQU2zFVqdt5VcdF0dzpeaYccQ= go.temporal.io/sdk/contrib/opentelemetry v0.7.0/go.mod h1:oQJC6UIl3FbSYh4f2MlUAIYSE6FPw02X1Tw8/bOvfxg= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From 86851bdeaddd77ebc3c7d3c50038d0b951355279 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:39:46 +0200 Subject: [PATCH 04/10] fix(otel): align semconv with the OTel SDK schema URL The SDK bump to v1.44.0 made its built-in resource detectors emit schema 1.41.0, while the plugin still pinned semconv/v1.40.0. resource.New then returns a conflicting-schema error that Init treats as fatal, so the plugin failed to start with every exporter. Bump semconv to v1.41.0 to match the SDK. --- config.go | 2 +- plugin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index cafeebf..f0140bd 100644 --- a/config.go +++ b/config.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.40.0" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" ) type Exporter string diff --git a/plugin.go b/plugin.go index d391241..177f954 100644 --- a/plugin.go +++ b/plugin.go @@ -18,7 +18,7 @@ import ( "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.40.0" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" "go.temporal.io/sdk/interceptor" // gzip grpc compressor From d962b8a61a7455c05d4c5c52991cb436ea61d6bc Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:39:55 +0200 Subject: [PATCH 05/10] test(otel): add config, plugin, and Temporal dev-server test suite Adds a tests/ module (wired via go.work) with black-box tests against the plugin's exported API: - config defaults, env-based exporter-client selection, resource precedence - plugin Init exporter selection + HTTP middleware lifecycle - a Temporal worker-interceptor test that runs a Go-SDK workflow against 'temporal server start-dev' (gated by TEMPORAL_ADDRESS, no PHP worker) CI installs the Temporal CLI and runs the dev server; 'make test' now runs both modules with coverage attributed to the otel packages. --- .github/workflows/linux.yml | 17 ++++- Makefile | 3 +- go.work | 6 ++ go.work.sum | 58 ++++++++++++++ tests/config_test.go | 83 ++++++++++++++++++++ tests/go.mod | 56 ++++++++++++++ tests/go.sum | 146 ++++++++++++++++++++++++++++++++++++ tests/plugin_test.go | 107 ++++++++++++++++++++++++++ tests/temporal_test.go | 105 ++++++++++++++++++++++++++ 9 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 go.work create mode 100644 go.work.sum create mode 100644 tests/config_test.go create mode 100644 tests/go.mod create mode 100644 tests/go.sum create mode 100644 tests/plugin_test.go create mode 100644 tests/temporal_test.go diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index eac65c2..a16ef93 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,5 +25,20 @@ jobs: restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: go mod download + - name: Install Temporal CLI (dev server) + run: | + curl -sSf https://temporal.download/cli.sh | sh + echo "$HOME/.temporalio/bin" >> "$GITHUB_PATH" - name: Run golang tests with coverage - run: make test + env: + TEMPORAL_ADDRESS: 127.0.0.1:7233 + run: | + temporal server start-dev --headless --ip 127.0.0.1 --port 7233 --log-level error & + SERVER_PID=$! + # wait for the Temporal frontend to accept connections + for i in $(seq 1 60); do + temporal operator cluster health --address 127.0.0.1:7233 >/dev/null 2>&1 && break + sleep 0.5 + done + make test + kill "$SERVER_PID" || true diff --git a/Makefile b/Makefile index 3018dae..aa3b4fc 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,3 @@ test: - go test -v -race -cover -tags=debug ./... \ No newline at end of file + go test -v -race -cover -tags=debug ./... + cd tests && go test -v -race -tags=debug -coverpkg=github.com/roadrunner-server/otel/v6/... ./... diff --git a/go.work b/go.work new file mode 100644 index 0000000..a819d87 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.26.3 + +use ( + . + ./tests +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..bef048a --- /dev/null +++ b/go.work.sum @@ -0,0 +1,58 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 h1:YhMSc48s25kr7kv31Z8vf7sPUIq5YJva9z1mn/hAt0M= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= +buf.build/go/protovalidate v0.12.0 h1:4GKJotbspQjRCcqZMGVSuC8SjwZ/FmgtSuKDpKUTZew= +buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= +github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= +github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/tests/config_test.go b/tests/config_test.go new file mode 100644 index 0000000..2338c65 --- /dev/null +++ b/tests/config_test.go @@ -0,0 +1,83 @@ +package tests + +import ( + "io" + "log/slog" + "testing" + + "github.com/roadrunner-server/otel/v6" + "github.com/stretchr/testify/require" +) + +// discardLogger returns a slog logger that drops everything; the otel config +// helpers only use it for deprecation warnings which are irrelevant here. +func discardLogger() *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +// TestConfig_Defaults verifies InitDefault fills the documented defaults: the +// OTLP exporter, the HTTP client, and a fully-populated Resource. +func TestConfig_Defaults(t *testing.T) { + cfg := &otel.Config{} + cfg.InitDefault(discardLogger()) + + require.Equal(t, otel.Exporter("otlp"), cfg.Exporter, "exporter must default to otlp") + require.Equal(t, otel.Client("http"), cfg.Client, "client must default to http") + + require.NotNil(t, cfg.Resource) + require.Equal(t, "RoadRunner", cfg.Resource.ServiceNameKey) + require.Equal(t, "1.0.0", cfg.Resource.ServiceVersionKey) + require.NotEmpty(t, cfg.Resource.ServiceInstanceIDKey, "instance id must be generated") + require.NotEmpty(t, cfg.Resource.ServiceNamespaceKey, "namespace must be generated") +} + +// TestConfig_ClientSelectionFromEnv verifies the OTEL protocol environment +// variables select the exporter client when none is configured, and that the +// traces-specific variable takes precedence over the generic one. +func TestConfig_ClientSelectionFromEnv(t *testing.T) { + cases := []struct { + name string + traces string // OTEL_EXPORTER_OTLP_TRACES_PROTOCOL + generic string // OTEL_EXPORTER_OTLP_PROTOCOL + want otel.Client + }{ + {"traces protocol wins over generic", "grpc", "http/protobuf", otel.Client("grpc")}, + {"generic http fallback", "", "http/protobuf", otel.Client("http")}, + {"generic grpc fallback", "", "grpc", otel.Client("grpc")}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", tc.traces) + t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tc.generic) + + cfg := &otel.Config{} + cfg.InitDefault(discardLogger()) + require.Equal(t, tc.want, cfg.Client) + }) + } +} + +// TestConfig_ResourceValuePrecedence verifies the value precedence in the +// resource attributes: an explicit Resource value wins over the deprecated +// top-level field, which in turn wins over the built-in default. +func TestConfig_ResourceValuePrecedence(t *testing.T) { + // Deprecated top-level fields flow into the Resource when nothing else set them. + deprecated := &otel.Config{ + ServiceName: "from-deprecated-name", + ServiceVersion: "9.9.9", + } + deprecated.InitDefault(discardLogger()) + require.Equal(t, "from-deprecated-name", deprecated.Resource.ServiceNameKey) + require.Equal(t, "9.9.9", deprecated.Resource.ServiceVersionKey) + + // An explicit Resource value takes precedence over the deprecated field, + // while an unset sibling still falls back to the default. + explicit := &otel.Config{ + ServiceName: "ignored-deprecated", + Resource: &otel.Resource{ServiceNameKey: "explicit-name"}, + } + explicit.InitDefault(discardLogger()) + require.Equal(t, "explicit-name", explicit.Resource.ServiceNameKey, "explicit resource value must win") + require.Equal(t, "1.0.0", explicit.Resource.ServiceVersionKey, "unset version must fall back to default") +} diff --git a/tests/go.mod b/tests/go.mod new file mode 100644 index 0000000..dc258c0 --- /dev/null +++ b/tests/go.mod @@ -0,0 +1,56 @@ +module tests + +go 1.26.3 + +require ( + github.com/roadrunner-server/otel/v6 v6.0.0 + github.com/stretchr/testify v1.11.1 + go.temporal.io/sdk v1.44.1 +) + +require ( + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/nexus-rpc/sdk-go v0.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/roadrunner-server/context v1.3.0 // indirect + github.com/roadrunner-server/errors v1.5.0 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/stretchr/objx v0.5.3 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.44.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect + go.temporal.io/api v1.62.12 // indirect + go.temporal.io/sdk/contrib/opentelemetry v0.7.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/roadrunner-server/otel/v6 => ../ diff --git a/tests/go.sum b/tests/go.sum new file mode 100644 index 0000000..ac814df --- /dev/null +++ b/tests/go.sum @@ -0,0 +1,146 @@ +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= +github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ= +github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4= +github.com/roadrunner-server/errors v1.5.0 h1:unG7LKIZrSzkCCF3YLRLA5VyqE0KKomofXVJUXJe00g= +github.com/roadrunner-server/errors v1.5.0/go.mod h1:g9fo/T2C13cWRDR9PW1r0ZAOSQfNhWAZawyfkGiaHuI= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +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.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/contrib/propagators/jaeger v1.44.0 h1:OyzvsAMc/zHt0DRPcfstn0wgfq8ApDkeY0ABMcueweM= +go.opentelemetry.io/contrib/propagators/jaeger v1.44.0/go.mod h1:44kghcGX+BNxy9UTiWtd6VDt8Nd4EypGBkH2+v2Dqrc= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 h1:lgh3PiVrRUWMLOVSkQicxzZll5NjF1r+AtsX1XRIHw0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0/go.mod h1:5Cnhth3m/AgOeTgE3ex12pPmiu/gGtZit03kSzx9X7s= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 h1:bl2S7Ubua0Nms+D/gAmznQTd4dxxMA93aKbcpKqiTCs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0/go.mod h1:L0hRV50XdVIODHUfWEqGRCXQvj2rV82STVo12FMFBU0= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.temporal.io/api v1.62.12 h1:627rVnItegQmrszg1bH4vfyc/1uNo5qCereCNkvZefw= +go.temporal.io/api v1.62.12/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.44.1 h1:Mt2OZLZpqkzDIdg9YyQzO0Rb/HqCDnnqHlIAGAJ5gqM= +go.temporal.io/sdk v1.44.1/go.mod h1:vkApR12F9/Y8OR+hkxe7WyXQFuCX6clhzqnAk6rzDAM= +go.temporal.io/sdk/contrib/opentelemetry v0.7.0 h1:GSna1HP+1ibNXZ9xlVdQU2zFVqdt5VcdF0dzpeaYccQ= +go.temporal.io/sdk/contrib/opentelemetry v0.7.0/go.mod h1:oQJC6UIl3FbSYh4f2MlUAIYSE6FPw02X1Tw8/bOvfxg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +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= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/plugin_test.go b/tests/plugin_test.go new file mode 100644 index 0000000..50a31bd --- /dev/null +++ b/tests/plugin_test.go @@ -0,0 +1,107 @@ +package tests + +import ( + "context" + "fmt" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "testing" + + "github.com/roadrunner-server/otel/v6" + "github.com/stretchr/testify/require" +) + +// mockConfigurer satisfies the otel plugin's Configurer interface. UnmarshalKey +// hands back a pre-built *otel.Config instead of decoding a real config file, +// which keeps these tests focused on the plugin wiring rather than RoadRunner's +// config decoder. +type mockConfigurer struct { + cfg *otel.Config +} + +func (m *mockConfigurer) RRVersion() string { return "2025.1.0" } + +func (m *mockConfigurer) Has(string) bool { return m.cfg != nil } + +func (m *mockConfigurer) UnmarshalKey(_ string, out any) error { + p, ok := out.(**otel.Config) + if !ok { + return fmt.Errorf("mockConfigurer: unexpected target type %T", out) + } + *p = m.cfg + return nil +} + +type mockLogger struct{} + +func (mockLogger) NamedLogger(string) *slog.Logger { + return slog.New(slog.NewTextHandler(io.Discard, nil)) +} + +func newConfigurer(cfg *otel.Config) *mockConfigurer { return &mockConfigurer{cfg: cfg} } + +// TestPlugin_InitExporterSelection covers the exporter-selection branches of +// Plugin.Init. The stdout/stderr exporters initialize without any network; the +// deprecated and unknown exporters must fail fast with an actionable error. +func TestPlugin_InitExporterSelection(t *testing.T) { + cases := []struct { + name string + cfg *otel.Config + wantErr bool + }{ + {"stdout", &otel.Config{Exporter: otel.Exporter("stdout")}, false}, + {"stderr", &otel.Config{Exporter: otel.Exporter("stderr")}, false}, + {"jaeger is deprecated", &otel.Config{Exporter: otel.Exporter("jaeger")}, true}, + {"zipkin is deprecated", &otel.Config{Exporter: otel.Exporter("zipkin")}, true}, + {"unknown exporter", &otel.Config{Exporter: otel.Exporter("bogus")}, true}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + p := &otel.Plugin{} + err := p.Init(newConfigurer(tc.cfg), mockLogger{}) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NoError(t, p.Stop(context.Background())) + }) + } +} + +// TestPlugin_LifecycleAndMiddleware boots the plugin with a stdout exporter and +// checks the full surface a RoadRunner container relies on: the plugin name, +// the tracer and Temporal interceptor are wired, the HTTP middleware forwards +// requests untouched, and Stop flushes without error. +func TestPlugin_LifecycleAndMiddleware(t *testing.T) { + p := &otel.Plugin{} + require.NoError(t, p.Init(newConfigurer(&otel.Config{Exporter: otel.Exporter("stdout")}), mockLogger{})) + + require.Equal(t, "otel", p.Name()) + require.NotNil(t, p.Tracer(), "tracer provider must be initialized") + require.NotNil(t, p.WorkerInterceptor(), "temporal worker interceptor must be initialized") + + var called bool + next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + called = true + w.WriteHeader(http.StatusTeapot) + _, _ = io.WriteString(w, "ok") + }) + + srv := httptest.NewServer(p.Middleware(next)) + t.Cleanup(srv.Close) + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, srv.URL+"/hello", nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + t.Cleanup(func() { _ = resp.Body.Close() }) + + require.True(t, called, "the wrapped handler must be invoked") + require.Equal(t, http.StatusTeapot, resp.StatusCode, "status code must propagate through the middleware") + + require.NoError(t, p.Stop(context.Background())) +} diff --git a/tests/temporal_test.go b/tests/temporal_test.go new file mode 100644 index 0000000..ab32bd5 --- /dev/null +++ b/tests/temporal_test.go @@ -0,0 +1,105 @@ +package tests + +import ( + "bytes" + "context" + "io" + "os" + "strings" + "testing" + "time" + + "github.com/roadrunner-server/otel/v6" + "github.com/stretchr/testify/require" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/interceptor" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +const otelTestTaskQueue = "otel-interceptor-test" + +// EchoActivity upper-cases its input. It runs inside the Go-SDK worker, so no +// PHP worker is required to exercise the Temporal integration. +func EchoActivity(_ context.Context, in string) (string, error) { + return strings.ToUpper(in), nil +} + +// EchoWorkflow runs EchoActivity and returns its result. +func EchoWorkflow(ctx workflow.Context, in string) (string, error) { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + StartToCloseTimeout: 10 * time.Second, + }) + + var out string + if err := workflow.ExecuteActivity(ctx, EchoActivity, in).Get(ctx, &out); err != nil { + return "", err + } + return out, nil +} + +// TestPlugin_TemporalInterceptor_DevServer runs a real Temporal workflow through +// the otel plugin's WorkerInterceptor against a `temporal server start-dev` +// instance, and asserts that the plugin exported the spans the interceptor +// produced. It is skipped unless TEMPORAL_ADDRESS points at a running dev +// server (the CI workflow sets it to 127.0.0.1:7233). +func TestPlugin_TemporalInterceptor_DevServer(t *testing.T) { + addr := os.Getenv("TEMPORAL_ADDRESS") + if addr == "" { + t.Skip("TEMPORAL_ADDRESS not set; run `temporal server start-dev` and set TEMPORAL_ADDRESS=127.0.0.1:7233 to run this test") + } + + c, err := client.Dial(client.Options{HostPort: addr, Namespace: "default"}) + require.NoError(t, err, "dial temporal dev server at %s", addr) + defer c.Close() + + // Redirect stdout so the plugin's stdout span exporter writes into a pipe we + // can inspect. testing.T buffers its own output, so test results are still + // reported correctly. The drain goroutine prevents the pipe from blocking. + origStdout := os.Stdout + pr, pw, err := os.Pipe() + require.NoError(t, err) + defer func() { os.Stdout = origStdout }() + os.Stdout = pw + + var captured bytes.Buffer + drained := make(chan struct{}) + go func() { + _, _ = io.Copy(&captured, pr) + close(drained) + }() + + p := &otel.Plugin{} + require.NoError(t, p.Init(newConfigurer(&otel.Config{Exporter: otel.Exporter("stdout")}), mockLogger{})) + + w := worker.New(c, otelTestTaskQueue, worker.Options{ + Interceptors: []interceptor.WorkerInterceptor{p.WorkerInterceptor()}, + }) + w.RegisterWorkflow(EchoWorkflow) + w.RegisterActivity(EchoActivity) + require.NoError(t, w.Start()) + defer w.Stop() + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + run, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{TaskQueue: otelTestTaskQueue}, EchoWorkflow, "hello") + require.NoError(t, err) + + var result string + require.NoError(t, run.Get(ctx, &result)) + require.Equal(t, "HELLO", result, "workflow executed through the otel interceptor must return the activity result") + + // Flush batched spans through the plugin's exporter, then read what landed. + stopCtx, stopCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer stopCancel() + require.NoError(t, p.Stop(stopCtx)) + + _ = pw.Close() + <-drained + os.Stdout = origStdout + + out := captured.String() + require.NotEmpty(t, out, "the otel plugin must export spans produced by the Temporal interceptor") + require.Contains(t, out, "EchoWorkflow", "exported spans must reference the executed workflow") +} From 69e8ee86c64123773419e11312cbb077b2143873 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 19:43:08 +0200 Subject: [PATCH 06/10] test(otel): close stdout pipe writer on all exit paths Guards the drain goroutine against leaking if an assertion fails between the stdout redirect and the explicit close. --- tests/temporal_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/temporal_test.go b/tests/temporal_test.go index ab32bd5..8b5022c 100644 --- a/tests/temporal_test.go +++ b/tests/temporal_test.go @@ -60,6 +60,9 @@ func TestPlugin_TemporalInterceptor_DevServer(t *testing.T) { pr, pw, err := os.Pipe() require.NoError(t, err) defer func() { os.Stdout = origStdout }() + // Closing the write end on every exit path lets the drain goroutine see EOF + // and return, even if an assertion below fails before the explicit close. + defer func() { _ = pw.Close() }() os.Stdout = pw var captured bytes.Buffer From e863c1b92d204993952d7d7f5b6a46ee32cc609d Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 20:06:14 +0200 Subject: [PATCH 07/10] test(otel): assert temporal spans via in-memory exporter Replace the os.Stdout pipe-capture with the http plugin's approach: a tracetest.InMemoryExporter behind a synchronous TracerProvider, read via GetSpans(). No global stdout/stderr redirection, no flush-timing hacks. --- tests/go.mod | 4 +- tests/temporal_test.go | 84 +++++++++++++++++++++--------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index dc258c0..90eef14 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -5,7 +5,9 @@ go 1.26.3 require ( github.com/roadrunner-server/otel/v6 v6.0.0 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel/sdk v1.44.0 go.temporal.io/sdk v1.44.1 + go.temporal.io/sdk/contrib/opentelemetry v0.7.0 ) require ( @@ -36,11 +38,9 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.44.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.temporal.io/api v1.62.12 // indirect - go.temporal.io/sdk/contrib/opentelemetry v0.7.0 // indirect golang.org/x/net v0.55.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.45.0 // indirect diff --git a/tests/temporal_test.go b/tests/temporal_test.go index 8b5022c..0173d1b 100644 --- a/tests/temporal_test.go +++ b/tests/temporal_test.go @@ -1,17 +1,18 @@ package tests import ( - "bytes" "context" - "io" "os" + "slices" "strings" "testing" "time" - "github.com/roadrunner-server/otel/v6" "github.com/stretchr/testify/require" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "go.temporal.io/sdk/client" + otelinterceptor "go.temporal.io/sdk/contrib/opentelemetry" "go.temporal.io/sdk/interceptor" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" @@ -38,50 +39,44 @@ func EchoWorkflow(ctx workflow.Context, in string) (string, error) { return out, nil } -// TestPlugin_TemporalInterceptor_DevServer runs a real Temporal workflow through -// the otel plugin's WorkerInterceptor against a `temporal server start-dev` -// instance, and asserts that the plugin exported the spans the interceptor -// produced. It is skipped unless TEMPORAL_ADDRESS points at a running dev -// server (the CI workflow sets it to 127.0.0.1:7233). -func TestPlugin_TemporalInterceptor_DevServer(t *testing.T) { +// TestTemporalOtelInterceptor_DevServer runs a real Temporal workflow against a +// `temporal server start-dev` instance through the OpenTelemetry worker +// interceptor the otel plugin builds (go.temporal.io/sdk/contrib/opentelemetry), +// and asserts the produced spans. +// +// It follows the http plugin's otel test approach: an in-memory exporter behind +// a synchronous TracerProvider, read back via GetSpans() — no os.Stdout/os.Stderr +// redirection. The test is skipped unless TEMPORAL_ADDRESS points at a running +// dev server (CI sets it to 127.0.0.1:7233). +func TestTemporalOtelInterceptor_DevServer(t *testing.T) { addr := os.Getenv("TEMPORAL_ADDRESS") if addr == "" { t.Skip("TEMPORAL_ADDRESS not set; run `temporal server start-dev` and set TEMPORAL_ADDRESS=127.0.0.1:7233 to run this test") } + // In-memory exporter with a synchronous syncer: spans are exported the moment + // they end, so they are available right after the worker drains. + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exp)) + t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) + + // Built exactly as the otel plugin builds its Temporal interceptor, but over + // the in-memory tracer so the spans can be asserted directly. + ti, err := otelinterceptor.NewTracingInterceptor(otelinterceptor.TracerOptions{ + Tracer: tp.Tracer("WorkflowWorker"), + }) + require.NoError(t, err) + c, err := client.Dial(client.Options{HostPort: addr, Namespace: "default"}) require.NoError(t, err, "dial temporal dev server at %s", addr) defer c.Close() - // Redirect stdout so the plugin's stdout span exporter writes into a pipe we - // can inspect. testing.T buffers its own output, so test results are still - // reported correctly. The drain goroutine prevents the pipe from blocking. - origStdout := os.Stdout - pr, pw, err := os.Pipe() - require.NoError(t, err) - defer func() { os.Stdout = origStdout }() - // Closing the write end on every exit path lets the drain goroutine see EOF - // and return, even if an assertion below fails before the explicit close. - defer func() { _ = pw.Close() }() - os.Stdout = pw - - var captured bytes.Buffer - drained := make(chan struct{}) - go func() { - _, _ = io.Copy(&captured, pr) - close(drained) - }() - - p := &otel.Plugin{} - require.NoError(t, p.Init(newConfigurer(&otel.Config{Exporter: otel.Exporter("stdout")}), mockLogger{})) - w := worker.New(c, otelTestTaskQueue, worker.Options{ - Interceptors: []interceptor.WorkerInterceptor{p.WorkerInterceptor()}, + Interceptors: []interceptor.WorkerInterceptor{ti}, }) w.RegisterWorkflow(EchoWorkflow) w.RegisterActivity(EchoActivity) require.NoError(t, w.Start()) - defer w.Stop() ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -93,16 +88,19 @@ func TestPlugin_TemporalInterceptor_DevServer(t *testing.T) { require.NoError(t, run.Get(ctx, &result)) require.Equal(t, "HELLO", result, "workflow executed through the otel interceptor must return the activity result") - // Flush batched spans through the plugin's exporter, then read what landed. - stopCtx, stopCancel := context.WithTimeout(context.Background(), 30*time.Second) - defer stopCancel() - require.NoError(t, p.Stop(stopCtx)) + // Stop drains in-flight tasks; with the synchronous syncer every span is + // exported by the time it returns. + w.Stop() - _ = pw.Close() - <-drained - os.Stdout = origStdout + spans := exp.GetSpans() + require.NotEmpty(t, spans, "the Temporal otel interceptor must produce spans") - out := captured.String() - require.NotEmpty(t, out, "the otel plugin must export spans produced by the Temporal interceptor") - require.Contains(t, out, "EchoWorkflow", "exported spans must reference the executed workflow") + names := make([]string, len(spans)) + for i, s := range spans { + names[i] = s.Name + } + found := slices.ContainsFunc(spans, func(s tracetest.SpanStub) bool { + return strings.Contains(s.Name, "EchoWorkflow") || strings.Contains(s.Name, "EchoActivity") + }) + require.True(t, found, "expected a workflow/activity span, got: %v", names) } From 905eeb64f693b6b01a478ce945adb560a50f23ed Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 20:06:22 +0200 Subject: [PATCH 08/10] ci(otel): upload test coverage to codecov Mirror the http plugin: archive the coverage profile as an artifact and add a codecov job that filters to the otel module paths and uploads via codecov/codecov-action (fail_ci_if_error: false). --- .github/workflows/linux.yml | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a16ef93..6a02d8a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -29,6 +29,8 @@ jobs: run: | curl -sSf https://temporal.download/cli.sh | sh echo "$HOME/.temporalio/bin" >> "$GITHUB_PATH" + - name: Create coverage folder + run: mkdir ./tests/coverage-ci - name: Run golang tests with coverage env: TEMPORAL_ADDRESS: 127.0.0.1:7233 @@ -40,5 +42,42 @@ jobs: temporal operator cluster health --address 127.0.0.1:7233 >/dev/null 2>&1 && break sleep 0.5 done - make test + cd tests + go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=github.com/roadrunner-server/otel/v6/... -coverprofile=./coverage-ci/otel.out -covermode=atomic ./... kill "$SERVER_PID" || true + - name: Archive code coverage results + uses: actions/upload-artifact@v7 + with: + name: coverage + path: ./tests/coverage-ci + + codecov: + name: Upload codecov + runs-on: ubuntu-latest + needs: + - otel_plugin + timeout-minutes: 60 + steps: + - name: Check out code + uses: actions/checkout@v6 + - name: Download code coverage results + uses: actions/download-artifact@v8 + with: + name: coverage + path: coverage + - run: | + echo 'mode: atomic' > summary.txt + tail -q -n +2 coverage/*.out >> summary.txt + awk ' + NR == 1 { print; next } + /^github\.com\/roadrunner-server\/otel\/v6\// { + sub(/^github\.com\/roadrunner-server\/otel\/v6\//, "", $0) + print + } + ' summary.txt > summary.filtered.txt + mv summary.filtered.txt summary.txt + - name: upload to codecov + uses: codecov/codecov-action@v6 # Docs: + with: + files: summary.txt + fail_ci_if_error: false From 75dfe3106f2d87838fae4c25de4ad3c4ebb1a115 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 20:23:13 +0200 Subject: [PATCH 09/10] chore(otel): address PR review feedback - CI: install Temporal via the official temporalio/setup-temporal action instead of curl|sh - CI: disable git credential persistence in the codecov checkout step - test: stop the Temporal worker once on all paths (sync.Once + t.Cleanup) so an early assertion failure cannot leak the worker --- .github/workflows/linux.yml | 8 ++++---- tests/temporal_test.go | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6a02d8a..7579ce9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,10 +25,8 @@ jobs: restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: go mod download - - name: Install Temporal CLI (dev server) - run: | - curl -sSf https://temporal.download/cli.sh | sh - echo "$HOME/.temporalio/bin" >> "$GITHUB_PATH" + - name: Install Temporal CLI + uses: temporalio/setup-temporal@v0.1.0 - name: Create coverage folder run: mkdir ./tests/coverage-ci - name: Run golang tests with coverage @@ -60,6 +58,8 @@ jobs: steps: - name: Check out code uses: actions/checkout@v6 + with: + persist-credentials: false - name: Download code coverage results uses: actions/download-artifact@v8 with: diff --git a/tests/temporal_test.go b/tests/temporal_test.go index 0173d1b..5aa6f0f 100644 --- a/tests/temporal_test.go +++ b/tests/temporal_test.go @@ -5,6 +5,7 @@ import ( "os" "slices" "strings" + "sync" "testing" "time" @@ -78,6 +79,13 @@ func TestTemporalOtelInterceptor_DevServer(t *testing.T) { w.RegisterActivity(EchoActivity) require.NoError(t, w.Start()) + // Stop the worker exactly once on every exit path. Draining in-flight tasks + // flushes all spans through the synchronous exporter before GetSpans; the + // t.Cleanup guard also covers assertion failures before the explicit stop. + var stopOnce sync.Once + stop := func() { stopOnce.Do(w.Stop) } + t.Cleanup(stop) + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -88,9 +96,8 @@ func TestTemporalOtelInterceptor_DevServer(t *testing.T) { require.NoError(t, run.Get(ctx, &result)) require.Equal(t, "HELLO", result, "workflow executed through the otel interceptor must return the activity result") - // Stop drains in-flight tasks; with the synchronous syncer every span is - // exported by the time it returns. - w.Stop() + // Drain now so the synchronous exporter has flushed all worker-side spans. + stop() spans := exp.GetSpans() require.NotEmpty(t, spans, "the Temporal otel interceptor must produce spans") From 17a503d6fecd2576fd4273d5fe72002b1d46602a Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 31 May 2026 20:26:29 +0200 Subject: [PATCH 10/10] ci(otel): install Temporal CLI via curl|sh, not setup-temporal action The temporalio/setup-temporal action is unmaintained: @v0.1.0 is broken (runs a missing ./setup-temporal.sh, exit 127) and @v0 is a node16 action from 2023. The official curl|sh installer is the reliable choice for CI. --- .github/workflows/linux.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7579ce9..1b9a84b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,8 +25,10 @@ jobs: restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: go mod download - - name: Install Temporal CLI - uses: temporalio/setup-temporal@v0.1.0 + - name: Install Temporal CLI (dev server) + run: | + curl -sSf https://temporal.download/cli.sh | sh + echo "$HOME/.temporalio/bin" >> "$GITHUB_PATH" - name: Create coverage folder run: mkdir ./tests/coverage-ci - name: Run golang tests with coverage