this repo has no description

:sparkles: added otel

Changed files
+202 -10
cmd
app
internal
+28 -3
cmd/app/main.go
··· 1 1 package main 2 2 3 3 import ( 4 - "log" 4 + "context" 5 + "errors" 5 6 "os" 7 + "os/signal" 6 8 9 + "github.com/Tulkdan/payment-gateway/internal/lib" 7 10 "github.com/Tulkdan/payment-gateway/internal/providers" 8 11 "github.com/Tulkdan/payment-gateway/internal/service" 9 12 "github.com/Tulkdan/payment-gateway/internal/web" ··· 17 20 } 18 21 19 22 func main() { 23 + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 24 + defer stop() 25 + 26 + otelShutdown, err := lib.SetupOTelSDK(ctx) 27 + if err != nil { 28 + return 29 + } 30 + 31 + defer func() { 32 + err = errors.Join(err, otelShutdown(context.Background())) 33 + }() 34 + 20 35 providers := providers.NewUseProviders([]providers.Provider{ 21 36 providers.NewBraintreeProvider(getEnv("BRAINTREE_URL", "localhost:8001")), 22 37 providers.NewStripeProvider(getEnv("STRIPE_URL", "localhost:8002")), ··· 27 42 server := web.NewServer(paymentsService, port) 28 43 server.ConfigureRouter() 29 44 30 - if err := server.Start(); err != nil { 31 - log.Fatal("Error starting server: ", err) 45 + srvErr := make(chan error, 1) 46 + go func() { 47 + srvErr <- server.Start(ctx) 48 + }() 49 + 50 + select { 51 + case err = <-srvErr: 52 + return 53 + case <-ctx.Done(): 54 + stop() 32 55 } 56 + 57 + err = server.Shutdown() 33 58 }
+20
go.mod
··· 3 3 go 1.24.4 4 4 5 5 require github.com/google/uuid v1.6.0 6 + 7 + require ( 8 + github.com/felixge/httpsnoop v1.0.4 // indirect 9 + github.com/go-logr/logr v1.4.3 // indirect 10 + github.com/go-logr/stdr v1.2.2 // indirect 11 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect 12 + go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 // indirect 13 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect 14 + go.opentelemetry.io/otel v1.37.0 // indirect 15 + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 // indirect 16 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect 17 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 // indirect 18 + go.opentelemetry.io/otel/log v0.13.0 // indirect 19 + go.opentelemetry.io/otel/metric v1.37.0 // indirect 20 + go.opentelemetry.io/otel/sdk v1.37.0 // indirect 21 + go.opentelemetry.io/otel/sdk/log v0.13.0 // indirect 22 + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect 23 + go.opentelemetry.io/otel/trace v1.37.0 // indirect 24 + golang.org/x/sys v0.33.0 // indirect 25 + )
+35
go.sum
··· 1 + github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 2 + github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 3 + github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 4 + github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 5 + github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 6 + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 7 + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 1 8 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 2 9 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 + go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 11 + go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 12 + go.opentelemetry.io/contrib/bridges/otelslog v0.12.0 h1:lFM7SZo8Ce01RzRfnUFQZEYeWRf/MtOA3A5MobOqk2g= 13 + go.opentelemetry.io/contrib/bridges/otelslog v0.12.0/go.mod h1:Dw05mhFtrKAYu72Tkb3YBYeQpRUJ4quDgo2DQw3No5A= 14 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= 15 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= 16 + go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= 17 + go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= 18 + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0 h1:yEX3aC9KDgvYPhuKECHbOlr5GLwH6KTjLJ1sBSkkxkc= 19 + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.13.0/go.mod h1:/GXR0tBmmkxDaCUGahvksvp66mx4yh5+cFXgSlhg0vQ= 20 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= 21 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= 22 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= 23 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= 24 + go.opentelemetry.io/otel/log v0.13.0 h1:yoxRoIZcohB6Xf0lNv9QIyCzQvrtGZklVbdCoyb7dls= 25 + go.opentelemetry.io/otel/log v0.13.0/go.mod h1:INKfG4k1O9CL25BaM1qLe0zIedOpvlS5Z7XgSbmN83E= 26 + go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= 27 + go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= 28 + go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= 29 + go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= 30 + go.opentelemetry.io/otel/sdk/log v0.13.0 h1:I3CGUszjM926OphK8ZdzF+kLqFvfRY/IIoFq/TjwfaQ= 31 + go.opentelemetry.io/otel/sdk/log v0.13.0/go.mod h1:lOrQyCCXmpZdN7NchXb6DOZZa1N5G1R2tm5GMMTpDBw= 32 + go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= 33 + go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= 34 + go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= 35 + go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= 36 + golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 37 + golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+96
internal/lib/openTelemetry.go
··· 1 + package lib 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "time" 7 + 8 + "go.opentelemetry.io/otel" 9 + "go.opentelemetry.io/otel/exporters/stdout/stdoutlog" 10 + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 11 + "go.opentelemetry.io/otel/log/global" 12 + "go.opentelemetry.io/otel/propagation" 13 + "go.opentelemetry.io/otel/sdk/log" 14 + "go.opentelemetry.io/otel/sdk/trace" 15 + ) 16 + 17 + // setupOTelSDK bootstraps the OpenTelemetry pipeline. 18 + // If it does not return an error, make sure to call shutdown for proper cleanup. 19 + func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { 20 + var shutdownFuncs []func(context.Context) error 21 + 22 + // shutdown calls cleanup functions registered via shutdownFuncs. 23 + // The errors from the calls are joined. 24 + // Each registered cleanup will be invoked once. 25 + shutdown = func(ctx context.Context) error { 26 + var err error 27 + for _, fn := range shutdownFuncs { 28 + err = errors.Join(err, fn(ctx)) 29 + } 30 + shutdownFuncs = nil 31 + return err 32 + } 33 + 34 + // handleErr calls shutdown for cleanup and makes sure that all errors are returned. 35 + handleErr := func(inErr error) { 36 + err = errors.Join(inErr, shutdown(ctx)) 37 + } 38 + 39 + // Set up propagator. 40 + prop := newPropagator() 41 + otel.SetTextMapPropagator(prop) 42 + 43 + // Set up trace provider. 44 + tracerProvider, err := newTracerProvider() 45 + if err != nil { 46 + handleErr(err) 47 + return 48 + } 49 + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) 50 + otel.SetTracerProvider(tracerProvider) 51 + 52 + // Set up logger provider. 53 + loggerProvider, err := newLoggerProvider() 54 + if err != nil { 55 + handleErr(err) 56 + return 57 + } 58 + shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) 59 + global.SetLoggerProvider(loggerProvider) 60 + 61 + return 62 + } 63 + 64 + func newPropagator() propagation.TextMapPropagator { 65 + return propagation.NewCompositeTextMapPropagator( 66 + propagation.TraceContext{}, 67 + propagation.Baggage{}, 68 + ) 69 + } 70 + 71 + func newTracerProvider() (*trace.TracerProvider, error) { 72 + traceExporter, err := stdouttrace.New( 73 + stdouttrace.WithPrettyPrint()) 74 + if err != nil { 75 + return nil, err 76 + } 77 + 78 + tracerProvider := trace.NewTracerProvider( 79 + trace.WithBatcher(traceExporter, 80 + // Default is 5s. Set to 1s for demonstrative purposes. 81 + trace.WithBatchTimeout(time.Second)), 82 + ) 83 + return tracerProvider, nil 84 + } 85 + 86 + func newLoggerProvider() (*log.LoggerProvider, error) { 87 + logExporter, err := stdoutlog.New() 88 + if err != nil { 89 + return nil, err 90 + } 91 + 92 + loggerProvider := log.NewLoggerProvider( 93 + log.WithProcessor(log.NewBatchProcessor(logExporter)), 94 + ) 95 + return loggerProvider, nil 96 + }
+23 -7
internal/web/server.go
··· 1 1 package web 2 2 3 3 import ( 4 + "context" 5 + "net" 4 6 "net/http" 7 + "time" 5 8 6 9 "github.com/Tulkdan/payment-gateway/internal/service" 7 10 "github.com/Tulkdan/payment-gateway/internal/web/handler" 11 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 8 12 ) 9 13 10 14 type Server struct { 11 15 port string 12 - router *http.ServeMux 16 + router http.Handler 13 17 server *http.Server 14 18 15 19 paymentsService *service.PaymentService ··· 23 27 } 24 28 25 29 func (s *Server) ConfigureRouter() { 26 - r := &http.ServeMux{} 30 + mux := http.NewServeMux() 31 + 32 + handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) { 33 + handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc)) 34 + mux.Handle(pattern, handler) 35 + } 27 36 28 37 paymentsHandler := handler.NewPaymentsHandler(s.paymentsService) 29 38 30 - r.HandleFunc("POST /payments", paymentsHandler.Create) 39 + handleFunc("POST /payments", paymentsHandler.Create) 31 40 // r.HandleFunc("POST /refunds", func(http.ResponseWriter, *http.Request) {}) 32 41 // r.HandleFunc("GET /payments/{id}", func(w http.ResponseWriter, r *http.Request) { 33 42 // id := r.PathValue("id") 34 43 // }) 35 44 36 - s.router = r 45 + s.router = otelhttp.NewHandler(mux, "/") 37 46 } 38 47 39 - func (s *Server) Start() error { 48 + func (s *Server) Start(ctx context.Context) error { 40 49 s.server = &http.Server{ 41 - Addr: ":" + s.port, 42 - Handler: s.router, 50 + Addr: ":" + s.port, 51 + Handler: s.router, 52 + BaseContext: func(_ net.Listener) context.Context { return ctx }, 53 + ReadTimeout: time.Second, 54 + WriteTimeout: 10 * time.Second, 43 55 } 44 56 45 57 return s.server.ListenAndServe() 46 58 } 59 + 60 + func (s *Server) Shutdown() error { 61 + return s.server.Shutdown(context.Background()) 62 + }