aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrii Kushch <[email protected]>2022-03-08 20:18:32 +0100
committerGitHub <[email protected]>2022-03-08 12:18:32 -0700
commitd0b608af3178bc674936f4b1c6cce00591ebbf09 (patch)
treea8646d0d406acf98df62856815d4eae4d44cdd4f
parentd9b1d463259a6f8f520edd6659dac11218c82b4e (diff)
downloadcaddy-d0b608af3178bc674936f4b1c6cce00591ebbf09.tar.gz
caddy-d0b608af3178bc674936f4b1c6cce00591ebbf09.zip
tracing: New OpenTelemetry module (#4361)
* opentelemetry: create a new module * fix imports * fix test * Update modules/caddyhttp/opentelemetry/README.md Co-authored-by: Dave Henderson <[email protected]> * Update modules/caddyhttp/opentelemetry/README.md Co-authored-by: Dave Henderson <[email protected]> * Update modules/caddyhttp/opentelemetry/README.md Co-authored-by: Dave Henderson <[email protected]> * Update modules/caddyhttp/opentelemetry/tracer.go Co-authored-by: Dave Henderson <[email protected]> * rename error ErrUnsupportedTracesProtocol * replace spaces with tabs in the test data * Update modules/caddyhttp/opentelemetry/README.md Co-authored-by: Francis Lavoie <[email protected]> * Update modules/caddyhttp/opentelemetry/README.md Co-authored-by: Francis Lavoie <[email protected]> * replace spaces with tabs in the README.md * use default values for a propagation and exporter protocol * set http attributes with helper * simplify code * Cleanup modules/caddyhttp/opentelemetry/README.md Co-authored-by: Dave Henderson <[email protected]> * Update link in README.md Co-authored-by: Dave Henderson <[email protected]> * Update documentation in README.md Co-authored-by: Dave Henderson <[email protected]> * Update link to naming spec in README.md Co-authored-by: Dave Henderson <[email protected]> * Rename module from opentelemetry to tracing Co-authored-by: Dave Henderson <[email protected]> * Rename span_name to span Co-authored-by: Dave Henderson <[email protected]> * Rename span_name to span Co-authored-by: Dave Henderson <[email protected]> * Simplify otel resource creation Co-authored-by: Dave Henderson <[email protected]> * handle extra attributes Co-authored-by: Dave Henderson <[email protected]> * update go.opentelemetry.io/otel/semconv to 1.7.0 Co-authored-by: Dave Henderson <[email protected]> * update go.opentelemetry.io/otel version * remove environment variable handling * always use tracecontext,baggage as propagators * extract tracer name into variable * rename OpenTelemetry to Tracing * simplify resource creation * update go.mod * rename package from opentelemetry to tracing * cleanup tests * update Caddyfile example in README.md * update README.md * fix test * fix module name in README.md * fix module name in README.md * change names in README.md and tests * order imports * remove redundant tests * Update documentation README.md Co-authored-by: Dave Henderson <[email protected]> * Fix grammar Co-authored-by: Dave Henderson <[email protected]> * Update comments Co-authored-by: Dave Henderson <[email protected]> * Update comments Co-authored-by: Dave Henderson <[email protected]> * update go.sum * update go.sum * Add otelhttp instrumentation, update OpenTelemetry libraries. * Use otelhttp instrumentation for instrumenting HTTP requests. This change uses context.WithValue to inject the next handler into the request context via a "nextCall" carrier struct, and pass it on to a standard Go HTTP handler returned by otelhttp.NewHandler. The underlying handler will extract the next handler from the context, call it and pass the returned error to the carrier struct. * use zap.Error() for the error log * remove README.md * update dependencies * clean up the code * change comment * move serveHTTP method from separate file * add syntax to the UnmarshalCaddyfile comment * go import the file * admin: Write proper status on invalid requests (#4569) (fix #4561) * update dependencies Co-authored-by: Dave Henderson <[email protected]> Co-authored-by: Francis Lavoie <[email protected]> Co-authored-by: Vibhav Pant <[email protected]> Co-authored-by: Alok Naushad <[email protected]> Co-authored-by: Cedric Ziel <[email protected]>
-rw-r--r--caddyconfig/httpcaddyfile/directives.go2
-rw-r--r--caddytest/integration/caddyfile_adapt/tracing.txt36
-rw-r--r--go.mod4
-rw-r--r--go.sum47
-rw-r--r--modules/caddyhttp/standard/imports.go1
-rw-r--r--modules/caddyhttp/tracing/module.go121
-rw-r--r--modules/caddyhttp/tracing/module_test.go182
-rw-r--r--modules/caddyhttp/tracing/tracer.go108
-rw-r--r--modules/caddyhttp/tracing/tracer_test.go27
-rw-r--r--modules/caddyhttp/tracing/tracerprovider.go63
-rw-r--r--modules/caddyhttp/tracing/tracerprovider_test.go43
11 files changed, 632 insertions, 2 deletions
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index feb8e91ad..03e6753ee 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -37,6 +37,8 @@ import (
// The header directive goes second so that headers
// can be manipulated before doing redirects.
var directiveOrder = []string{
+ "tracing",
+
"map",
"root",
diff --git a/caddytest/integration/caddyfile_adapt/tracing.txt b/caddytest/integration/caddyfile_adapt/tracing.txt
new file mode 100644
index 000000000..322866001
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tracing.txt
@@ -0,0 +1,36 @@
+:80 {
+ tracing /myhandler {
+ span my-span
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "path": [
+ "/myhandler"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "tracing",
+ "span": "my-span"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/go.mod b/go.mod
index 511d2a847..61e2360f7 100644
--- a/go.mod
+++ b/go.mod
@@ -28,6 +28,10 @@ require (
github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74
github.com/yuin/goldmark v1.4.4
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0
+ go.opentelemetry.io/otel v1.4.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0
+ go.opentelemetry.io/otel/sdk v1.4.0
go.uber.org/zap v1.20.0
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
golang.org/x/net v0.0.0-20210913180222-943fd674d43e
diff --git a/go.sum b/go.sum
index 2107d539a..ff454d44c 100644
--- a/go.sum
+++ b/go.sum
@@ -194,7 +194,10 @@ github.com/caddyserver/certmagic v0.15.4/go.mod h1:qhkAOthf72ufAcp3Y5jF2RaGE96oi
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
+github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -218,7 +221,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
@@ -289,11 +296,14 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
+github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -330,6 +340,10 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+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/go-piv/piv-go v1.7.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -412,8 +426,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-licenses v0.0.0-20210329231322-ce1d9163b77d/go.mod h1:+TYOmkVoJOpwnS0wfdsJCV9CoD5nJYsHoFk/0CrTK4M=
@@ -489,6 +504,7 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
@@ -1001,17 +1017,39 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0 h1:SLme4Porm+UwX0DdHMxlwRt7FzPSE0sys81bet2o0pU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
+go.opentelemetry.io/otel v1.4.0 h1:7ESuKPq6zpjRaY5nvVDGiuwK7VAJ8MwkKnmNJ9whNZ4=
+go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk=
+go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0 h1:j7AwzDdAQBJjcqayAaYbvpYeZzII7cEe5qJTu+De6UY=
+go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0 h1:lRpP10E8oTGVmY1nVXcwelCT1Z8ca41/l5ce7AqLAss=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.0/go.mod h1:3oS+j2WUoJVyj6/BzQN/52G17lNJDulngsOxDm1w2PY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0 h1:buSx4AMC/0Z232slPhicN/fU5KIlj0bMngct5pcZhkI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0/go.mod h1:ew1NcwkHo0QFT3uTm3m2IVZMkZdVIpbOYNPasgWwpdk=
+go.opentelemetry.io/otel/internal/metric v0.27.0 h1:9dAVGAfFiiEq5NVB9FUJ5et+btbDQAUIJehJ+ikyryk=
+go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
+go.opentelemetry.io/otel/metric v0.27.0 h1:HhJPsGhJoKRSegPQILFbODU56NS/L1UE4fS1sC5kIwQ=
+go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
+go.opentelemetry.io/otel/sdk v1.4.0 h1:LJE4SW3jd4lQTESnlpQZcBhQ3oci0U2MLR5uhicfTHQ=
+go.opentelemetry.io/otel/sdk v1.4.0/go.mod h1:71GJPNJh4Qju6zJuYl1CrYtXbrgfau/M9UAggqiy1UE=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
+go.opentelemetry.io/otel/trace v1.4.0 h1:4OOUrPZdVFQkbzl/JSdvGCWIdw5ONXXxzHlaLlWppmo=
+go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.opentelemetry.io/proto/otlp v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c=
+go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ=
go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
@@ -1026,8 +1064,9 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
-go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
@@ -1279,6 +1318,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -1557,6 +1597,9 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
diff --git a/modules/caddyhttp/standard/imports.go b/modules/caddyhttp/standard/imports.go
index 0e2203c1e..8ce239579 100644
--- a/modules/caddyhttp/standard/imports.go
+++ b/modules/caddyhttp/standard/imports.go
@@ -17,4 +17,5 @@ import (
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi"
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
+ _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing"
)
diff --git a/modules/caddyhttp/tracing/module.go b/modules/caddyhttp/tracing/module.go
new file mode 100644
index 000000000..7cce6692b
--- /dev/null
+++ b/modules/caddyhttp/tracing/module.go
@@ -0,0 +1,121 @@
+package tracing
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+ "go.uber.org/zap"
+)
+
+func init() {
+ caddy.RegisterModule(Tracing{})
+ httpcaddyfile.RegisterHandlerDirective("tracing", parseCaddyfile)
+}
+
+// Tracing implements an HTTP handler that adds support for distributed tracing,
+// using OpenTelemetry. This module is responsible for the injection and
+// propagation of the trace context. Configure this module via environment
+// variables (see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md).
+// Some values can be overwritten in the configuration file.
+type Tracing struct {
+ // SpanName is a span name. It should follow the naming guidelines here:
+ // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span
+ SpanName string `json:"span"`
+
+ // otel implements opentelemetry related logic.
+ otel openTelemetryWrapper
+
+ logger *zap.Logger
+}
+
+// CaddyModule returns the Caddy module information.
+func (Tracing) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "http.handlers.tracing",
+ New: func() caddy.Module { return new(Tracing) },
+ }
+}
+
+// Provision implements caddy.Provisioner.
+func (ot *Tracing) Provision(ctx caddy.Context) error {
+ ot.logger = ctx.Logger(ot)
+
+ var err error
+ ot.otel, err = newOpenTelemetryWrapper(ctx, ot.SpanName)
+
+ return err
+}
+
+// Cleanup implements caddy.CleanerUpper and closes any idle connections. It
+// calls Shutdown method for a trace provider https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown.
+func (ot *Tracing) Cleanup() error {
+ if err := ot.otel.cleanup(ot.logger); err != nil {
+ return fmt.Errorf("tracerProvider shutdown: %w", err)
+ }
+ return nil
+}
+
+// ServeHTTP implements caddyhttp.MiddlewareHandler.
+func (ot *Tracing) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
+ return ot.otel.ServeHTTP(w, r, next)
+}
+
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// tracing {
+// [span <span_name>]
+// }
+//
+func (ot *Tracing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ setParameter := func(d *caddyfile.Dispenser, val *string) error {
+ if d.NextArg() {
+ *val = d.Val()
+ } else {
+ return d.ArgErr()
+ }
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ return nil
+ }
+
+ // paramsMap is a mapping between "string" parameter from the Caddyfile and its destination within the module
+ paramsMap := map[string]*string{
+ "span": &ot.SpanName,
+ }
+
+ for d.Next() {
+ args := d.RemainingArgs()
+ if len(args) > 0 {
+ return d.ArgErr()
+ }
+
+ for d.NextBlock(0) {
+ if dst, ok := paramsMap[d.Val()]; ok {
+ if err := setParameter(d, dst); err != nil {
+ return err
+ }
+ } else {
+ return d.ArgErr()
+ }
+ }
+ }
+ return nil
+}
+
+func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
+ var m Tracing
+ err := m.UnmarshalCaddyfile(h.Dispenser)
+ return &m, err
+}
+
+// Interface guards
+var (
+ _ caddy.Provisioner = (*Tracing)(nil)
+ _ caddyhttp.MiddlewareHandler = (*Tracing)(nil)
+ _ caddyfile.Unmarshaler = (*Tracing)(nil)
+)
diff --git a/modules/caddyhttp/tracing/module_test.go b/modules/caddyhttp/tracing/module_test.go
new file mode 100644
index 000000000..0fbc05bbd
--- /dev/null
+++ b/modules/caddyhttp/tracing/module_test.go
@@ -0,0 +1,182 @@
+package tracing
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+)
+
+func TestTracing_UnmarshalCaddyfile(t *testing.T) {
+ tests := []struct {
+ name string
+ spanName string
+ d *caddyfile.Dispenser
+ wantErr bool
+ }{
+ {
+ name: "Full config",
+ spanName: "my-span",
+ d: caddyfile.NewTestDispenser(`
+tracing {
+ span my-span
+}`),
+ wantErr: false,
+ },
+ {
+ name: "Only span name in the config",
+ spanName: "my-span",
+ d: caddyfile.NewTestDispenser(`
+tracing {
+ span my-span
+}`),
+ wantErr: false,
+ },
+ {
+ name: "Empty config",
+ d: caddyfile.NewTestDispenser(`
+tracing {
+}`),
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ot := &Tracing{}
+ if err := ot.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr {
+ t.Errorf("UnmarshalCaddyfile() error = %v, wantErrType %v", err, tt.wantErr)
+ }
+
+ if ot.SpanName != tt.spanName {
+ t.Errorf("UnmarshalCaddyfile() SpanName = %v, want SpanName %v", ot.SpanName, tt.spanName)
+ }
+ })
+ }
+}
+
+func TestTracing_UnmarshalCaddyfile_Error(t *testing.T) {
+ tests := []struct {
+ name string
+ d *caddyfile.Dispenser
+ wantErr bool
+ }{
+ {
+ name: "Unknown parameter",
+ d: caddyfile.NewTestDispenser(`
+ tracing {
+ foo bar
+ }`),
+ wantErr: true,
+ },
+ {
+ name: "Missed argument",
+ d: caddyfile.NewTestDispenser(`
+tracing {
+ span
+}`),
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ot := &Tracing{}
+ if err := ot.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr {
+ t.Errorf("UnmarshalCaddyfile() error = %v, wantErrType %v", err, tt.wantErr)
+ }
+ })
+ }
+}
+
+func TestTracing_ServeHTTP_Propagation_Without_Initial_Headers(t *testing.T) {
+ ot := &Tracing{
+ SpanName: "mySpan",
+ }
+
+ req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+ w := httptest.NewRecorder()
+
+ var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error {
+ traceparent := request.Header.Get("Traceparent")
+ if traceparent == "" || strings.HasPrefix(traceparent, "00-00000000000000000000000000000000-0000000000000000") {
+ t.Errorf("Invalid traceparent: %v", traceparent)
+ }
+
+ return nil
+ }
+
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
+ defer cancel()
+
+ if err := ot.Provision(ctx); err != nil {
+ t.Errorf("Provision error: %v", err)
+ t.FailNow()
+ }
+
+ if err := ot.ServeHTTP(w, req, handler); err != nil {
+ t.Errorf("ServeHTTP error: %v", err)
+ }
+}
+
+func TestTracing_ServeHTTP_Propagation_With_Initial_Headers(t *testing.T) {
+ ot := &Tracing{
+ SpanName: "mySpan",
+ }
+
+ req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+ req.Header.Set("traceparent", "00-11111111111111111111111111111111-1111111111111111-01")
+ w := httptest.NewRecorder()
+
+ var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error {
+ traceparent := request.Header.Get("Traceparent")
+ if !strings.HasPrefix(traceparent, "00-11111111111111111111111111111111") {
+ t.Errorf("Invalid traceparent: %v", traceparent)
+ }
+
+ return nil
+ }
+
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
+ defer cancel()
+
+ if err := ot.Provision(ctx); err != nil {
+ t.Errorf("Provision error: %v", err)
+ t.FailNow()
+ }
+
+ if err := ot.ServeHTTP(w, req, handler); err != nil {
+ t.Errorf("ServeHTTP error: %v", err)
+ }
+}
+
+func TestTracing_ServeHTTP_Next_Error(t *testing.T) {
+ ot := &Tracing{
+ SpanName: "mySpan",
+ }
+
+ req := httptest.NewRequest("GET", "https://example.com/foo", nil)
+ w := httptest.NewRecorder()
+
+ expectErr := errors.New("test error")
+
+ var handler caddyhttp.HandlerFunc = func(writer http.ResponseWriter, request *http.Request) error {
+ return expectErr
+ }
+
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
+ defer cancel()
+
+ if err := ot.Provision(ctx); err != nil {
+ t.Errorf("Provision error: %v", err)
+ t.FailNow()
+ }
+
+ if err := ot.ServeHTTP(w, req, handler); err == nil || !errors.Is(err, expectErr) {
+ t.Errorf("expected error, got: %v", err)
+ }
+}
diff --git a/modules/caddyhttp/tracing/tracer.go b/modules/caddyhttp/tracing/tracer.go
new file mode 100644
index 000000000..ce23944cf
--- /dev/null
+++ b/modules/caddyhttp/tracing/tracer.go
@@ -0,0 +1,108 @@
+package tracing
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "github.com/caddyserver/caddy/v2"
+
+ caddycmd "github.com/caddyserver/caddy/v2/cmd"
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
+ "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.7.0"
+ "go.uber.org/zap"
+)
+
+const (
+ webEngineName = "Caddy"
+ defaultSpanName = "handler"
+ nextCallCtxKey caddy.CtxKey = "nextCall"
+)
+
+// nextCall store the next handler, and the error value return on calling it (if any)
+type nextCall struct {
+ next caddyhttp.Handler
+ err error
+}
+
+// openTelemetryWrapper is responsible for the tracing injection, extraction and propagation.
+type openTelemetryWrapper struct {
+ propagators propagation.TextMapPropagator
+
+ handler http.Handler
+
+ spanName string
+}
+
+// newOpenTelemetryWrapper is responsible for the openTelemetryWrapper initialization using provided configuration.
+func newOpenTelemetryWrapper(
+ ctx context.Context,
+ spanName string,
+) (openTelemetryWrapper, error) {
+ if spanName == "" {
+ spanName = defaultSpanName
+ }
+
+ ot := openTelemetryWrapper{
+ spanName: spanName,
+ }
+
+ res, err := ot.newResource(webEngineName, caddycmd.CaddyVersion())
+ if err != nil {
+ return ot, fmt.Errorf("creating resource error: %w", err)
+ }
+
+ traceExporter, err := otlptracegrpc.New(ctx)
+ if err != nil {
+ return ot, fmt.Errorf("creating trace exporter error: %w", err)
+ }
+
+ ot.propagators = propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})
+
+ tracerProvider := globalTracerProvider.getTracerProvider(
+ sdktrace.WithBatcher(traceExporter),
+ sdktrace.WithResource(res),
+ )
+
+ ot.handler = otelhttp.NewHandler(http.HandlerFunc(ot.serveHTTP), ot.spanName, otelhttp.WithTracerProvider(tracerProvider), otelhttp.WithPropagators(ot.propagators))
+ return ot, nil
+}
+
+// serveHTTP injects a tracing context and call the next handler.
+func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request) {
+ ot.propagators.Inject(r.Context(), propagation.HeaderCarrier(r.Header))
+ next := r.Context().Value(nextCallCtxKey).(*nextCall)
+ next.err = next.next.ServeHTTP(w, r)
+}
+
+// ServeHTTP propagates call to the by wrapped by `otelhttp` next handler.
+func (ot *openTelemetryWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
+ n := &nextCall{
+ next: next,
+ err: nil,
+ }
+ ot.handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), nextCallCtxKey, n)))
+
+ return n.err
+}
+
+// cleanup flush all remaining data and shutdown a tracerProvider
+func (ot *openTelemetryWrapper) cleanup(logger *zap.Logger) error {
+ return globalTracerProvider.cleanupTracerProvider(logger)
+}
+
+// newResource creates a resource that describe current handler instance and merge it with a default attributes value.
+func (ot *openTelemetryWrapper) newResource(
+ webEngineName,
+ webEngineVersion string,
+) (*resource.Resource, error) {
+ return resource.Merge(resource.Default(), resource.NewSchemaless(
+ semconv.WebEngineNameKey.String(webEngineName),
+ semconv.WebEngineVersionKey.String(webEngineVersion),
+ ))
+}
diff --git a/modules/caddyhttp/tracing/tracer_test.go b/modules/caddyhttp/tracing/tracer_test.go
new file mode 100644
index 000000000..36a32ff46
--- /dev/null
+++ b/modules/caddyhttp/tracing/tracer_test.go
@@ -0,0 +1,27 @@
+package tracing
+
+import (
+ "context"
+ "testing"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func TestOpenTelemetryWrapper_newOpenTelemetryWrapper(t *testing.T) {
+ ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
+ defer cancel()
+
+ var otw openTelemetryWrapper
+ var err error
+
+ if otw, err = newOpenTelemetryWrapper(ctx,
+ "",
+ ); err != nil {
+ t.Errorf("newOpenTelemetryWrapper() error = %v", err)
+ t.FailNow()
+ }
+
+ if otw.propagators == nil {
+ t.Errorf("Propagators should not be empty")
+ }
+}
diff --git a/modules/caddyhttp/tracing/tracerprovider.go b/modules/caddyhttp/tracing/tracerprovider.go
new file mode 100644
index 000000000..035425ed6
--- /dev/null
+++ b/modules/caddyhttp/tracing/tracerprovider.go
@@ -0,0 +1,63 @@
+package tracing
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ "go.uber.org/zap"
+)
+
+// globalTracerProvider stores global tracer provider and is responsible for graceful shutdown when nobody is using it.
+var globalTracerProvider = &tracerProvider{}
+
+type tracerProvider struct {
+ mu sync.Mutex
+ tracerProvider *sdktrace.TracerProvider
+ tracerProvidersCounter int
+}
+
+// getTracerProvider create or return an existing global TracerProvider
+func (t *tracerProvider) getTracerProvider(opts ...sdktrace.TracerProviderOption) *sdktrace.TracerProvider {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ t.tracerProvidersCounter++
+
+ if t.tracerProvider == nil {
+ t.tracerProvider = sdktrace.NewTracerProvider(
+ opts...,
+ )
+ }
+
+ return t.tracerProvider
+}
+
+// cleanupTracerProvider gracefully shutdown a TracerProvider
+func (t *tracerProvider) cleanupTracerProvider(logger *zap.Logger) error {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+
+ if t.tracerProvidersCounter > 0 {
+ t.tracerProvidersCounter--
+ }
+
+ if t.tracerProvidersCounter == 0 {
+ if t.tracerProvider != nil {
+ // tracerProvider.ForceFlush SHOULD be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush
+ if err := t.tracerProvider.ForceFlush(context.Background()); err != nil {
+ logger.Error("forcing flush", zap.Error(err))
+ }
+
+ // tracerProvider.Shutdown MUST be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown
+ if err := t.tracerProvider.Shutdown(context.Background()); err != nil {
+ return fmt.Errorf("tracerProvider shutdown error: %w", err)
+ }
+ }
+
+ t.tracerProvider = nil
+ }
+
+ return nil
+}
diff --git a/modules/caddyhttp/tracing/tracerprovider_test.go b/modules/caddyhttp/tracing/tracerprovider_test.go
new file mode 100644
index 000000000..cb2e5936f
--- /dev/null
+++ b/modules/caddyhttp/tracing/tracerprovider_test.go
@@ -0,0 +1,43 @@
+package tracing
+
+import (
+ "testing"
+
+ "go.uber.org/zap"
+)
+
+func Test_tracersProvider_getTracerProvider(t *testing.T) {
+ tp := tracerProvider{}
+
+ tp.getTracerProvider()
+ tp.getTracerProvider()
+
+ if tp.tracerProvider == nil {
+ t.Errorf("There should be tracer provider")
+ }
+
+ if tp.tracerProvidersCounter != 2 {
+ t.Errorf("Tracer providers counter should equal to 2")
+ }
+}
+
+func Test_tracersProvider_cleanupTracerProvider(t *testing.T) {
+ tp := tracerProvider{}
+
+ tp.getTracerProvider()
+ tp.getTracerProvider()
+
+ err := tp.cleanupTracerProvider(zap.NewNop())
+
+ if err != nil {
+ t.Errorf("There should be no error: %v", err)
+ }
+
+ if tp.tracerProvider == nil {
+ t.Errorf("There should be tracer provider")
+ }
+
+ if tp.tracerProvidersCounter != 1 {
+ t.Errorf("Tracer providers counter should equal to 1")
+ }
+}