From 1f9f90dd261dd75e86b20ebb962dc66c74bdaa58 Mon Sep 17 00:00:00 2001 From: Richard Carson Derr Date: Sat, 14 Sep 2024 16:33:50 -0400 Subject: [PATCH] story(issue-258): example update custom framework to use new package api (#266) * refactor(issue-258): update custom framework example * feat(issue-258): actually configure trace provider for custom framework * chore(docs): updated coverage badge. --------- Co-authored-by: GitHub Action --- README.md | 2 +- example/custom_framework/echo/app/app.go | 31 +++ example/custom_framework/echo/config.yaml | 1 - .../custom_framework/echo/endpoint/echo.go | 61 +++++ .../echo/endpoint/echo_test.go | 57 +++++ example/custom_framework/echo/main.go | 4 +- .../custom_framework/echo/service/service.go | 71 ------ .../{base_config.yaml => default_config.yaml} | 5 +- .../custom_framework/framework/framework.go | 81 +++++-- .../framework/internal/config.go | 28 +++ .../framework/internal/global/config.go | 14 ++ .../framework/rest/default_config.yaml | 6 + .../framework/rest/operation.go | 45 ++++ .../custom_framework/framework/rest/rest.go | 210 ++++++++++++++++++ go.mod | 4 + go.sum | 8 + 16 files changed, 531 insertions(+), 97 deletions(-) create mode 100644 example/custom_framework/echo/app/app.go create mode 100644 example/custom_framework/echo/endpoint/echo.go create mode 100644 example/custom_framework/echo/endpoint/echo_test.go delete mode 100644 example/custom_framework/echo/service/service.go rename example/custom_framework/framework/{base_config.yaml => default_config.yaml} (54%) create mode 100644 example/custom_framework/framework/internal/config.go create mode 100644 example/custom_framework/framework/internal/global/config.go create mode 100644 example/custom_framework/framework/rest/default_config.yaml create mode 100644 example/custom_framework/framework/rest/operation.go create mode 100644 example/custom_framework/framework/rest/rest.go diff --git a/README.md b/README.md index e492dfb..60f8157 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![Go Reference](https://pkg.go.dev/badge/github.com/z5labs/bedrock.svg)](https://pkg.go.dev/github.com/z5labs/bedrock) [![Go Report Card](https://goreportcard.com/badge/github.com/z5labs/bedrock)](https://goreportcard.com/report/github.com/z5labs/bedrock) -![Coverage](https://img.shields.io/badge/Coverage-96.5%25-brightgreen) +![Coverage](https://img.shields.io/badge/Coverage-96.9%25-brightgreen) [![build](https://github.com/z5labs/bedrock/actions/workflows/build.yaml/badge.svg)](https://github.com/z5labs/bedrock/actions/workflows/build.yaml) **bedrock provides a minimal, modular and composable foundation for diff --git a/example/custom_framework/echo/app/app.go b/example/custom_framework/echo/app/app.go new file mode 100644 index 0000000..1d62765 --- /dev/null +++ b/example/custom_framework/echo/app/app.go @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package app + +import ( + "context" + + "github.com/z5labs/bedrock/example/custom_framework/echo/endpoint" + + "github.com/z5labs/bedrock/example/custom_framework/framework" + "github.com/z5labs/bedrock/example/custom_framework/framework/rest" +) + +type Config struct { + rest.Config `config:",squash"` +} + +func Init(ctx context.Context, cfg Config) (framework.App, error) { + log := framework.Logger(cfg.Logging) + + app := rest.NewApp( + rest.OpenApi(cfg.OpenApi), + rest.OTel(cfg.OTel), + rest.HttpServer(cfg.Http), + rest.WithEndpoint(endpoint.Echo(log)), + ) + return app, nil +} diff --git a/example/custom_framework/echo/config.yaml b/example/custom_framework/echo/config.yaml index 8607230..e69de29 100644 --- a/example/custom_framework/echo/config.yaml +++ b/example/custom_framework/echo/config.yaml @@ -1 +0,0 @@ -custom: "hello" \ No newline at end of file diff --git a/example/custom_framework/echo/endpoint/echo.go b/example/custom_framework/echo/endpoint/echo.go new file mode 100644 index 0000000..3b2ad71 --- /dev/null +++ b/example/custom_framework/echo/endpoint/echo.go @@ -0,0 +1,61 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package endpoint + +import ( + "context" + "encoding/json" + "log/slog" + "net/http" + + "github.com/z5labs/bedrock/example/custom_framework/framework/rest" +) + +type echoHandler struct { + log *slog.Logger +} + +func Echo(log *slog.Logger) rest.Endpoint { + h := &echoHandler{ + log: log, + } + + return rest.Endpoint{ + Method: http.MethodPost, + Path: "/echo", + Operation: rest.NewOperation(h), + } +} + +type EchoRequest struct { + Msg string `json:"msg"` +} + +func (EchoRequest) ContentType() string { + return "application/json" +} + +func (req *EchoRequest) UnmarshalBinary(b []byte) error { + return json.Unmarshal(b, req) +} + +type EchoResponse struct { + Msg string `json:"msg"` +} + +func (EchoResponse) ContentType() string { + return "application/json" +} + +func (resp *EchoResponse) MarshalBinary() ([]byte, error) { + return json.Marshal(resp) +} + +func (h *echoHandler) Handle(ctx context.Context, req *EchoRequest) (*EchoResponse, error) { + h.log.InfoContext(ctx, "echoing back received message to client", slog.String("echo_msg", req.Msg)) + resp := &EchoResponse{Msg: req.Msg} + return resp, nil +} diff --git a/example/custom_framework/echo/endpoint/echo_test.go b/example/custom_framework/echo/endpoint/echo_test.go new file mode 100644 index 0000000..3b23d2c --- /dev/null +++ b/example/custom_framework/echo/endpoint/echo_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package endpoint + +import ( + "encoding/json" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEcho(t *testing.T) { + t.Run("will echo message back", func(t *testing.T) { + t.Run("always", func(t *testing.T) { + e := Echo(slog.Default()) + + req := `{"msg": "hello world"}` + + w := httptest.NewRecorder() + r := httptest.NewRequest( + http.MethodPost, + "/echo", + strings.NewReader(req), + ) + r.Header.Set("Content-Type", "application/json") + + e.ServeHTTP(w, r) + + resp := w.Result() + if !assert.Equal(t, http.StatusOK, resp.StatusCode) { + return + } + + b, err := io.ReadAll(resp.Body) + if !assert.Nil(t, err) { + return + } + + var echoResp EchoResponse + err = json.Unmarshal(b, &echoResp) + if !assert.Nil(t, err) { + return + } + if !assert.Equal(t, "hello world", echoResp.Msg) { + return + } + }) + }) +} diff --git a/example/custom_framework/echo/main.go b/example/custom_framework/echo/main.go index 47135ad..77e39b4 100644 --- a/example/custom_framework/echo/main.go +++ b/example/custom_framework/echo/main.go @@ -4,7 +4,7 @@ import ( "bytes" _ "embed" - "github.com/z5labs/bedrock/example/custom_framework/echo/service" + "github.com/z5labs/bedrock/example/custom_framework/echo/app" "github.com/z5labs/bedrock/example/custom_framework/framework" ) @@ -12,5 +12,5 @@ import ( var cfgSrc []byte func main() { - framework.Rest(bytes.NewReader(cfgSrc), service.Init) + framework.Run(bytes.NewReader(cfgSrc), app.Init) } diff --git a/example/custom_framework/echo/service/service.go b/example/custom_framework/echo/service/service.go deleted file mode 100644 index f671210..0000000 --- a/example/custom_framework/echo/service/service.go +++ /dev/null @@ -1,71 +0,0 @@ -package service - -import ( - "bytes" - "context" - "io" - "log/slog" - "net/http" - "os" - - "github.com/z5labs/bedrock/example/custom_framework/framework" -) - -type Config struct { - // This is completely optional since none of the base config - // values are used by this service. - // This simply acts as an example for how to embed a custom framework - // config into your service config. - framework.Config `config:",squash"` - - Custom string `config:"custom"` -} - -type service struct { - log *slog.Logger -} - -func Init(ctx context.Context, cfg Config, mux *http.ServeMux) error { - logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ - Level: cfg.Logging.Level, - AddSource: true, - })) - - mux.Handle("/echo", &service{ - log: logger, - }) - - return nil -} - -func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - b, err := readAllAndClose(r.Body) - if err != nil { - s.log.ErrorContext(ctx, "failed to read request", slog.String("error", err.Error())) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - s.log.InfoContext(ctx, "echoing back the received data") - - n, err := io.Copy(w, bytes.NewReader(b)) - if err != nil { - s.log.ErrorContext(ctx, "failed to write response body", slog.String("error", err.Error())) - http.Error(w, "internal server error", http.StatusInternalServerError) - return - } - if n != int64(len(b)) { - s.log.ErrorContext( - ctx, - "failed to write entire response body", - slog.Any("bytes_written", n), - slog.Int("total_bytes", len(b)), - ) - } -} - -func readAllAndClose(rc io.ReadCloser) ([]byte, error) { - defer rc.Close() - return io.ReadAll(rc) -} diff --git a/example/custom_framework/framework/base_config.yaml b/example/custom_framework/framework/default_config.yaml similarity index 54% rename from example/custom_framework/framework/base_config.yaml rename to example/custom_framework/framework/default_config.yaml index c3af97a..a08a375 100644 --- a/example/custom_framework/framework/base_config.yaml +++ b/example/custom_framework/framework/default_config.yaml @@ -1,10 +1,9 @@ otel: - serviceName: {{env "SERVICE_NAME"}} + service_name: {{env "SERVICE_NAME"}} + service_version: {{env "SERVICE_VERSION"}} otlp: target: {{env "OTLP_ADDR"}} logging: level: {{env "LOG_LEVEL" | default "INFO"}} -http: - port: {{env "HTTP_PORT" | default 8080}} \ No newline at end of file diff --git a/example/custom_framework/framework/framework.go b/example/custom_framework/framework/framework.go index 91ef726..1a8b06a 100644 --- a/example/custom_framework/framework/framework.go +++ b/example/custom_framework/framework/framework.go @@ -1,33 +1,76 @@ package framework import ( + "bytes" "context" _ "embed" "io" "log/slog" - "net/http" + "os" + "sync" + + "github.com/z5labs/bedrock/example/custom_framework/framework/internal" + "github.com/z5labs/bedrock/example/custom_framework/framework/internal/global" + + "github.com/z5labs/bedrock" ) -//go:embed base_config.yaml -var baseCfgSrc []byte +//go:embed default_config.yaml +var configBytes []byte + +func init() { + global.RegisterConfigSource(internal.ConfigSource(bytes.NewReader(configBytes))) +} + +type OTelConfig struct { + ServiceName string `config:"service_name"` + ServiceVersion string `config:"service_version"` + OTLP struct { + Target string `config:"target"` + } `config:"otlp"` +} + +type LoggingConfig struct { + Level slog.Level `config:"level"` +} type Config struct { - OTel struct { - ServiceName string `config:"serviceName"` - OTLP struct { - Target string `config:"target"` - } `config:"otlp"` - } `config:"otel"` - - Logging struct { - Level slog.Level `config:"level"` - } `config:"logging"` - - Http struct { - Port uint `config:"port"` - } `config:"http"` + OTel OTelConfig `config:"otel"` + Logging LoggingConfig `config:"logging"` +} + +var logger *slog.Logger +var initLoggerOnce sync.Once + +func Logger(cfg LoggingConfig) *slog.Logger { + initLoggerOnce.Do(func() { + logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + Level: cfg.Level, + })) + }) + return logger } -func Rest[T any](cfg io.Reader, f func(context.Context, T, *http.ServeMux) error) error { - return nil +type App bedrock.App + +func Run[T any](r io.Reader, build func(context.Context, T) (App, error)) { + err := bedrock.Run( + context.Background(), + bedrock.AppBuilderFunc[T](func(ctx context.Context, cfg T) (bedrock.App, error) { + return build(ctx, cfg) + }), + global.ConfigSources..., + ) + if err == nil { + return + } + + // there's a chance Run failed on config parsing/unmarshalling + // thus the logging config is most likely unusable and we should + // instead create our own logger here for logging this error + log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + })) + log.Error("failed while running application", slog.String("error", err.Error())) } diff --git a/example/custom_framework/framework/internal/config.go b/example/custom_framework/framework/internal/config.go new file mode 100644 index 0000000..ed84bf1 --- /dev/null +++ b/example/custom_framework/framework/internal/config.go @@ -0,0 +1,28 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package internal + +import ( + "io" + "os" + + "github.com/z5labs/bedrock/pkg/config" +) + +func ConfigSource(r io.Reader) config.Source { + return config.FromYaml( + config.RenderTextTemplate( + r, + config.TemplateFunc("env", os.Getenv), + config.TemplateFunc("default", func(s string, v any) any { + if len(s) == 0 { + return v + } + return s + }), + ), + ) +} diff --git a/example/custom_framework/framework/internal/global/config.go b/example/custom_framework/framework/internal/global/config.go new file mode 100644 index 0000000..bffe9bc --- /dev/null +++ b/example/custom_framework/framework/internal/global/config.go @@ -0,0 +1,14 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package global + +import "github.com/z5labs/bedrock/pkg/config" + +var ConfigSources []config.Source + +func RegisterConfigSource(src config.Source) { + ConfigSources = append(ConfigSources, src) +} diff --git a/example/custom_framework/framework/rest/default_config.yaml b/example/custom_framework/framework/rest/default_config.yaml new file mode 100644 index 0000000..e0e46df --- /dev/null +++ b/example/custom_framework/framework/rest/default_config.yaml @@ -0,0 +1,6 @@ +openapi: + title: {{env "SERVICE_NAME"}} + version: {{env "SERVICE_VERSION"}} + +http: + port: {{env "HTTP_PORT" | default "8080"}} \ No newline at end of file diff --git a/example/custom_framework/framework/rest/operation.go b/example/custom_framework/framework/rest/operation.go new file mode 100644 index 0000000..16c58f4 --- /dev/null +++ b/example/custom_framework/framework/rest/operation.go @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package rest + +import ( + "github.com/z5labs/bedrock/rest" + "github.com/z5labs/bedrock/rest/endpoint" +) + +type Operation rest.Operation + +type OperationHandler[Req, Resp any] endpoint.Handler[Req, Resp] + +type operationConfig struct { + headers []endpoint.Header +} + +type OperationOption func(*operationConfig) + +func Header(name string, required bool, pattern string) OperationOption { + return func(oc *operationConfig) { + oc.headers = append(oc.headers, endpoint.Header{ + Name: name, + Required: required, + Pattern: pattern, + }) + } +} + +func NewOperation[Req, Resp any](h OperationHandler[Req, Resp], opts ...OperationOption) rest.Operation { + opOpts := &operationConfig{} + for _, opt := range opts { + opt(opOpts) + } + + var endpointOpts []endpoint.Option + if len(opOpts.headers) > 0 { + endpointOpts = append(endpointOpts, endpoint.Headers(opOpts.headers...)) + } + + return endpoint.NewOperation(h, endpointOpts...) +} diff --git a/example/custom_framework/framework/rest/rest.go b/example/custom_framework/framework/rest/rest.go new file mode 100644 index 0000000..0691192 --- /dev/null +++ b/example/custom_framework/framework/rest/rest.go @@ -0,0 +1,210 @@ +// Copyright (c) 2024 Z5Labs and Contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package rest + +import ( + "bytes" + "context" + _ "embed" + "net/http" + "os" + "strings" + + "github.com/z5labs/bedrock/example/custom_framework/framework" + "github.com/z5labs/bedrock/example/custom_framework/framework/internal" + "github.com/z5labs/bedrock/example/custom_framework/framework/internal/global" + + "github.com/z5labs/bedrock" + "github.com/z5labs/bedrock/pkg/app" + "github.com/z5labs/bedrock/rest" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "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.26.0" + "go.opentelemetry.io/otel/trace" +) + +//go:embed default_config.yaml +var configBytes []byte + +func init() { + global.RegisterConfigSource(internal.ConfigSource(bytes.NewReader(configBytes))) +} + +type OpenApiConfig struct { + Title string `config:"title"` + Version string `config:"version"` +} + +type HttpServerConfig struct { + Port uint `config:"port"` +} + +type Config struct { + framework.Config `config:",squash"` + + OpenApi OpenApiConfig `config:"openapi"` + + Http HttpServerConfig `config:"http"` +} + +type Option func(*App) + +func OpenApi(cfg OpenApiConfig) Option { + return func(ra *App) { + ra.restOpts = append( + ra.restOpts, + rest.Title(cfg.Title), + rest.Version(cfg.Version), + ) + } +} + +func OTel(cfg framework.OTelConfig) Option { + return func(ra *App) { + ra.otelOpts = append( + ra.otelOpts, + app.OTelTextMapPropogator(func(ctx context.Context) (propagation.TextMapPropagator, error) { + tmp := propagation.NewCompositeTextMapPropagator(propagation.Baggage{}, propagation.TraceContext{}) + return tmp, nil + }), + app.OTelTracerProvider(func(ctx context.Context) (trace.TracerProvider, error) { + // framework.Logger will return a logger that writes to STDOUT + // so we'll just send traces to STDERR for demo purposes. + exp, err := stdouttrace.New( + stdouttrace.WithWriter(os.Stderr), + ) + if err != nil { + return nil, err + } + + r, err := resource.Detect( + ctx, + resourceDetectFunc(func(ctx context.Context) (*resource.Resource, error) { + return resource.Default(), nil + }), + resource.StringDetector(semconv.SchemaURL, semconv.ServiceNameKey, func() (string, error) { + return cfg.ServiceName, nil + }), + resource.StringDetector(semconv.SchemaURL, semconv.ServiceVersionKey, func() (string, error) { + return cfg.ServiceVersion, nil + }), + ) + if err != nil { + return nil, err + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithResource(r), + sdktrace.WithSampler(sdktrace.AlwaysSample()), + ) + ra.postRunHooks = append(ra.postRunHooks, shutdownHook(tp)) + + return tp, nil + }), + ) + } +} + +func HttpServer(cfg HttpServerConfig) Option { + return func(ra *App) { + ra.restOpts = append(ra.restOpts, rest.ListenOn(cfg.Port)) + } +} + +type Endpoint struct { + Method string + Path string + Operation Operation +} + +// ServeHTTP implements the http.Handler interface by simply just calling +// ServeHTTP on the Operation for the Endpoint. This method is only implemented +// as a convenience to simplify unit testing. +func (e Endpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) { + e.Operation.ServeHTTP(w, r) +} + +func WithEndpoint(e Endpoint) Option { + return func(ra *App) { + ra.restOpts = append(ra.restOpts, rest.Endpoint(e.Method, e.Path, e.Operation)) + } +} + +type App struct { + restOpts []rest.Option + otelOpts []app.OTelOption + postRunHooks []app.LifecycleHook +} + +func NewApp(opts ...Option) *App { + ra := &App{} + for _, opt := range opts { + opt(ra) + } + return ra +} + +type resourceDetectFunc func(context.Context) (*resource.Resource, error) + +func (f resourceDetectFunc) Detect(ctx context.Context) (*resource.Resource, error) { + return f(ctx) +} + +func (ra *App) Run(ctx context.Context) error { + var base bedrock.App = rest.NewApp(ra.restOpts...) + + base = app.WithLifecycleHooks(base, app.Lifecycle{ + PostRun: composePostRunHooks(ra.postRunHooks...), + }) + + base = app.WithOTel(base, ra.otelOpts...) + + base = app.WithSignalNotifications(base, os.Interrupt, os.Kill) + + return base.Run(ctx) +} + +type multiErr []error + +func (e multiErr) Error() string { + var sb strings.Builder + sb.WriteString("captured error(s):\n") + for _, err := range e { + sb.WriteString("\t- ") + sb.WriteString(err.Error()) + sb.WriteByte('\n') + } + return sb.String() +} + +func composePostRunHooks(hooks ...app.LifecycleHook) app.LifecycleHook { + return app.LifecycleHookFunc(func(ctx context.Context) error { + var me multiErr + for _, hook := range hooks { + err := hook.Run(ctx) + if err != nil { + me = append(me, err) + } + } + if len(me) == 0 { + return nil + } + return me + }) +} + +type shutdown interface { + Shutdown(context.Context) error +} + +func shutdownHook(s shutdown) app.LifecycleHook { + return app.LifecycleHookFunc(func(ctx context.Context) error { + return s.Shutdown(ctx) + }) +} diff --git a/go.mod b/go.mod index 24a1477..71406f2 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/swaggest/openapi-go v0.2.54 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 go.opentelemetry.io/otel v1.30.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0 go.opentelemetry.io/otel/log v0.6.0 go.opentelemetry.io/otel/metric v1.30.0 + go.opentelemetry.io/otel/sdk v1.30.0 go.opentelemetry.io/otel/trace v1.30.0 golang.org/x/sync v0.8.0 gopkg.in/yaml.v3 v3.0.1 @@ -21,10 +23,12 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/swaggest/refl v1.3.0 // indirect + golang.org/x/sys v0.25.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index e23d84b..1dfd387 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpG github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -51,14 +53,20 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= 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/exporters/stdout/stdouttrace v1.30.0 h1:kn1BudCgwtE7PxLqcZkErpD8GKqLZ6BSzeW9QihQJeM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.30.0/go.mod h1:ljkUDtAMdleoi9tIG1R6dJUpVwDcYjw3J2Q6Q/SuiC0= go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8= go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=