@@ -3,18 +3,31 @@ package metrics
33import (
44 "context"
55 "errors"
6+ "net"
67 "net/http"
78 "strconv"
89 "time"
910
1011 "github.com/prometheus/client_golang/prometheus/promhttp"
1112 "github.com/rs/zerolog"
13+ "github.com/rs/zerolog/log"
14+
15+ "github.com/onflow/flow-go/module/component"
16+ "github.com/onflow/flow-go/module/irrecoverable"
1217)
1318
19+ // metricsServerShutdownTimeout is the time to wait for the server to shut down gracefully
20+ const metricsServerShutdownTimeout = 5 * time .Second
21+
1422// Server is the http server that will be serving the /metrics request for prometheus
1523type Server struct {
16- server * http.Server
17- log zerolog.Logger
24+ component.Component
25+
26+ address string
27+ server * http.Server
28+ log zerolog.Logger
29+
30+ startupCompleted chan struct {}
1831}
1932
2033// NewServer creates a new server that will start on the specified port,
@@ -25,44 +38,72 @@ func NewServer(log zerolog.Logger, port uint) *Server {
2538 mux := http .NewServeMux ()
2639 endpoint := "/metrics"
2740 mux .Handle (endpoint , promhttp .Handler ())
28- log .Info ().Str ("address" , addr ).Str ("endpoint" , endpoint ).Msg ("metrics server started" )
2941
3042 m := & Server {
31- server : & http.Server {Addr : addr , Handler : mux },
32- log : log ,
43+ address : addr ,
44+ server : & http.Server {Addr : addr , Handler : mux },
45+ log : log .With ().Str ("address" , addr ).Str ("endpoint" , endpoint ).Logger (),
46+ startupCompleted : make (chan struct {}),
3347 }
3448
49+ m .Component = component .NewComponentManagerBuilder ().
50+ AddWorker (m .serve ).
51+ AddWorker (m .shutdownOnContextDone ).
52+ Build ()
53+
3554 return m
3655}
3756
38- // Ready returns a channel that will close when the network stack is ready.
39- func (m * Server ) Ready () <- chan struct {} {
40- ready := make (chan struct {})
41- go func () {
42- if err := m .server .ListenAndServe (); err != nil {
43- // http.ErrServerClosed is returned when Close or Shutdown is called
44- // we don't consider this an error, so print this with debug level instead
45- if errors .Is (err , http .ErrServerClosed ) {
46- m .log .Debug ().Err (err ).Msg ("metrics server shutdown" )
47- } else {
48- m .log .Err (err ).Msg ("error shutting down metrics server" )
49- }
57+ func (m * Server ) serve (ctx irrecoverable.SignalerContext , ready component.ReadyFunc ) {
58+ m .log .Info ().Msg ("starting metrics server on address" )
59+
60+ l , err := net .Listen ("tcp" , m .address )
61+ if err != nil {
62+ m .log .Err (err ).Msg ("failed to start the metrics server" )
63+ ctx .Throw (err )
64+ return
65+ }
66+
67+ ready ()
68+
69+ // pass the signaler context to the server so that the signaler context
70+ // can control the server's lifetime
71+ m .server .BaseContext = func (_ net.Listener ) context.Context {
72+ return ctx
73+ }
74+
75+ err = m .server .Serve (l ) // blocking call
76+ if err != nil {
77+ if errors .Is (err , http .ErrServerClosed ) {
78+ return
5079 }
51- }()
52- go func () {
53- close (ready )
54- }()
55- return ready
80+ log .Err (err ).Msg ("fatal error in the metrics server" )
81+ ctx .Throw (err )
82+ }
5683}
5784
58- // Done returns a channel that will close when shutdown is complete.
59- func (m * Server ) Done () <- chan struct {} {
60- done := make (chan struct {})
61- go func () {
62- ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
63- _ = m .server .Shutdown (ctx )
64- cancel ()
65- close (done )
66- }()
67- return done
85+ func (m * Server ) shutdownOnContextDone (ictx irrecoverable.SignalerContext , ready component.ReadyFunc ) {
86+ ready ()
87+ <- ictx .Done ()
88+
89+ ctx , cancel := context .WithTimeout (context .Background (), metricsServerShutdownTimeout )
90+ defer cancel ()
91+
92+ // shutdown the server gracefully
93+ err := m .server .Shutdown (ctx )
94+ if err == nil {
95+ m .log .Info ().Msg ("metrics server graceful shutdown completed" )
96+ return
97+ }
98+
99+ if errors .Is (err , ctx .Err ()) {
100+ m .log .Warn ().Msg ("metrics server graceful shutdown timed out" )
101+ // shutdown the server forcefully
102+ err := m .server .Close ()
103+ if err != nil {
104+ m .log .Err (err ).Msg ("error closing metrics server" )
105+ }
106+ } else {
107+ m .log .Err (err ).Msg ("error shutting down metrics server" )
108+ }
68109}
0 commit comments