From 792eef26e764c0290dc3b82584bd8bfa395726a7 Mon Sep 17 00:00:00 2001 From: Andrii Kushch Date: Mon, 27 Sep 2021 10:54:22 +0200 Subject: [PATCH] opentelemetry: create a new module --- caddyconfig/httpcaddyfile/directives.go | 2 + .../caddyfile_adapt/opentelemetry.txt | 35 +++ go.mod | 6 + go.sum | 23 +- modules/caddyhttp/opentelemetry/README.md | 59 +++++ modules/caddyhttp/opentelemetry/module.go | 123 +++++++++ .../caddyhttp/opentelemetry/module_test.go | 237 ++++++++++++++++++ modules/caddyhttp/opentelemetry/tracer.go | 225 +++++++++++++++++ .../caddyhttp/opentelemetry/tracer_test.go | 138 ++++++++++ .../caddyhttp/opentelemetry/tracerprovider.go | 64 +++++ .../opentelemetry/tracerprovider_test.go | 42 ++++ modules/caddyhttp/standard/imports.go | 1 + 12 files changed, 954 insertions(+), 1 deletion(-) create mode 100644 caddytest/integration/caddyfile_adapt/opentelemetry.txt create mode 100644 modules/caddyhttp/opentelemetry/README.md create mode 100644 modules/caddyhttp/opentelemetry/module.go create mode 100644 modules/caddyhttp/opentelemetry/module_test.go create mode 100644 modules/caddyhttp/opentelemetry/tracer.go create mode 100644 modules/caddyhttp/opentelemetry/tracer_test.go create mode 100644 modules/caddyhttp/opentelemetry/tracerprovider.go create mode 100644 modules/caddyhttp/opentelemetry/tracerprovider_test.go diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 360f91e7c628..d97bd283ebf6 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{ + "opentelemetry", + "map", "root", diff --git a/caddytest/integration/caddyfile_adapt/opentelemetry.txt b/caddytest/integration/caddyfile_adapt/opentelemetry.txt new file mode 100644 index 000000000000..1ba777ea2a73 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/opentelemetry.txt @@ -0,0 +1,35 @@ +:80 { + opentelemetry /myhandler { + span_name my-span + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":80" + ], + "routes": [ + { + "match": [ + { + "path": [ + "/myhandler" + ] + } + ], + "handle": [ + { + "span_name": "my-span", + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 522abd8c8eb6..1516bd9b32d3 100644 --- a/go.mod +++ b/go.mod @@ -24,11 +24,17 @@ require ( github.com/smallstep/truststore v0.9.6 github.com/yuin/goldmark v1.4.0 github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01 + go.opentelemetry.io/otel v1.0.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0 + go.opentelemetry.io/otel/sdk v1.0.0 + go.opentelemetry.io/otel/trace v1.0.0 go.uber.org/zap v1.19.0 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/term v0.0.0-20210503060354-a79de5458b56 google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 + google.golang.org/grpc v1.40.0 google.golang.org/protobuf v1.27.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index ec17c2d26c23..3c55ae2f838a 100644 --- a/go.sum +++ b/go.sum @@ -178,7 +178,10 @@ github.com/caddyserver/certmagic v0.14.5/go.mod h1:/0VQ5og2Jxa5yBQ8eT80wWS7fi/Dg 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.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/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= @@ -201,6 +204,7 @@ 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/xds/go v0.0.0-20210312221358-fbca930ec8ed/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= @@ -271,6 +275,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 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/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= @@ -468,6 +473,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= @@ -973,14 +979,27 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 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/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= +go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= +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/otlptrace v1.0.0 h1:Vv4wbLEjheCTPV07jEav7fyUpJkyftQK7Ss2G7qgdSo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.0/go.mod h1:3VqVbIbjAycfL1C7sIu/Uh/kACIUPWHztt8ODYwR3oM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0 h1:B9VtEB1u41Ohnl8U6rMCh1jjedu8HwFh4D0QeB+1N+0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0/go.mod h1:zhEt6O5GGJ3NCAICr4hlCPoDb2GQuh4Obb4gZBgkoQQ= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= 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.0.0 h1:BNPMYUONPNbLneMttKSjQhOTlFLOD9U22HNG1KrIN2Y= +go.opentelemetry.io/otel/sdk v1.0.0/go.mod h1:PCrDHlSy5x1kjezSdL37PhbFUMjrsLRshJ2zCzeXwbM= 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.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4= +go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.9.0 h1:C0g6TWmQYvjKRnljRULLWUVJGy8Uvu0NEL/5frY2/t4= +go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.step.sm/cli-utils v0.4.1 h1:QztRUhGYjOPM1I2Nmi7V6XejQyVtcESmo+sbegxvX7Q= go.step.sm/cli-utils v0.4.1/go.mod h1:hWYVOSlw8W9Pd+BwIbs/aftVVMRms3EG7Q2qLRwc0WA= go.step.sm/crypto v0.9.0 h1:q2AllTSnVj4NRtyEPkGW2ohArLmbGbe6ZAL/VIOKDzA= @@ -1247,6 +1266,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= @@ -1486,8 +1506,9 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +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/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/opentelemetry/README.md b/modules/caddyhttp/opentelemetry/README.md new file mode 100644 index 000000000000..1a3516484795 --- /dev/null +++ b/modules/caddyhttp/opentelemetry/README.md @@ -0,0 +1,59 @@ +# OpenTelemetry module + +This module provides integration with OpenTelemetry tracing facilities. It is implemented +as `caddyhttp.MiddlewareHandler` and can be chained into a list of other handlers. + +When enabled, it propagates an existing tracing context or will init a new one otherwise. + +It is based on `https://github.com/open-telemetry/opentelemetry-go`. + +## Configuration + +### Environment variables + +It can be configured using environment variables defined +by [spec](https://github.com/open-telemetry/opentelemetry-specification). + +IMPORTANT: Please, consider the version +of [https://github.com/open-telemetry/opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go). Some parts +of the specification may be not implemented yet. + +Required by module environment variables are: OTEL_EXPORTER_OTLP_PROTOCOL/OTEL_EXPORTER_OTLP_TRACES_PROTOCOL and +OTEL_PROPAGATORS. + +If neither OTEL_EXPORTER_OTLP_INSECURE nor OTEL_EXPORTER_OTLP_SPAN_INSECURE is provided, then: + +1. If OTEL_EXPORTER_OTLP_CERTIFICATE or OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE are specified they will be used for TLS. +2. Else if both OTEL_EXPORTER_OTLP_CERTIFICATE and OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE are not specified, then default + TLS with the default `tls.Config` config will be used for an exporter. + +For the exporter configuration details, please +see [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/a4440931b522c7351b0485ff4899f786b4ff4459/specification/protocol/exporter.md) +. + +Example: + +```bash +export OTEL_EXPORTER_OTLP_HEADERS="myAuthHeader=myToken" +export OTEL_EXPORTER_OTLP_PROTOCOL=grpc +export OTEL_PROPAGATORS=tracecontext,baggage +export OTEL_EXPORTER_OTLP_SPAN_INSECURE=false +export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=localhost:12345 +``` + +### Caddy file configuration + +Here is an example of **Caddyfile**: + +``` +handle /myHanlder { + opentelemetry { + span_name my-span + } + reverse_proxy 127.0.0.1:8081 +} +``` + +Please check span +naming [guideline](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span) +. \ No newline at end of file diff --git a/modules/caddyhttp/opentelemetry/module.go b/modules/caddyhttp/opentelemetry/module.go new file mode 100644 index 000000000000..ed67db4912b1 --- /dev/null +++ b/modules/caddyhttp/opentelemetry/module.go @@ -0,0 +1,123 @@ +package opentelemetry + +import ( + "errors" + "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(OpenTelemetry{}) + httpcaddyfile.RegisterHandlerDirective("opentelemetry", parseCaddyfile) +} + +// OpenTelemetry implements an HTTP handler that adds support for the opentelemetry tracing. +// It is responsible for the injection and propagation of the tracing contexts. +// OpenTelemetry module can be configured via environment variables https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md. Some values can be overwritten with values from the configuration file. +type OpenTelemetry struct { + // SpanName is a span name. It SHOULD follow the naming guideline https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#span + SpanName string `json:"span_name"` + + // otel implements opentelemetry related logic. + otel openTelemetryWrapper + + logger *zap.Logger +} + +// CaddyModule returns the Caddy module information. +func (OpenTelemetry) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.opentelemetry", + New: func() caddy.Module { return new(OpenTelemetry) }, + } +} + +// Provision implements caddy.Provisioner. +func (ot *OpenTelemetry) 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 *OpenTelemetry) Cleanup() error { + if err := ot.otel.cleanup(ot.logger); err != nil { + return fmt.Errorf("tracerProvider shutdown: %w", err) + } + return nil +} + +// Validate implements caddy.Validator. +func (ot *OpenTelemetry) Validate() error { + if ot.otel.tracer == nil { + return errors.New("openTelemetry tracer is nil") + } + + return nil +} + +// ServeHTTP implements caddyhttp.MiddlewareHandler. +func (ot *OpenTelemetry) 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. +func (ot *OpenTelemetry) 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_name": &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 OpenTelemetry + err := m.UnmarshalCaddyfile(h.Dispenser) + return &m, err +} + +// Interface guards +var ( + _ caddy.Provisioner = (*OpenTelemetry)(nil) + _ caddy.Validator = (*OpenTelemetry)(nil) + _ caddyhttp.MiddlewareHandler = (*OpenTelemetry)(nil) + _ caddyfile.Unmarshaler = (*OpenTelemetry)(nil) +) diff --git a/modules/caddyhttp/opentelemetry/module_test.go b/modules/caddyhttp/opentelemetry/module_test.go new file mode 100644 index 000000000000..814b87c02e6c --- /dev/null +++ b/modules/caddyhttp/opentelemetry/module_test.go @@ -0,0 +1,237 @@ +package opentelemetry + +import ( + "context" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" +) + +func TestOpenTelemetry_UnmarshalCaddyfile(t *testing.T) { + tests := []struct { + name string + spanName string + d *caddyfile.Dispenser + wantErr bool + }{ + { + name: "Full config", + spanName: "my-span", + d: caddyfile.NewTestDispenser(` +opentelemetry { + span_name my-span +}`), + wantErr: false, + }, + { + name: "Only span name in the config", + spanName: "my-span", + d: caddyfile.NewTestDispenser(` +opentelemetry { + span_name my-span +}`), + wantErr: false, + }, + { + name: "Empty config", + d: caddyfile.NewTestDispenser(` +opentelemetry { +}`), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ot := &OpenTelemetry{} + 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 TestOpenTelemetry_UnmarshalCaddyfile_Error(t *testing.T) { + tests := []struct { + name string + d *caddyfile.Dispenser + wantErr bool + }{ + { + name: "Unknown parameter", + d: caddyfile.NewTestDispenser(` + opentelemetry { + foo bar + }`), + wantErr: true, + }, + { + name: "Missed argument", + d: caddyfile.NewTestDispenser(` +opentelemetry { + span_name +}`), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ot := &OpenTelemetry{} + if err := ot.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalCaddyfile() error = %v, wantErrType %v", err, tt.wantErr) + } + }) + } +} + +func TestOpenTelemetry_Provision(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := th.SetEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + + defer func() { + if err := th.UnsetEnv(); err != nil { + t.Errorf("Environment variable unset error: %v", err) + } + }() + + ot := &OpenTelemetry{} + + if err := ot.Provision(ctx); err != nil { + t.Errorf("Provision() error = %v", err) + } +} + +func TestOpenTelemetry_Provision_WithoutEnvironmentVariables(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + ot := &OpenTelemetry{} + + if err := ot.Provision(ctx); err == nil { + t.Errorf("Provision() error should not be nil") + } +} + +func TestOpenTelemetry_ServeHTTP_Propagation_Without_Initial_Headers(t *testing.T) { + if err := th.SetEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + + defer func() { + if err := th.UnsetEnv(); err != nil { + t.Errorf("Environment variable unset error: %v", err) + } + }() + + ot := &OpenTelemetry{ + 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 TestOpenTelemetry_ServeHTTP_Propagation_With_Initial_Headers(t *testing.T) { + if err := th.SetEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + + defer func() { + if err := th.UnsetEnv(); err != nil { + t.Errorf("Environment variable unset error: %v", err) + } + }() + + ot := &OpenTelemetry{ + 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) + } +} + +type testHelper struct { + SetEnv func() error + UnsetEnv func() error +} + +var th = testHelper{ + SetEnv: func() error { + if err := os.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc"); err != nil { + return err + } + + if err := os.Setenv("OTEL_PROPAGATORS", "tracecontext,baggage"); err != nil { + return err + } + + return nil + }, + UnsetEnv: func() error { + if err := os.Unsetenv("OTEL_EXPORTER_OTLP_PROTOCOL"); err != nil { + return err + } + + if err := os.Unsetenv("OTEL_PROPAGATORS"); err != nil { + return err + } + + return nil + }, +} diff --git a/modules/caddyhttp/opentelemetry/tracer.go b/modules/caddyhttp/opentelemetry/tracer.go new file mode 100644 index 000000000000..9829a1d2c63e --- /dev/null +++ b/modules/caddyhttp/opentelemetry/tracer.go @@ -0,0 +1,225 @@ +package opentelemetry + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "go.opentelemetry.io/otel/attribute" + "net/http" + "os" + "strings" + + caddycmd "github.com/caddyserver/caddy/v2/cmd" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "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.4.0" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" + "google.golang.org/grpc/credentials" +) + +const ( + envOtelPropagators = "OTEL_PROPAGATORS" + + envOtelExporterOtlpProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL" + envOtelExporterOtlpTracesProtocol = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" + + envOtelExporterOtlpCertificate = "OTEL_EXPORTER_OTLP_CERTIFICATE" + envOtelExporterOtlpTracesCertificate = "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE" + + envOtelExporterOtlpInsecure = "OTEL_EXPORTER_OTLP_INSECURE" + envOtelExporterOtlpSpanInsecure = "OTEL_EXPORTER_OTLP_SPAN_INSECURE" + + webEngineName = "Caddy" + defaultSpanName = "handler" +) + +var ( + ErrUnspecifiedTracesProtocol = errors.New("unspecified opentelemetry traces protocol") + ErrNonSupportedTracesProtocol = errors.New("non supported opentelemetry traces protocol") + ErrUnspecifiedPropagators = errors.New("unspecified opentelemtry propagators") +) + +// openTelemetryWrapper is responsible for the tracing injection, extraction and propagation. +type openTelemetryWrapper struct { + tracer trace.Tracer + propagators propagation.TextMapPropagator + + 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(ctx, webEngineName, caddycmd.CaddyVersion()) + if err != nil { + return ot, fmt.Errorf("creating resource error: %w", err) + } + + // + traceExporter, err := ot.getTracerExporter(ctx) + if err != nil { + return ot, fmt.Errorf("creating trace exporter error: %w", err) + } + + // handle propagators related configuration, because it is not supported by opentelemetry lib yet. + // Please check status of https://github.com/open-telemetry/opentelemetry-go/issues/1698. + propagators := os.Getenv(envOtelPropagators) + if propagators == "" { + return ot, ErrUnspecifiedPropagators + } + + ot.propagators = ot.getPropagators(propagators) + + // create a tracer + ot.tracer = globalTracerProvider.getTracerProvider( + sdktrace.WithBatcher(traceExporter), + sdktrace.WithResource(res), + ).Tracer("github.com/caddyserver/caddy/v2/modules/caddyhttp/opentelemetry") + + return ot, nil +} + +// ServeHTTP extract current tracing context or create a new one, then method propagates it to the wrapped next handler. +func (ot *openTelemetryWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + + commonLabels := []attribute.KeyValue{ + attribute.String("http.method", r.Method), + attribute.String("http.scheme", r.URL.Scheme), + attribute.String("http.host", r.Host), + attribute.String("http.user_agent", r.UserAgent()), + } + + // It will be default span kind as for now. Proper span kind (Span.Kind.LOAD_BALANCER (PROXY/SIDECAR)) is being discussed here https://github.com/open-telemetry/opentelemetry-specification/issues/51. + ctx, span := ot.tracer.Start( + ot.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)), + ot.spanName, + trace.WithAttributes(commonLabels...), + ) + defer span.End() + + ot.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header)) + + return next.ServeHTTP(w, r) +} + +// 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( + ctx context.Context, + webEngineName, + webEngineVersion string, +) (*resource.Resource, error) { + option := resource.WithAttributes( + semconv.WebEngineNameKey.String(webEngineName), + semconv.WebEngineVersionKey.String(webEngineVersion), + ) + + caddyResource, err := resource.New(ctx, + option, + resource.WithFromEnv(), + ) + + if err != nil { + return nil, err + } + + return resource.Merge(resource.Default(), caddyResource) +} + +// getTracerExporter returns protocol specific exporter or error if the protocol is not supported by current module implementation. +// +// If opentelemetry is not configured with "insecure" parameter and certificate related headers missed, +// then default TLS with default `tls.Config` config will be used. +func (ot *openTelemetryWrapper) getTracerExporter(ctx context.Context) (*otlptrace.Exporter, error) { + exporterTracesProtocol := ot.getTracesProtocolFromEnv() + + switch exporterTracesProtocol { + case "grpc": + var opts []otlptracegrpc.Option + if ot.getInsecureFromEnv() { + opts = append(opts, otlptracegrpc.WithInsecure()) + } else { + if !ot.isCertificateHeaderSet() { + var tlsConf tls.Config + transportCredentials := credentials.NewTLS(&tlsConf) + opts = append(opts, otlptracegrpc.WithTLSCredentials(transportCredentials)) + } + } + + return otlptracegrpc.New(ctx, opts...) + case "": + return nil, ErrUnspecifiedTracesProtocol + default: + return nil, fmt.Errorf("%w: tracesProtocol %s", ErrNonSupportedTracesProtocol, exporterTracesProtocol) + } +} + +// getTracesProtocolFromEnv returns opentelemetry exporter otlp protocol, if it is specified via environment variable or empty string if not. +func (ot *openTelemetryWrapper) getTracesProtocolFromEnv() string { + protocol := os.Getenv(envOtelExporterOtlpTracesProtocol) + if protocol == "" { + protocol = os.Getenv(envOtelExporterOtlpProtocol) + } + + return protocol +} + +// getInsecureFromEnv returns value of "insecure" option if it was specified by environment variable. +func (ot *openTelemetryWrapper) getInsecureFromEnv() bool { + insecure := os.Getenv(envOtelExporterOtlpSpanInsecure) + if insecure == "" { + insecure = os.Getenv(envOtelExporterOtlpInsecure) + } + + return strings.ToLower(insecure) == "true" +} + +func (ot *openTelemetryWrapper) isCertificateHeaderSet() bool { + return os.Getenv(envOtelExporterOtlpCertificate) != "" || os.Getenv(envOtelExporterOtlpTracesCertificate) != "" +} + +// getPropagators deduplicate propagators, according to the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration. +// Parameter propagators is a "," separated string, ex: "baggage,tracecontext". +// Current implementation supports only "baggage" and "tracecontext" values. +func (ot *openTelemetryWrapper) getPropagators(propagators string) propagation.TextMapPropagator { + // deduplicationMap filters duplicated propagator + deduplicationMap := make(map[string]struct{}) + + // store unique values + var propagatorsList []propagation.TextMapPropagator + + for _, v := range strings.Split(propagators, ",") { + propagatorName := strings.TrimSpace(v) + if _, ok := deduplicationMap[propagatorName]; !ok { + deduplicationMap[propagatorName] = struct{}{} + switch propagatorName { + case "baggage": + propagatorsList = append(propagatorsList, propagation.Baggage{}) + case "tracecontext": + propagatorsList = append(propagatorsList, propagation.TraceContext{}) + } + } + } + + return propagation.NewCompositeTextMapPropagator(propagatorsList...) +} diff --git a/modules/caddyhttp/opentelemetry/tracer_test.go b/modules/caddyhttp/opentelemetry/tracer_test.go new file mode 100644 index 000000000000..12f5ed78ff8c --- /dev/null +++ b/modules/caddyhttp/opentelemetry/tracer_test.go @@ -0,0 +1,138 @@ +package opentelemetry + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/caddyserver/caddy/v2" +) + +func TestOpenTelemetry_newOpenTelemetryWrapper(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := th.SetEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + defer func() { + if err := th.UnsetEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + }() + + var otw openTelemetryWrapper + var err error + + if otw, err = newOpenTelemetryWrapper(ctx, + "", + ); err != nil { + t.Errorf("newOpenTelemetryWrapper() error = %v", err) + t.FailNow() + } + + if otw.tracer == nil { + t.Errorf("Tracer should not be empty") + } + + if otw.propagators == nil { + t.Errorf("Propagators should not be empty") + } +} + +func TestOpenTelemetry_newOpenTelemetryWrapper_Error(t *testing.T) { + tests := []struct { + name string + wantErrType error + setEnv func() error + unsetEnv func() error + }{ + { + name: "With OTEL_EXPORTER_OTLP_PROTOCOL environment variables only", + setEnv: func() error { + return os.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc") + }, + unsetEnv: func() error { + return os.Unsetenv("OTEL_EXPORTER_OTLP_PROTOCOL") + }, + wantErrType: ErrUnspecifiedPropagators, + }, + { + name: "With OTEL_PROPAGATORS environment variables only", + setEnv: func() error { + return os.Setenv("OTEL_PROPAGATORS", "tracecontext,baggage") + }, + unsetEnv: func() error { + return os.Unsetenv("OTEL_PROPAGATORS") + }, + wantErrType: ErrUnspecifiedTracesProtocol, + }, + { + name: "Not supported protocol", + setEnv: func() error { + return os.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "ftp") + }, + unsetEnv: func() error { + return os.Unsetenv("OTEL_EXPORTER_OTLP_PROTOCOL") + }, + wantErrType: ErrNonSupportedTracesProtocol, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) + defer cancel() + + if err := tt.setEnv(); err != nil { + t.Errorf("Environment variable set error: %v", err) + } + defer func() { + if err := tt.unsetEnv(); err != nil { + t.Errorf("Environment variable unset error: %v", err) + } + }() + + _, err := newOpenTelemetryWrapper(ctx, + "", + ) + + if !errors.Is(err, tt.wantErrType) { + t.Errorf("newOpenTelemetryWrapper() error is %v, expected %v", err, tt.wantErrType) + } + }) + } +} + +func Test_openTelemetryWrapper_newResource_WithServiceName(t *testing.T) { + err := os.Setenv("OTEL_SERVICE_NAME", "MyService") + defer os.Unsetenv("OTEL_SERVICE_NAME") + + res, err := (&openTelemetryWrapper{}).newResource(context.Background(), "TestEngine", "Version 1") + + if err != nil { + t.Errorf("can not create resource: %v", err) + } + + const expectedAttributesNumber = 6 + if len(res.Attributes()) != expectedAttributesNumber { + t.Errorf("resource should have %d attributes, has : %v", expectedAttributesNumber, len(res.Attributes())) + } + + attributesMap := make(map[string]string) + for i := 0; i < expectedAttributesNumber; i++ { + attributesMap[string(res.Attributes()[i].Key)] = res.Attributes()[i].Value.AsString() + } + + for k, v := range map[string]string{ + "telemetry.sdk.language": "go", + "telemetry.sdk.name": "opentelemetry", + "webengine.version": "Version 1", + "webengine.name": "TestEngine", + "service.name": "MyService", + } { + if attributesMap[k] != v { + t.Errorf("attribute %v is %v, expeted %v", k, attributesMap[k], v) + } + } +} diff --git a/modules/caddyhttp/opentelemetry/tracerprovider.go b/modules/caddyhttp/opentelemetry/tracerprovider.go new file mode 100644 index 000000000000..72a915615b4b --- /dev/null +++ b/modules/caddyhttp/opentelemetry/tracerprovider.go @@ -0,0 +1,64 @@ +package opentelemetry + +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("forceFlush error: " + err.Error()) + } + + // 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 + t.tracerProvidersCounter = 0 + } + + return nil +} diff --git a/modules/caddyhttp/opentelemetry/tracerprovider_test.go b/modules/caddyhttp/opentelemetry/tracerprovider_test.go new file mode 100644 index 000000000000..c3e8b3ead7c0 --- /dev/null +++ b/modules/caddyhttp/opentelemetry/tracerprovider_test.go @@ -0,0 +1,42 @@ +package opentelemetry + +import ( + "go.uber.org/zap" + "testing" +) + +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") + } +} diff --git a/modules/caddyhttp/standard/imports.go b/modules/caddyhttp/standard/imports.go index 0e2203c1ebd7..79dd8aaf01eb 100644 --- a/modules/caddyhttp/standard/imports.go +++ b/modules/caddyhttp/standard/imports.go @@ -11,6 +11,7 @@ import ( _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map" + _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/opentelemetry" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/push" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody" _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"