/
cmd.go
122 lines (104 loc) · 3.28 KB
/
cmd.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
package cmd
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"strconv"
"github.com/axiomhq/axiom-go/axiom"
"go.uber.org/zap"
"github.com/axiomhq/pkg/version"
)
// RunFunc is implemented by the main packages and passed to the `Run` function
// which takes care of signal handling, loading the runtime configuration and
// setting up logging, the Axiom client, etc. It must block until the context is
// marked done. Errors returned from the `RunFunc` should be created using the
// `Error()` function.
type RunFunc func(context.Context, *zap.Logger, *axiom.Client) error
// Run the named app with the given `RunFunc`. Additionally, options can be
// passed to configure the behaviour of the bootstrapping process.
func Run(appName string, fn RunFunc, options ...Option) {
if code := run(appName, fn, options...); code != exitOK {
code.exit()
}
}
func run(appName string, fn RunFunc, options ...Option) exitCode {
// Setup the default config and apply the supplied options.
cfg := &config{
loggerOptions: DefaultLoggerOptions(),
exitSignals: DefaultExitSignals(),
}
for _, option := range options {
if err := option(cfg); err != nil {
return exitConfig
}
}
// Set up logger.
var (
logger *zap.Logger
err error
)
if v, _ := strconv.ParseBool(os.Getenv("DEBUG")); v {
logger, err = zap.NewDevelopment(cfg.loggerOptions...)
} else {
logger, err = zap.NewProduction(cfg.loggerOptions...)
}
if err != nil {
log.Printf("failed to create logger: %v", err)
return exitConfig
}
defer func() {
logger.Warn("stopped")
// HINT(lukasmalkmus): Ignore error because of
// https://github.com/uber-go/zap/issues/880.
_ = logger.Sync()
}()
// Add application name to the logger
logger = logger.Named(appName)
// Log version information.
logger.Info("starting",
zap.String("release", version.Release()),
zap.String("revision", version.Revision()),
zap.String("build_date", version.BuildDateString()),
zap.String("build_user", version.BuildUser()),
zap.String("go_version", version.GoVersion()),
)
// Make sure the required environment variables are set.
for _, env := range cfg.requiredEnvVars {
if os.Getenv(env) == "" {
logger.Error("missing environment variable", zap.String("name", env))
return exitConfig
}
}
// Listen for termination signals.
ctx, cancel := signal.NotifyContext(context.Background(), cfg.exitSignals...)
defer cancel()
// Create the Axiom client.
client, err := axiom.NewClient(cfg.axiomOptions...)
if err != nil {
logger.Error("create axiom client", zap.Error(err))
return exitConfig
}
// If enabled, validate the credentials of the Axiom client.
if cfg.validateAxiomCredentials {
if err = client.ValidateCredentials(ctx); err != nil {
logger.Error("validate axiom credentials", zap.Error(err))
return exitConfig
}
}
logger.Info("started")
// Call the actual `RunFunc`. If the returned error was composed using
// `cmd.Error()`, it can be logged properly. If not, logging the error is
// done as well but with less context to it.
if err = fn(ctx, logger, client); err != nil {
if mainErr, ok := err.(*mainFuncError); ok {
logger.Error(mainErr.msg, mainErr.Fields()...)
} else {
msg := fmt.Sprintf("%s.RunFunc", appName)
logger.Error(msg, zap.Error(err))
}
return exitInternal
}
return exitOK
}