• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cshum / imagor / 4142091150

10 Feb 2023 08:24AM UTC coverage: 90.999% (-0.2%) from 91.179%
4142091150

Pull #282

github

Adrian Shum
refactor interfaces
Pull Request #282: feat(server): Add optional Prometheus metrics server

67 of 67 new or added lines in 4 files covered. (100.0%)

5530 of 6077 relevant lines covered (91.0%)

1.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.44
/server/server.go
1
package server
2

3
import (
4
        "context"
5
        "net/http"
6
        "os/signal"
7
        "strconv"
8
        "syscall"
9
        "time"
10

11
        "go.uber.org/zap"
12
)
13

14
// Service is a http.Handler with Startup and Shutdown lifecycle
15
type Service interface {
16
        http.Handler
17

18
        // Startup controls app startup
19
        Startup(ctx context.Context) error
20

21
        // Shutdown controls app shutdown
22
        Shutdown(ctx context.Context) error
23
}
24

25
// Metrics represents metrics server Startup an d Shutdown lifecycle
26
type Metrics interface {
27
        Startup(ctx context.Context) error
28
        Shutdown(ctx context.Context) error
29
}
30

31
// Server wraps the Service with additional http and app lifecycle handling
32
type Server struct {
33
        http.Server
34
        App             Service
35
        Address         string
36
        Port            int
37
        CertFile        string
38
        KeyFile         string
39
        PathPrefix      string
40
        StartupTimeout  time.Duration
41
        ShutdownTimeout time.Duration
42
        Logger          *zap.Logger
43
        Debug           bool
44
        Metrics         Metrics
45
}
46

47
// New create new Server
48
func New(app Service, options ...Option) *Server {
1✔
49
        s := &Server{}
1✔
50
        s.App = app
1✔
51
        s.Port = 8000
1✔
52
        s.MaxHeaderBytes = 1 << 20
1✔
53
        s.StartupTimeout = time.Second * 10
1✔
54
        s.ShutdownTimeout = time.Second * 10
1✔
55
        s.Logger = zap.NewNop()
1✔
56
        s.Handler = pathHandler(http.MethodGet, map[string]http.HandlerFunc{
1✔
57
                "/favicon.ico": handleOk,
1✔
58
                "/healthcheck": handleOk,
1✔
59
        })(s.App)
1✔
60

1✔
61
        for _, option := range options {
2✔
62
                option(s)
1✔
63
        }
1✔
64
        if s.PathPrefix != "" {
2✔
65
                s.Handler = http.StripPrefix(s.PathPrefix, s.Handler)
1✔
66
        }
1✔
67
        s.Handler = s.panicHandler(s.Handler)
1✔
68
        if s.Addr == "" {
2✔
69
                s.Addr = s.Address + ":" + strconv.Itoa(s.Port)
1✔
70
        }
1✔
71
        s.ErrorLog = newServerErrorLog(s.Logger)
1✔
72
        return s
1✔
73
}
74

75
// Run server that terminates on SIGINT, SIGTERM signals
76
func (s *Server) Run() {
×
77
        ctx, cancel := signal.NotifyContext(
×
78
                context.Background(), syscall.SIGINT, syscall.SIGTERM)
×
79
        defer cancel()
×
80
        s.RunContext(ctx)
×
81
}
×
82

83
// RunContext run server with context
84
func (s *Server) RunContext(ctx context.Context) {
1✔
85
        s.startup(ctx)
1✔
86

1✔
87
        go func() {
2✔
88
                if err := s.listenAndServe(); err != nil && err != http.ErrServerClosed {
1✔
89
                        s.Logger.Fatal("listen", zap.Error(err))
×
90
                }
×
91
        }()
92
        s.Logger.Info("listen", zap.String("addr", s.Addr))
1✔
93

1✔
94
        if s.Metrics != nil {
1✔
95
                if err := s.Metrics.Startup(ctx); err != nil {
×
96
                        s.Logger.Fatal("metrics-startup", zap.Error(err))
×
97
                }
×
98
        }
99

100
        <-ctx.Done()
1✔
101

1✔
102
        s.shutdown(context.Background())
1✔
103
}
104

105
func (s *Server) startup(ctx context.Context) {
1✔
106
        ctx, cancel := context.WithTimeout(ctx, s.StartupTimeout)
1✔
107
        defer cancel()
1✔
108
        if err := s.App.Startup(ctx); err != nil {
1✔
109
                s.Logger.Fatal("app-startup", zap.Error(err))
×
110
        }
×
111
}
112

113
func (s *Server) shutdown(ctx context.Context) {
1✔
114
        ctx, cancel := context.WithTimeout(ctx, s.ShutdownTimeout)
1✔
115
        defer cancel()
1✔
116
        s.Logger.Info("shutdown")
1✔
117
        if s.Metrics != nil {
1✔
118
                if err := s.Metrics.Shutdown(ctx); err != nil {
×
119
                        s.Logger.Error("metrics-shutdown", zap.Error(err))
×
120
                }
×
121
        }
122
        if err := s.Shutdown(ctx); err != nil {
1✔
123
                s.Logger.Error("server-shutdown", zap.Error(err))
×
124
        }
×
125
        if err := s.App.Shutdown(ctx); err != nil {
1✔
126
                s.Logger.Error("app-shutdown", zap.Error(err))
×
127
        }
×
128
}
129

130
func (s *Server) listenAndServe() error {
1✔
131
        if s.CertFile != "" && s.KeyFile != "" {
1✔
132
                return s.ListenAndServeTLS(s.CertFile, s.KeyFile)
×
133
        }
×
134
        return s.ListenAndServe()
1✔
135
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc