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

cshum / imagor / 4142608967

10 Feb 2023 09:36AM UTC coverage: 91.071% (-0.1%) from 91.179%
4142608967

Pull #282

github

Adrian Shum
fix tests
Pull Request #282: feat(server): Add optional Prometheus metrics server

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

5538 of 6081 relevant lines covered (91.07%)

1.07 hits per line

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

70.67
/server/server.go
1
package server
2

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

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

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

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

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

26
// Metrics represents metrics Startup and Shutdown lifecycle
27
type Metrics interface {
28
        Startup(ctx context.Context) error
29
        Shutdown(ctx context.Context) error
30
}
31

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

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

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

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

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

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

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

101
        <-ctx.Done()
1✔
102

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

106
func isNil(c any) bool {
1✔
107
        return c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil())
1✔
108
}
1✔
109

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

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

135
func (s *Server) listenAndServe() error {
1✔
136
        if s.CertFile != "" && s.KeyFile != "" {
1✔
137
                return s.ListenAndServeTLS(s.CertFile, s.KeyFile)
×
138
        }
×
139
        return s.ListenAndServe()
1✔
140
}
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