app long start/stop logger

This commit is contained in:
Roman Khafizianov 2023-03-21 15:27:15 +01:00 committed by Mikhail Iudin
parent 53ed41bb55
commit 0c6aff5af7
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0

View File

@ -21,6 +21,9 @@ var (
var ( var (
log = logger.NewNamed("app") log = logger.NewNamed("app")
StopDeadline = time.Minute
StopWarningAfter = time.Second * 10
StartWarningAfter = time.Second * 10
) )
// Component is a minimal interface for a common app.Component // Component is a minimal interface for a common app.Component
@ -53,7 +56,8 @@ type ComponentStatable interface {
type App struct { type App struct {
components []Component components []Component
mu sync.RWMutex mu sync.RWMutex
startStat StartStat startStat Stat
stopStat Stat
deviceState int deviceState int
} }
@ -67,18 +71,25 @@ func (app *App) Version() string {
return GitSummary return GitSummary
} }
type StartStat struct { type Stat struct {
SpentMsPerComp map[string]int64 SpentMsPerComp map[string]int64
SpentMsTotal int64 SpentMsTotal int64
} }
// StartStat returns total time spent per comp // StartStat returns total time spent per comp for the last Start
func (app *App) StartStat() StartStat { func (app *App) StartStat() Stat {
app.mu.Lock() app.mu.Lock()
defer app.mu.Unlock() defer app.mu.Unlock()
return app.startStat return app.startStat
} }
// StopStat returns total time spent per comp for the last Close
func (app *App) StopStat() Stat {
app.mu.Lock()
defer app.mu.Unlock()
return app.stopStat
}
// VersionDescription return the full info about the build // VersionDescription return the full info about the build
func (app *App) VersionDescription() string { func (app *App) VersionDescription() string {
return VersionDescription() return VersionDescription()
@ -158,7 +169,17 @@ func (app *App) Start(ctx context.Context) (err error) {
app.mu.RLock() app.mu.RLock()
defer app.mu.RUnlock() defer app.mu.RUnlock()
app.startStat.SpentMsPerComp = make(map[string]int64) app.startStat.SpentMsPerComp = make(map[string]int64)
var currentComponentStarting string
done := make(chan struct{})
go func() {
select {
case <-done:
return
case <-time.After(StartWarningAfter):
l := statLogger(app.stopStat, log).With(zap.String("in_progress", currentComponentStarting))
l.Warn("components start in progress")
}
}()
closeServices := func(idx int) { closeServices := func(idx int) {
for i := idx; i >= 0; i-- { for i := idx; i >= 0; i-- {
if serviceClose, ok := app.components[i].(ComponentRunnable); ok { if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
@ -188,7 +209,13 @@ func (app *App) Start(ctx context.Context) (err error) {
app.startStat.SpentMsPerComp[s.Name()] = spent app.startStat.SpentMsPerComp[s.Name()] = spent
} }
} }
log.Debug("all components started")
close(done)
l := statLogger(app.stopStat, log)
if app.startStat.SpentMsTotal > StartWarningAfter.Milliseconds() {
l.Warn("all components started")
}
l.Debug("all components started")
return return
} }
@ -203,18 +230,41 @@ func stackAllGoroutines() []byte {
} }
} }
func statLogger(stat Stat, ctxLogger logger.CtxLogger) logger.CtxLogger {
l := ctxLogger
for k, v := range stat.SpentMsPerComp {
l = l.With(zap.Int64(k, v))
}
l = l.With(zap.Int64("total", stat.SpentMsTotal))
return l
}
// Close stops the application // Close stops the application
// All components with ComponentRunnable implementation will be closed in the reversed order // All components with ComponentRunnable implementation will be closed in the reversed order
func (app *App) Close(ctx context.Context) error { func (app *App) Close(ctx context.Context) error {
log.Debug("close components...") log.Debug("close components...")
app.mu.RLock() app.mu.RLock()
defer app.mu.RUnlock() defer app.mu.RUnlock()
app.stopStat.SpentMsPerComp = make(map[string]int64)
var currentComponentStopping string
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
select { select {
case <-done: case <-done:
return return
case <-time.After(time.Minute): case <-time.After(StopWarningAfter):
statLogger(app.stopStat, log).
With(zap.String("in_progress", currentComponentStopping)).
Warn("components close in progress")
}
}()
go func() {
select {
case <-done:
return
case <-time.After(StopDeadline):
_, _ = os.Stderr.Write([]byte("app.Close timeout\n")) _, _ = os.Stderr.Write([]byte("app.Close timeout\n"))
_, _ = os.Stderr.Write(stackAllGoroutines()) _, _ = os.Stderr.Write(stackAllGoroutines())
panic("app.Close timeout") panic("app.Close timeout")
@ -224,16 +274,27 @@ func (app *App) Close(ctx context.Context) error {
var errs []string var errs []string
for i := len(app.components) - 1; i >= 0; i-- { for i := len(app.components) - 1; i >= 0; i-- {
if serviceClose, ok := app.components[i].(ComponentRunnable); ok { if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
start := time.Now()
currentComponentStopping = app.components[i].Name()
if e := serviceClose.Close(ctx); e != nil { if e := serviceClose.Close(ctx); e != nil {
errs = append(errs, fmt.Sprintf("Component '%s' close error: %v", serviceClose.Name(), e)) errs = append(errs, fmt.Sprintf("Component '%s' close error: %v", serviceClose.Name(), e))
} }
spent := time.Since(start).Milliseconds()
app.stopStat.SpentMsTotal += spent
app.stopStat.SpentMsPerComp[app.components[i].Name()] = spent
} }
} }
close(done) close(done)
if len(errs) > 0 { if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n")) return errors.New(strings.Join(errs, "\n"))
} }
log.Debug("all components have been closed")
l := statLogger(app.stopStat, log)
if app.stopStat.SpentMsTotal > StopWarningAfter.Milliseconds() {
l.Warn("all components have been closed")
}
l.Debug("all components have been closed")
return nil return nil
} }