-
Notifications
You must be signed in to change notification settings - Fork 61
/
main.go
155 lines (131 loc) · 3.84 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package main
import (
"context"
"fmt"
"os"
"sync"
"time"
"github.com/bnkamalesh/errors"
"github.com/naughtygopher/goapp/cmd/server/grpc"
"github.com/naughtygopher/goapp/cmd/server/http"
"github.com/naughtygopher/goapp/internal/api"
"github.com/naughtygopher/goapp/internal/configs"
"github.com/naughtygopher/goapp/internal/pkg/apm"
"github.com/naughtygopher/goapp/internal/pkg/logger"
"github.com/naughtygopher/goapp/internal/pkg/postgres"
"github.com/naughtygopher/goapp/internal/pkg/sysignals"
"github.com/naughtygopher/goapp/internal/users"
)
// recoverer is used for panic recovery of the application (note: this is not for the HTTP/gRPC servers).
// So that even if the main function panics we can produce required logs for troubleshooting
var exitErr error
func recoverer(ctx context.Context) {
exitCode := 0
var exitInfo any
rec := recover()
err, _ := rec.(error)
if err != nil {
exitCode = 1
exitInfo = err
} else if rec != nil {
exitCode = 2
exitInfo = rec
} else if exitErr != nil {
exitCode = 3
exitInfo = exitErr
}
// exiting after receiving a quit signal can be considered a *clean/successful* exit
if errors.Is(exitErr, sysignals.ErrSigQuit) {
exitCode = 0
}
// logging this because we have info logs saying "listening to" various port numbers
// based on the server type (gRPC, HTTP etc.). But it's unclear *from the logs*
// if the server is up and running, if it exits for any reason
if exitCode == 0 {
logger.Info(ctx, fmt.Sprintf("shutdown complete: %+v", exitInfo))
} else {
logger.Error(ctx, fmt.Sprintf("shutdown complete (exit: %d): %+v", exitCode, exitInfo))
}
os.Exit(exitCode)
}
func shutdown(
httpServer *http.HTTP,
grpcServer *grpc.GRPC,
apmIns *apm.APM,
) {
const shutdownTimeout = time.Second * 60
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
logger.Info(ctx, "initiating shutdown")
wgroup := &sync.WaitGroup{}
wgroup.Add(1)
go func() {
defer wgroup.Done()
_ = httpServer.Shutdown(ctx)
}()
// after all the APIs of the application are shutdown (e.g. HTTP, gRPC, Pubsub listener etc.)
// we should close connections to dependencies like database, cache etc.
// This should only be done after the APIs are shutdown completely
wgroup.Add(1)
go func() {
defer wgroup.Done()
_ = apmIns.Shutdown(ctx)
}()
wgroup.Wait()
}
func startAPM(ctx context.Context, cfg *configs.Configs) *apm.APM {
ap, err := apm.New(ctx, &apm.Options{
Debug: cfg.Environment == configs.EnvLocal,
Environment: cfg.Environment.String(),
ServiceName: cfg.AppName,
ServiceVersion: cfg.AppVersion,
TracesSampleRate: 50.00,
UseStdOut: cfg.Environment == configs.EnvLocal,
})
if err != nil {
panic(errors.Wrap(err, "failed to start APM"))
}
return ap
}
func startServers(svr api.Server, cfgs *configs.Configs) (*http.HTTP, *grpc.GRPC) {
hcfg, _ := cfgs.HTTP()
hserver, err := http.NewService(hcfg, svr)
if err != nil {
panic(errors.Wrap(err, "failed to initialize HTTP server"))
}
err = hserver.Start()
if err != nil {
panic(errors.Wrap(err, "failed to start HTTP server"))
}
return hserver, nil
}
func main() {
ctx := context.Background()
defer recoverer(ctx)
fatalErr := make(chan error, 1)
cfgs, err := configs.New()
if err != nil {
panic(errors.Wrap(err))
}
logger.UpdateDefaultLogger(logger.New(
cfgs.AppName, cfgs.AppVersion, 0,
map[string]string{
"env": cfgs.Environment.String(),
}),
)
apmhandler := startAPM(ctx, cfgs)
pqdriver, err := postgres.NewPool(cfgs.Postgres())
if err != nil {
panic(errors.Wrap(err))
}
userPGstore := users.NewPostgresStore(pqdriver, cfgs.UserPostgresTable())
userSvc := users.NewService(userPGstore)
svrAPIs := api.NewServer(userSvc, nil)
hserver, gserver := startServers(svrAPIs, cfgs)
defer shutdown(
hserver,
gserver,
apmhandler,
)
exitErr = <-fatalErr
}