101 lines
2.7 KiB
Go
101 lines
2.7 KiB
Go
// Package startup reports process initialization without exposing private diagnostics.
|
|
package startup
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Phase string
|
|
|
|
const (
|
|
OpeningDatabase Phase = "opening_database"
|
|
BackingUpDatabase Phase = "backing_up_database"
|
|
ApplyingMigrations Phase = "applying_migrations"
|
|
InitializingServices Phase = "initializing_services"
|
|
RecoveringSessions Phase = "recovering_sessions"
|
|
Ready Phase = "ready"
|
|
)
|
|
|
|
// Label accepts only protocol-owned phase names, never arbitrary server text.
|
|
func (p Phase) Label() string {
|
|
switch p {
|
|
case OpeningDatabase:
|
|
return "Opening database"
|
|
case BackingUpDatabase:
|
|
return "Backing up database"
|
|
case ApplyingMigrations:
|
|
return "Applying migrations"
|
|
case InitializingServices:
|
|
return "Initializing services"
|
|
case RecoveringSessions:
|
|
return "Recovering sessions"
|
|
case Ready:
|
|
return "Ready"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
type Snapshot struct {
|
|
Phase Phase `json:"phase"`
|
|
ElapsedMS int64 `json:"elapsed_ms"`
|
|
PhaseElapsedMS int64 `json:"phase_elapsed_ms"`
|
|
}
|
|
|
|
type Reporter struct {
|
|
mu sync.Mutex
|
|
started, phaseStarted time.Time
|
|
phase Phase
|
|
log *logger.Logger
|
|
}
|
|
|
|
type contextKey struct{}
|
|
|
|
func New(log *logger.Logger) *Reporter {
|
|
now := time.Now()
|
|
return &Reporter{started: now, phaseStarted: now, phase: OpeningDatabase, log: log}
|
|
}
|
|
func WithReporter(ctx context.Context, r *Reporter) context.Context {
|
|
return context.WithValue(ctx, contextKey{}, r)
|
|
}
|
|
func FromContext(ctx context.Context) *Reporter {
|
|
r, _ := ctx.Value(contextKey{}).(*Reporter)
|
|
return r
|
|
}
|
|
func SetPhase(ctx context.Context, phase Phase) {
|
|
if r := FromContext(ctx); r != nil {
|
|
r.Set(phase)
|
|
}
|
|
}
|
|
func (r *Reporter) Snapshot() Snapshot {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return Snapshot{Phase: r.phase, ElapsedMS: time.Since(r.started).Milliseconds(), PhaseElapsedMS: time.Since(r.phaseStarted).Milliseconds()}
|
|
}
|
|
func (r *Reporter) Set(phase Phase) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if r.phase == phase {
|
|
return
|
|
}
|
|
if r.log != nil {
|
|
r.log.Info("Startup phase completed", zap.String("phase", string(r.phase)), zap.Duration("duration", time.Since(r.phaseStarted)), zap.Duration("elapsed", time.Since(r.started)))
|
|
}
|
|
r.phase = phase
|
|
r.phaseStarted = time.Now()
|
|
if r.log != nil {
|
|
r.log.Info("Startup phase started", zap.String("phase", string(phase)), zap.Duration("elapsed", time.Since(r.started)))
|
|
}
|
|
}
|
|
func (r *Reporter) Report() {
|
|
s := r.Snapshot()
|
|
if r.log != nil && s.Phase != Ready {
|
|
r.log.Info("Startup in progress", zap.String("phase", string(s.Phase)), zap.Int64("elapsed_ms", s.ElapsedMS), zap.Int64("phase_elapsed_ms", s.PhaseElapsedMS))
|
|
}
|
|
}
|