diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..f98a72a --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,12 @@ +with-expecter: true +disable-version-string: true +inpackage: false +mockname: "{{.InterfaceName}}" +filename: "{{.InterfaceNameSnake}}.go" +outpkg: "mocks" +dir: "{{.InterfaceDirRelative}}/mocks" + +packages: + "flamingo.me/opentelemetry": + interfaces: + Shutdowner: diff --git a/Readme.md b/Readme.md index 2540cfc..f936063 100644 --- a/Readme.md +++ b/Readme.md @@ -47,7 +47,7 @@ Flamingo's `URLPrefixSampler` and config from `flamingo.opencensus.tracing.sampl Before you can create your own spans, you have to initialize a tracer: ```go -tracer := otel.Tracer("my-app") +var tracer = otel.Tracer("my-app", trace.WithInstrumentationVersion("1.2.3")) ``` Now you can create a span based on a `context.Context`. This will automatically attach all tracing-relevant @@ -55,10 +55,10 @@ information (e.g. trace-ID) to the span. ```go func doSomething(ctx context.Context) { -ctx, span := tracer.Start(ctx, "my-span") -defer span.End() - -// do some work to track with my-span + ctx, span := tracer.Start(ctx, "my-span") + defer span.End() + + // do some work to track with my-span } ``` @@ -70,14 +70,15 @@ official [OpenTelemetry documentation](https://opentelemetry.io/docs/instrumenta To collect your own metrics, you have to initialize a meter: ```go -meter := otel.Meter("my-app") +var meter = otel.Meter("my-app", metric.WithInstrumentationVersion("1.2.3")) ``` Now you can create a new metric, e.g. a counter: ```go -counter, _ := meter.Int64Counter("my.count", -metric.WithDescription("count of something"), +counter, _ := meter.Int64Counter("my.count", + metric.WithDescription("count of something"), + metric.WithUnit("{something}") ) counter.Add(ctx, 1) diff --git a/go.mod b/go.mod index f2d1e1d..4bd4e18 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/redis/go-redis/v9 v9.6.1 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/zemirco/memorystore v0.0.0-20160308183530-ecd57e5134f6 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect diff --git a/go.sum b/go.sum index c0789cb..5617331 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -133,6 +131,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -143,18 +143,12 @@ github.com/zemirco/memorystore v0.0.0-20160308183530-ecd57e5134f6 h1:j+ZgVPhfLkC github.com/zemirco/memorystore v0.0.0-20160308183530-ecd57e5134f6/go.mod h1:PLhuixMlky6sB4/LEnpp1//u2BcRF2pKUYXLMVyOrIc= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0 h1:2JydY5UiDpqvj2p7sO9bgHuhTy4hgTZ0ymehdq/Ob0Q= -go.opentelemetry.io/contrib/instrumentation/runtime v0.45.0/go.mod h1:ch3a5QxOqVWxas4CzjCFFOOQe+7HgAXC/N1oVxS9DK4= go.opentelemetry.io/contrib/instrumentation/runtime v0.54.0 h1:KD+8SJvRaW9n0vE0UgkytT207J3CmV1hGf9GYYU73ns= go.opentelemetry.io/contrib/instrumentation/runtime v0.54.0/go.mod h1:/CsTuLR28IN3Vn13YEc72HljfHiGOMXiCbl4xiCSDhA= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/bridge/opencensus v0.45.0 h1:kEOlv9Exuv3J8GCf1nLMHfrTPGnZOuIkN8YlRM14TtQ= -go.opentelemetry.io/otel/bridge/opencensus v0.45.0/go.mod h1:tkVMJeFOr43+zzwbxtIWsNcCCDT7rI5/c9rhMfMIENg= go.opentelemetry.io/otel/bridge/opencensus v1.30.0 h1:F6WsF4aSV6g6IBiK4NZ8gWNHqFjtwxrEsUnrYEGyBSw= go.opentelemetry.io/otel/bridge/opencensus v1.30.0/go.mod h1:3FOD7DbLQhWLxwnzbYoT1oHucvoQIqLXHez1u2KIxMc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= diff --git a/mocks/shutdowner.go b/mocks/shutdowner.go new file mode 100644 index 0000000..972840f --- /dev/null +++ b/mocks/shutdowner.go @@ -0,0 +1,82 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// Shutdowner is an autogenerated mock type for the Shutdowner type +type Shutdowner struct { + mock.Mock +} + +type Shutdowner_Expecter struct { + mock *mock.Mock +} + +func (_m *Shutdowner) EXPECT() *Shutdowner_Expecter { + return &Shutdowner_Expecter{mock: &_m.Mock} +} + +// Shutdown provides a mock function with given fields: ctx +func (_m *Shutdowner) Shutdown(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Shutdown") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Shutdowner_Shutdown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Shutdown' +type Shutdowner_Shutdown_Call struct { + *mock.Call +} + +// Shutdown is a helper method to define mock.On call +// - ctx context.Context +func (_e *Shutdowner_Expecter) Shutdown(ctx interface{}) *Shutdowner_Shutdown_Call { + return &Shutdowner_Shutdown_Call{Call: _e.mock.On("Shutdown", ctx)} +} + +func (_c *Shutdowner_Shutdown_Call) Run(run func(ctx context.Context)) *Shutdowner_Shutdown_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Shutdowner_Shutdown_Call) Return(_a0 error) *Shutdowner_Shutdown_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Shutdowner_Shutdown_Call) RunAndReturn(run func(context.Context) error) *Shutdowner_Shutdown_Call { + _c.Call.Return(run) + return _c +} + +// NewShutdowner creates a new instance of Shutdowner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewShutdowner(t interface { + mock.TestingT + Cleanup(func()) +}) *Shutdowner { + mock := &Shutdowner{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/module.go b/module.go index cc219cc..d439e94 100644 --- a/module.go +++ b/module.go @@ -1,5 +1,7 @@ package opentelemetry +//go:generate go run github.com/vektra/mockery/v2@v2.45.1 + import ( "context" "fmt" @@ -69,6 +71,8 @@ func (m *Module) Inject( func (m *Module) Configure(injector *dingo.Injector) { http.DefaultTransport = &correlationIDInjector{next: otelhttp.NewTransport(http.DefaultTransport)} + flamingo.BindEventSubscriber(injector).To(new(Listener)) + m.initTraces() m.initMetrics(injector) } diff --git a/module_test.go b/module_test.go new file mode 100644 index 0000000..d810e24 --- /dev/null +++ b/module_test.go @@ -0,0 +1,28 @@ +package opentelemetry_test + +import ( + "testing" + + "flamingo.me/dingo" + "flamingo.me/flamingo/v3/framework/config" + "flamingo.me/flamingo/v3/framework/flamingo" + + "flamingo.me/opentelemetry" +) + +type ( + loggerModule struct{} +) + +// Configure DI +func (m *loggerModule) Configure(injector *dingo.Injector) { + injector.Bind(new(flamingo.Logger)).To(new(flamingo.NullLogger)) +} + +func TestModule_Configure(t *testing.T) { + t.Parallel() + + if err := config.TryModules(nil, new(loggerModule), new(opentelemetry.Module)); err != nil { + t.Error(err) + } +} diff --git a/shutdown.go b/shutdown.go new file mode 100644 index 0000000..74992f0 --- /dev/null +++ b/shutdown.go @@ -0,0 +1,56 @@ +package opentelemetry + +import ( + "context" + + "flamingo.me/flamingo/v3/framework/flamingo" + "go.opentelemetry.io/otel" +) + +type ( + Shutdowner interface { + Shutdown(ctx context.Context) error + } + + Listener struct { + logger flamingo.Logger + } +) + +// Inject dependencies +func (l *Listener) Inject( + logger flamingo.Logger, +) *Listener { + l.logger = logger + + return l +} + +func (l *Listener) Notify(ctx context.Context, event flamingo.Event) { + if _, ok := event.(*flamingo.ShutdownEvent); ok { + tp := otel.GetTracerProvider() + if s, ok := tp.(Shutdowner); ok { + l.shutdown(ctx, s) + } + + mp := otel.GetMeterProvider() + if s, ok := mp.(Shutdowner); ok { + l.shutdown(ctx, s) + } + } +} + +func (l *Listener) shutdown(ctx context.Context, s Shutdowner) { + l.log().Debugf("Shutdown OpenTelemetry: %T", s) + + err := s.Shutdown(ctx) + if err != nil { + l.log().Error("", err) + } +} + +func (l *Listener) log() flamingo.Logger { + return l.logger. + WithField(flamingo.LogKeyModule, "opentelemetry"). + WithField(flamingo.LogKeyCategory, "Shutdown") +} diff --git a/shutdown_test.go b/shutdown_test.go new file mode 100644 index 0000000..aff651a --- /dev/null +++ b/shutdown_test.go @@ -0,0 +1,77 @@ +package opentelemetry_test + +import ( + "context" + "errors" + "testing" + + "flamingo.me/flamingo/v3/framework/flamingo" + "go.opentelemetry.io/otel" + noopMetric "go.opentelemetry.io/otel/metric/noop" + noopTrace "go.opentelemetry.io/otel/trace/noop" + + "flamingo.me/opentelemetry" + "flamingo.me/opentelemetry/mocks" +) + +type ( + tracerProvider struct { + noopTrace.TracerProvider + mocks.Shutdowner + } + + meterProvider struct { + noopMetric.MeterProvider + mocks.Shutdowner + } +) + +var errShutdown = errors.New("shutdown error") + +func TestListener_Notify(t *testing.T) { //nolint:tparallel // no parallel subtests possible because of global state manipulation + t.Parallel() + + type args struct { + event flamingo.Event + } + + tests := []struct { + name string + args args + traceShutdownError error + meterShutdownError error + }{ + { + name: "shutdown meter and tracer successfully", + args: args{ + event: new(flamingo.ShutdownEvent), + }, + traceShutdownError: nil, + meterShutdownError: nil, + }, + { + name: "error on shutdown meter and tracer", + args: args{ + event: new(flamingo.ShutdownEvent), + }, + traceShutdownError: errShutdown, + meterShutdownError: errShutdown, + }, + } + + for _, tt := range tests { //nolint:paralleltest // no parallel test possible because of global state manipulation + t.Run(tt.name, func(t *testing.T) { + tp := new(tracerProvider) + tp.Shutdowner.EXPECT().Shutdown(context.Background()).Once().Return(tt.traceShutdownError) + otel.SetTracerProvider(tp) + + mp := new(meterProvider) + mp.Shutdowner.EXPECT().Shutdown(context.Background()).Once().Return(tt.meterShutdownError) + otel.SetMeterProvider(mp) + + l := new(opentelemetry.Listener).Inject(new(flamingo.NullLogger)) + + l.Notify(context.Background(), tt.args.event) + }) + } +} diff --git a/version.go b/version.go deleted file mode 100644 index 244eb94..0000000 --- a/version.go +++ /dev/null @@ -1,10 +0,0 @@ -package opentelemetry - -func Version() string { - return "0.1.0" -} - -// SemVersion is the semantic version to be supplied to tracer/meter creation. -func SemVersion() string { - return "semver:" + Version() -}