diff --git a/pkg/appbuilder/otel.go b/pkg/appbuilder/otel.go index 4b60541..9cbcb7d 100644 --- a/pkg/appbuilder/otel.go +++ b/pkg/appbuilder/otel.go @@ -18,57 +18,70 @@ import ( "go.opentelemetry.io/otel/trace" ) -type otelOptions struct { - initPropogator func(context.Context) (propagation.TextMapPropagator, error) - initTracerProvider func(context.Context) (trace.TracerProvider, error) - initMeterProvider func(context.Context) (metric.MeterProvider, error) - initLoggerProvider func(context.Context) (log.LoggerProvider, error) +// TextMapPropagatorInitializer +type TextMapPropagatorInitializer interface { + InitTextMapPropogator(context.Context) (propagation.TextMapPropagator, error) } -// OTelOption -type OTelOption func(*otelOptions) - -// OTelTextMapPropogator -func OTelTextMapPropogator(f func(context.Context) (propagation.TextMapPropagator, error)) OTelOption { - return func(oo *otelOptions) { - oo.initPropogator = f - } +// TracerProviderInitializer +type TracerProviderInitializer interface { + InitTracerProvider(context.Context) (trace.TracerProvider, error) } -// OTelTracerProvider -func OTelTracerProvider(f func(context.Context) (trace.TracerProvider, error)) OTelOption { - return func(oo *otelOptions) { - oo.initTracerProvider = f - } +// MeterProviderInitializer +type MeterProviderInitializer interface { + InitMeterProvider(context.Context) (metric.MeterProvider, error) } -// OTelMeterProvider -func OTelMeterProvider(f func(context.Context) (metric.MeterProvider, error)) OTelOption { - return func(oo *otelOptions) { - oo.initMeterProvider = f - } +// LoggerProviderInitializer +type LoggerProviderInitializer interface { + InitLoggerProvider(context.Context) (log.LoggerProvider, error) } -// OTelLoggerProvider -func OTelLoggerProvider(f func(context.Context) (log.LoggerProvider, error)) OTelOption { - return func(oo *otelOptions) { - oo.initLoggerProvider = f - } +// OTelInitializer +type OTelInitializer interface { + TextMapPropagatorInitializer + TracerProviderInitializer + MeterProviderInitializer + LoggerProviderInitializer } -// WithOTel -func WithOTel[T any](builder bedrock.AppBuilder[T], opts ...OTelOption) bedrock.AppBuilder[T] { - oo := &otelOptions{} - for _, opt := range opts { - opt(oo) - } - +// OTel +func OTel[T OTelInitializer](builder bedrock.AppBuilder[T]) bedrock.AppBuilder[T] { return bedrock.AppBuilderFunc[T](func(ctx context.Context, cfg T) (bedrock.App, error) { fs := []func(context.Context) error{ - initTextMapPropogator(oo), - initTracerProvider(oo), - initMeterProvider(oo), - initLoggerProvider(oo), + func(ctx context.Context) error { + tmp, err := cfg.InitTextMapPropogator(ctx) + if err != nil || tmp == nil { + return err + } + otel.SetTextMapPropagator(tmp) + return nil + }, + func(ctx context.Context) error { + tp, err := cfg.InitTracerProvider(ctx) + if err != nil || tp == nil { + return err + } + otel.SetTracerProvider(tp) + return nil + }, + func(ctx context.Context) error { + mp, err := cfg.InitMeterProvider(ctx) + if err != nil || mp == nil { + return err + } + otel.SetMeterProvider(mp) + return nil + }, + func(ctx context.Context) error { + lp, err := cfg.InitLoggerProvider(ctx) + if err != nil || lp == nil { + return err + } + global.SetLoggerProvider(lp) + return nil + }, } for _, f := range fs { @@ -81,67 +94,3 @@ func WithOTel[T any](builder bedrock.AppBuilder[T], opts ...OTelOption) bedrock. return builder.Build(ctx, cfg) }) } - -func initTextMapPropogator(oo *otelOptions) func(context.Context) error { - return func(ctx context.Context) error { - if oo.initPropogator == nil { - return nil - } - - p, err := oo.initPropogator(ctx) - if err != nil { - return err - } - - otel.SetTextMapPropagator(p) - return nil - } -} - -func initTracerProvider(oo *otelOptions) func(context.Context) error { - return func(ctx context.Context) error { - if oo.initTracerProvider == nil { - return nil - } - - tp, err := oo.initTracerProvider(ctx) - if err != nil { - return err - } - - otel.SetTracerProvider(tp) - return nil - } -} - -func initMeterProvider(oo *otelOptions) func(context.Context) error { - return func(ctx context.Context) error { - if oo.initMeterProvider == nil { - return nil - } - - mp, err := oo.initMeterProvider(ctx) - if err != nil { - return err - } - - otel.SetMeterProvider(mp) - return nil - } -} - -func initLoggerProvider(oo *otelOptions) func(context.Context) error { - return func(ctx context.Context) error { - if oo.initLoggerProvider == nil { - return nil - } - - lp, err := oo.initLoggerProvider(ctx) - if err != nil { - return err - } - - global.SetLoggerProvider(lp) - return nil - } -} diff --git a/pkg/appbuilder/otel_example_test.go b/pkg/appbuilder/otel_example_test.go deleted file mode 100644 index e8986c1..0000000 --- a/pkg/appbuilder/otel_example_test.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright (c) 2024 Z5Labs and Contributors -// -// This software is released under the MIT License. -// https://opensource.org/licenses/MIT - -package appbuilder - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - - "github.com/z5labs/bedrock" - - "go.opentelemetry.io/contrib/bridges/otelslog" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/baggage" - "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" - "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" - "go.opentelemetry.io/otel/log" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/propagation" - sdklog "go.opentelemetry.io/otel/sdk/log" - sdkmetric "go.opentelemetry.io/otel/sdk/metric" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - "go.opentelemetry.io/otel/trace" -) - -type appFunc func(context.Context) error - -func (f appFunc) Run(ctx context.Context) error { - return f(ctx) -} - -func ExampleWithOTel_textMapPropogator() { - type MyConfig struct{} - - app := appFunc(func(ctx context.Context) error { - return nil - }) - - carrier := make(propagation.MapCarrier) - var builder bedrock.AppBuilder[MyConfig] = bedrock.AppBuilderFunc[MyConfig](func(ctx context.Context, cfg MyConfig) (bedrock.App, error) { - tmp := otel.GetTextMapPropagator() - tmp.Inject(ctx, carrier) - return app, nil - }) - - builder = WithOTel( - builder, - OTelTextMapPropogator(func(ctx context.Context) (propagation.TextMapPropagator, error) { - tmp := propagation.Baggage{} - return tmp, nil - }), - ) - - m, _ := baggage.NewMember("hello", "world") - b, _ := baggage.New(m) - ctx := baggage.ContextWithBaggage(context.Background(), b) - - _, err := builder.Build(ctx, MyConfig{}) - if err != nil { - fmt.Println(err) - return - } - - ctx = propagation.Baggage{}.Extract(context.Background(), carrier) - b = baggage.FromContext(ctx) - m = b.Member("hello") - fmt.Println(m.Value()) - // Output: world -} - -func ExampleWithOTel_tracerProvider() { - type MyConfig struct{} - - app := appFunc(func(ctx context.Context) error { - return nil - }) - - var builder bedrock.AppBuilder[MyConfig] = bedrock.AppBuilderFunc[MyConfig](func(ctx context.Context, cfg MyConfig) (bedrock.App, error) { - _, span := otel.Tracer("builder").Start(ctx, "build") - defer span.End() - return app, nil - }) - - var tp *sdktrace.TracerProvider - var buf bytes.Buffer - builder = WithOTel( - builder, - OTelTracerProvider(func(ctx context.Context) (trace.TracerProvider, error) { - // NOTE: this is only for example purposes. DO NOT USE IN PRODUCTION!!! - exp, err := stdouttrace.New( - stdouttrace.WithWriter(&buf), - ) - if err != nil { - return nil, err - } - - sp := sdktrace.NewSimpleSpanProcessor(exp) - - tp = sdktrace.NewTracerProvider( - sdktrace.WithSpanProcessor(sp), - ) - return tp, nil - }), - ) - - _, err := builder.Build(context.Background(), MyConfig{}) - if err != nil { - fmt.Println(err) - return - } - - // Ensure that the builder trace is flushed to buf - err = tp.Shutdown(context.Background()) - if err != nil { - fmt.Println(err) - return - } - - b, err := io.ReadAll(&buf) - if err != nil { - fmt.Println(err) - return - } - - var m map[string]any - err = json.Unmarshal(b, &m) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(m["Name"]) - // Output: build -} - -func ExampleWithOTel_meterProvider() { - type MyConfig struct{} - - app := appFunc(func(ctx context.Context) error { - return nil - }) - - var builder bedrock.AppBuilder[MyConfig] = bedrock.AppBuilderFunc[MyConfig](func(ctx context.Context, cfg MyConfig) (bedrock.App, error) { - counter, err := otel.Meter("builder").Int64Counter("build") - if err != nil { - return nil, err - } - counter.Add(ctx, 1) - return app, nil - }) - - var mp *sdkmetric.MeterProvider - var buf bytes.Buffer - builder = WithOTel( - builder, - OTelMeterProvider(func(ctx context.Context) (metric.MeterProvider, error) { - // NOTE: this is only for example purposes. DO NOT USE IN PRODUCTION!!! - exp, err := stdoutmetric.New( - stdoutmetric.WithWriter(&buf), - ) - if err != nil { - return nil, err - } - - r := sdkmetric.NewPeriodicReader(exp) - - mp = sdkmetric.NewMeterProvider( - sdkmetric.WithReader(r), - ) - return mp, nil - }), - ) - - _, err := builder.Build(context.Background(), MyConfig{}) - if err != nil { - fmt.Println(err) - return - } - - // Ensure that the builder metric is flushed to buf - err = mp.Shutdown(context.Background()) - if err != nil { - fmt.Println(err) - return - } - - b, err := io.ReadAll(&buf) - if err != nil { - fmt.Println(err) - return - } - - var m struct { - ScopeMetrics []struct { - Metrics []struct { - Name string `json:"Name"` - Data struct { - DataPoints []struct { - Value int `json:"Value"` - } `json:"DataPoints"` - } `json:"Data"` - } `json:"Metrics"` - } `json:"ScopeMetrics"` - } - err = json.Unmarshal(b, &m) - if err != nil { - fmt.Println(err) - return - } - - metric := m.ScopeMetrics[0].Metrics[0] - fmt.Println(metric.Name, metric.Data.DataPoints[0].Value) - // Output: build 1 -} - -func ExampleWithOTel_loggerProvider() { - type MyConfig struct{} - - app := appFunc(func(ctx context.Context) error { - return nil - }) - - var builder bedrock.AppBuilder[MyConfig] = bedrock.AppBuilderFunc[MyConfig](func(ctx context.Context, cfg MyConfig) (bedrock.App, error) { - // here we're using the otelslog bridge which will use the global - // LoggerProvider for us to create a otel Logger and map between - // the slog and otel log record types. - logger := otelslog.NewLogger("builder") - logger.InfoContext(ctx, "hello") - return app, nil - }) - - var lp *sdklog.LoggerProvider - var buf bytes.Buffer - builder = WithOTel( - builder, - OTelLoggerProvider(func(ctx context.Context) (log.LoggerProvider, error) { - // NOTE: this is only for example purposes. DO NOT USE IN PRODUCTION!!! - exp, err := stdoutlog.New( - stdoutlog.WithWriter(&buf), - ) - if err != nil { - return nil, err - } - - p := sdklog.NewSimpleProcessor(exp) - - lp = sdklog.NewLoggerProvider( - sdklog.WithProcessor(p), - ) - return lp, nil - }), - ) - - _, err := builder.Build(context.Background(), MyConfig{}) - if err != nil { - fmt.Println(err) - return - } - - // Ensure that the builder log is flushed to buf - err = lp.Shutdown(context.Background()) - if err != nil { - fmt.Println(err) - return - } - - b, err := io.ReadAll(&buf) - if err != nil { - fmt.Println(err) - return - } - - var m struct { - Body struct { - Value string `json:"Value"` - } `json:"Body"` - Scope struct { - Name string `json:"Name"` - } `json:"Scope"` - } - err = json.Unmarshal(b, &m) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(m.Scope.Name, m.Body.Value) - // Output: builder hello -} diff --git a/pkg/appbuilder/otel_test.go b/pkg/appbuilder/otel_test.go index efceead..53912c9 100644 --- a/pkg/appbuilder/otel_test.go +++ b/pkg/appbuilder/otel_test.go @@ -22,9 +22,42 @@ import ( tracenoop "go.opentelemetry.io/otel/trace/noop" ) -type config struct{} +type config struct { + initTextMapPropogator func(context.Context) (propagation.TextMapPropagator, error) + initTracerProvider func(context.Context) (trace.TracerProvider, error) + initMeterProvider func(context.Context) (metric.MeterProvider, error) + initLoggerProvider func(context.Context) (log.LoggerProvider, error) +} + +func (c config) InitTextMapPropogator(ctx context.Context) (propagation.TextMapPropagator, error) { + if c.initTextMapPropogator == nil { + return nil, nil + } + return c.initTextMapPropogator(ctx) +} + +func (c config) InitTracerProvider(ctx context.Context) (trace.TracerProvider, error) { + if c.initTracerProvider == nil { + return nil, nil + } + return c.initTracerProvider(ctx) +} + +func (c config) InitMeterProvider(ctx context.Context) (metric.MeterProvider, error) { + if c.initMeterProvider == nil { + return nil, nil + } + return c.initMeterProvider(ctx) +} -func TestWithOTel(t *testing.T) { +func (c config) InitLoggerProvider(ctx context.Context) (log.LoggerProvider, error) { + if c.initLoggerProvider == nil { + return nil, nil + } + return c.initLoggerProvider(ctx) +} + +func TestOTel(t *testing.T) { t.Run("will return an error", func(t *testing.T) { t.Run("if the base bedrock.AppBuilder fails to run", func(t *testing.T) { baseErr := errors.New("failed to run") @@ -32,7 +65,7 @@ func TestWithOTel(t *testing.T) { return nil, baseErr }) - app := WithOTel(base) + app := OTel(base) _, err := app.Build(context.Background(), config{}) if !assert.ErrorIs(t, err, baseErr) { return @@ -45,11 +78,13 @@ func TestWithOTel(t *testing.T) { }) initErr := errors.New("failed to init") - app := WithOTel(base, OTelTextMapPropogator(func(ctx context.Context) (propagation.TextMapPropagator, error) { - return nil, initErr - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initTextMapPropogator: func(ctx context.Context) (propagation.TextMapPropagator, error) { + return nil, initErr + }, + }) if !assert.ErrorIs(t, err, initErr) { return } @@ -61,11 +96,13 @@ func TestWithOTel(t *testing.T) { }) initErr := errors.New("failed to init") - app := WithOTel(base, OTelTracerProvider(func(ctx context.Context) (trace.TracerProvider, error) { - return nil, initErr - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initTracerProvider: func(ctx context.Context) (trace.TracerProvider, error) { + return nil, initErr + }, + }) if !assert.ErrorIs(t, err, initErr) { return } @@ -77,11 +114,13 @@ func TestWithOTel(t *testing.T) { }) initErr := errors.New("failed to init") - app := WithOTel(base, OTelMeterProvider(func(ctx context.Context) (metric.MeterProvider, error) { - return nil, initErr - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initMeterProvider: func(ctx context.Context) (metric.MeterProvider, error) { + return nil, initErr + }, + }) if !assert.ErrorIs(t, err, initErr) { return } @@ -93,11 +132,13 @@ func TestWithOTel(t *testing.T) { }) initErr := errors.New("failed to init") - app := WithOTel(base, OTelLoggerProvider(func(ctx context.Context) (log.LoggerProvider, error) { - return nil, initErr - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initLoggerProvider: func(ctx context.Context) (log.LoggerProvider, error) { + return nil, initErr + }, + }) if !assert.ErrorIs(t, err, initErr) { return } @@ -110,11 +151,13 @@ func TestWithOTel(t *testing.T) { return nil, nil }) - app := WithOTel(base, OTelTextMapPropogator(func(ctx context.Context) (propagation.TextMapPropagator, error) { - return propagation.TraceContext{}, nil - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initTextMapPropogator: func(ctx context.Context) (propagation.TextMapPropagator, error) { + return propagation.TraceContext{}, nil + }, + }) if !assert.Nil(t, err) { return } @@ -125,11 +168,13 @@ func TestWithOTel(t *testing.T) { return nil, nil }) - app := WithOTel(base, OTelTracerProvider(func(ctx context.Context) (trace.TracerProvider, error) { - return tracenoop.NewTracerProvider(), nil - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initTracerProvider: func(ctx context.Context) (trace.TracerProvider, error) { + return tracenoop.NewTracerProvider(), nil + }, + }) if !assert.Nil(t, err) { return } @@ -140,11 +185,13 @@ func TestWithOTel(t *testing.T) { return nil, nil }) - app := WithOTel(base, OTelMeterProvider(func(ctx context.Context) (metric.MeterProvider, error) { - return metricnoop.NewMeterProvider(), nil - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initMeterProvider: func(ctx context.Context) (metric.MeterProvider, error) { + return metricnoop.NewMeterProvider(), nil + }, + }) if !assert.Nil(t, err) { return } @@ -155,11 +202,13 @@ func TestWithOTel(t *testing.T) { return nil, nil }) - app := WithOTel(base, OTelLoggerProvider(func(ctx context.Context) (log.LoggerProvider, error) { - return lognoop.NewLoggerProvider(), nil - })) + app := OTel(base) - _, err := app.Build(context.Background(), config{}) + _, err := app.Build(context.Background(), config{ + initLoggerProvider: func(ctx context.Context) (log.LoggerProvider, error) { + return lognoop.NewLoggerProvider(), nil + }, + }) if !assert.Nil(t, err) { return }