1 /*******************************************************************************
2 
3     An HTTP response handler for Prometheus' requests, used in
4     `PrometheusListener`.
5 
6     Responds to GET requests only.
7 
8     Copyright:
9         Copyright (c) 2019 dunnhumby Germany GmbH.
10         All rights reserved
11 
12     License:
13         Boost Software License Version 1.0. See LICENSE.txt for details.
14 
15 *******************************************************************************/
16 
17 module ocean.util.prometheus.server.PrometheusHandler;
18 
19 import ocean.net.http.HttpConnectionHandler;
20 
21 /// ditto
22 public class PrometheusHandler : HttpConnectionHandler
23 {
24     import ocean.io.select.EpollSelectDispatcher : EpollSelectDispatcher;
25     import ocean.net.http.HttpRequest : HttpRequest;
26     import ocean.net.http.HttpConst: HttpResponseCode;
27     import ocean.net.http.consts.HttpMethod: HttpMethod;
28     import ocean.text.convert.Formatter : sformat;
29     import ocean.meta.types.Qualifiers;
30     import ocean.util.log.Logger : Logger, Log;
31     import ocean.util.prometheus.collector.CollectorRegistry :
32         CollectorRegistry;
33 
34     /// A static logger for logging information about connections.
35     private static Logger log;
36     static this ( )
37     {
38         this.log = Log.lookup("ocean.util.prometheus.server.PrometheusHandler");
39     }
40 
41     /// A CollectorRegistry instance that refers to the callbacks that need to
42     /// be called for stat collection.
43     private CollectorRegistry collector_registry;
44 
45     /// A buffer to hold error messages, if any exception while stat collection
46     /// is encountered.
47     private mstring err_buf;
48 
49     /***************************************************************************
50 
51         Constructor.
52 
53         Initializes an HTTP request handler instance for GET requests at the
54         `/metrics` endpoint.
55 
56         Params:
57             finalizer          = Finalizer callback of the select listener.
58             collector_registry = The CollectorRegistry instance, containing
59                                  references to the collection callbacks.
60             epoll              = The EpollSelectDispatcher instance used here.
61             stack_size         = The fiber stack size to use. Defaults to
62                                  `HttpConnectionHandler.default_stack_size`.
63 
64     ***************************************************************************/
65 
66     public this ( scope FinalizeDg finalizer, CollectorRegistry collector_registry,
67         EpollSelectDispatcher epoll,
68         size_t stack_size = HttpConnectionHandler.default_stack_size )
69     {
70         super(epoll, finalizer, stack_size, [HttpMethod.Get]);
71         this.collector_registry = collector_registry;
72     }
73 
74     /***************************************************************************
75 
76         Handles responses for incoming HTTP requests. Responds with stats when
77         the request endpoint is `/metrics`.
78         For all other endpoints, returns `HttpResponseCode.NotImplemented`.
79         If stat collection throws an error, responds with
80         `HttpResponseCode.InternalServerError`, with the exception message as
81         the response message body;
82 
83         Params:
84             response_msg_body = Body of the response body.
85 
86         Returns:
87             HTTP status code
88 
89     ***************************************************************************/
90 
91     override protected HttpResponseCode handleRequest (
92         out cstring response_msg_body )
93     {
94         if (this.request.uri_string() != "/metrics")
95         {
96             PrometheusHandler.log.info(
97                 "Received request at an unhandled endpoint: {}",
98                 this.request.uri_string());
99             return HttpResponseCode.NotImplemented;
100         }
101 
102         try
103         {
104             response_msg_body = this.collector_registry.collect();
105             return HttpResponseCode.OK;
106         }
107         catch (Exception ex)
108         {
109             err_buf.length = 0;
110             assumeSafeAppend(err_buf);
111 
112             sformat(err_buf, "{}({}):{}", ex.file, ex.line, ex.message());
113 
114             PrometheusHandler.log.error(err_buf);
115             response_msg_body = err_buf;
116 
117             return HttpResponseCode.InternalServerError;
118         }
119     }
120 
121     /***************************************************************************
122 
123         Logs an warning or error message when an IOWarning or IOError,
124         respectively, is caught.
125 
126         An IOWarning is thrown when a socket read/write operation results in an
127         end-of-flow or hung-up condition, an IOError when an error event is
128         triggered for a socket.
129 
130         Params:
131             e        = caught IOWarning or IOError
132             is_error = true: e was an IOError, false: e was an IOWarning
133 
134      **************************************************************************/
135 
136     override protected void notifyIOException (
137         ErrnoException e, bool is_error )
138     {
139         if (is_error)
140         {
141             PrometheusHandler.log.error("IOError encountered : {}({}):{}",
142                 e.file, e.line, e.message());
143         }
144         else
145         {
146             PrometheusHandler.log.warn("IOWarning encountered : {}({}):{}",
147                 e.file, e.line, e.message());
148         }
149     }
150 }