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 }