+28
-3
cmd/app/main.go
+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
+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
+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
+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
+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
+
}