/
app.go
156 lines (138 loc) · 3.25 KB
/
app.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
156
package app
import (
"context"
"html/template"
"net/http"
"os"
"sync"
"time"
"github.com/spyzhov/healthy/config"
"github.com/spyzhov/healthy/executor"
"github.com/spyzhov/healthy/step"
"github.com/spyzhov/safe"
"go.uber.org/zap"
)
type Application struct {
// region System
Config *Config
Logger *zap.Logger
Info *BuildInfo
Ctx context.Context
CtxCancel context.CancelFunc
WaitGroup *sync.WaitGroup
error chan error
once sync.Once
// endregion
// region Service
StepConfig *config.Config
StepGroups *step.Groups
Executor *executor.Executor
// endregion
// region Web
Http *http.ServeMux
Management *http.ServeMux
templates map[string]*template.Template
favicon []byte
// endregion
// region Cli
Status int
// endregion
}
func New() (app *Application, err error) {
app = &Application{
// region System
error: make(chan error, 1),
Info: NewBuildInfo(),
WaitGroup: new(sync.WaitGroup),
StepGroups: step.NewGroups(),
// endregion
// region Web
Http: http.NewServeMux(),
Management: http.NewServeMux(),
// endregion
}
app.Ctx, app.CtxCancel = context.WithCancel(context.Background())
defer func() {
if err != nil {
app.Close()
}
}()
app.Config, err = NewConfig()
if err != nil {
return nil, err
}
if app.Config.CallVersion {
app.printVersion()
}
app.Logger, err = NewLogger(app.Config.LogLevel, app.Config.LogFormat)
if err != nil {
return nil, err
}
app.Logger.Debug("debug mode on")
return app, app.init()
}
// Close all necessary resources
func (app *Application) Close() {
zap.L().Debug("Application stops")
if app == nil {
return
}
defer os.Exit(app.Status)
defer close(app.error)
defer safe.Close(app.Executor, "Executor")
//defer safe.Close(app.Resource, "resource name")
}
// Start initialize all long-living processes
func (app *Application) Start() {
defer app.Stop()
if app.Config.CallWeb {
// Run HTTP handler
if err := app.RunHttp(app.Http, app.Config.Port, "HTTP Server"); err != nil {
app.Logger.Panic("HTTP Server start error", zap.Error(err))
}
// Run HTTP Management handler
if err := app.RunHttp(app.Management, app.Config.ManagementPort, "HTTP Management Server"); err != nil {
app.Logger.Panic("HTTP Management Server start error", zap.Error(err))
}
} else {
// Run Action
if err := app.RunAction(); err != nil {
app.Logger.Panic("Action start error", zap.Error(err))
}
}
select {
case err := <-app.error:
app.Logger.Panic("crashed", zap.Error(err))
case <-app.Ctx.Done():
app.Logger.Info("stops via context")
case sig := <-WaitExit():
app.Logger.Info("stop", zap.Stringer("signal", sig))
}
}
// Stop waits for all resources be cosed
func (app *Application) Stop() {
if app == nil {
return
}
app.Logger.Info("stopping...")
app.CtxCancel()
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
go func() {
defer cancel()
app.WaitGroup.Wait()
}()
<-ctx.Done()
if ctx.Err() != context.Canceled {
app.Logger.Panic("service stopped with timeout")
} else {
app.Logger.Info("service stopped with success")
}
}
// Error - register global error, but only once
func (app *Application) Error(err error) {
if !safe.IsNil(err) {
app.once.Do(func() {
app.error <- err
})
}
}