def __init__(self, wsgi_app_reference, config: MetricsApiConfig): """ Constructs and initializes MetricsMiddleware WSGI middleware to be passed into the currently running WSGI web server. Args: wsgi_app_reference ([type]): Reference to the current WSGI application, which will be wrapped config (MetricsApiConfig): Instance of MetricsApiConfig object """ self.config = config self.app = wsgi_app_reference self.metrics_core = Metrics(config)
class ReadMeMetrics: def __init__(self, config: MetricsApiConfig, app: Flask = None): self.config = config self.metrics_core = Metrics(config) if app is not None: self.init_app(app) def init_app(self, app: Flask): self.config.LOGGER.info( f"Configuring {app.name} hooks to call ReadMeMetrics extension functions" ) app.before_request(self.before_request) app.after_request(self.after_request) def before_request(self): try: request.rm_start_dt = datetime.utcnow().strftime( "%Y-%m-%dT%H:%M:%SZ") request.rm_start_ts = int(time.time() * 1000) if "Content-Length" in request.headers or request.data: request.rm_content_length = request.headers[ "Content-Length"] or "0" request.rm_body = request.data or "" except Exception as e: # Errors in the Metrics SDK should never cause the application to # throw an error. Log it but don't re-raise. self.config.LOGGER.exception(e) def after_request(self, response): try: response_info = ResponseInfoWrapper( response.headers, response.status, content_type=None, content_length=None, body=response.data, ) self.metrics_core.process(request, response_info) except Exception as e: # Errors in the Metrics SDK should never cause the application to # throw an error. Log it but don't re-raise. self.config.LOGGER.exception(e) finally: return response
class MetricsMiddleware: def __init__(self, get_response, config=None): self.get_response = get_response self.config = config or settings.README_METRICS_CONFIG assert isinstance(self.config, MetricsApiConfig) self.metrics_core = Metrics(self.config) def __call__(self, request): try: request.rm_start_dt = str(datetime.utcnow()) request.rm_start_ts = int(time.time() * 1000) if request.headers["Content-Length"] or request.body: request.rm_content_length = request.headers[ "Content-Length"] or "0" request.rm_body = request.body or "" except Exception as e: # Errors in the Metrics SDK should never cause the application to # throw an error. Log it but don't re-raise. self.config.LOGGER.exception(e) response = self.get_response(request) try: try: body = response.content.decode("utf-8") except UnicodeDecodeError: body = "[NOT VALID UTF-8]" response_info = ResponseInfoWrapper( response.headers, response.status_code, content_type=None, content_length=None, body=body, ) self.metrics_core.process(request, response_info) except Exception as e: # Errors in the Metrics SDK should never cause the application to # throw an error. Log it but don't re-raise. self.config.LOGGER.exception(e) finally: return response
class MetricsMiddleware: """Core middleware class for ReadMe Metrics Attributes: config (MetricsApiConfig): Contains the configuration settings for the running middleware instance """ def __init__(self, wsgi_app_reference, config: MetricsApiConfig): """ Constructs and initializes MetricsMiddleware WSGI middleware to be passed into the currently running WSGI web server. Args: wsgi_app_reference ([type]): Reference to the current WSGI application, which will be wrapped config (MetricsApiConfig): Instance of MetricsApiConfig object """ self.config = config self.app = wsgi_app_reference self.metrics_core = Metrics(config) def __call__(self, environ, start_response): """Method that is called by the running WSGI server. You should NOT be calling this method yourself under normal circumstances. """ response_headers = {} response_status = 0 iterable = None req = Request(environ) def _start_response(_status, _response_headers, *args): write = start_response(_status, _response_headers, *args) # Populate response info (headers & status) nonlocal response_headers, response_status response_headers = _response_headers response_status = _status return write try: req.rm_start_dt = str(datetime.datetime.now()) req.rm_start_ts = int(time.time() * 1000) if req.method == "POST": # The next 4 lines are a workaround for a serious shortcoming in the # WSGI spec. # # The data can only be read once, after which the socket is exhausted # and cannot be read again. As such, we read the data and then # repopulate the variable so that it can be used by other code down the # pipeline. # # For more info: https://stackoverflow.com/a/13106009/643951 # the environment variable CONTENT_LENGTH may be empty or missing try: content_length = int(environ.get("CONTENT_LENGTH", 0)) except (ValueError): content_length = 0 content_body = environ["wsgi.input"].read(content_length) # guarding check to close stream if hasattr(environ["CONTENT_LENGTH"], "close"): environ["wsgi.input"].close() environ["wsgi.input"] = io.BytesIO(content_body) req.rm_content_length = content_length req.rm_body = content_body iterable = self.app(environ, _start_response) for data in iterable: res_ctype = "" res_clength = 0 htype = next( (h for h in response_headers if h[0] == "Content-Type"), None) hlength = next( (h for h in response_headers if h[0] == "Content-Length"), None) if htype and hlength: res_ctype = htype[1] res_clength = int(hlength[1]) # Populate response body res = ResponseInfoWrapper( response_headers, response_status, res_ctype, res_clength, data.decode("utf-8"), ) # Send off data to be queued (and processed) by ReadMe if allowed if self.config.ALLOWED_HTTP_HOSTS: if environ["HTTP_HOST"] in self.config.ALLOWED_HTTP_HOSTS: self.metrics_core.process(req, res) else: # If the allowed_http_hosts has not been set (None by default), send off the data to be queued self.metrics_core.process(req, res) yield data finally: # Undocumented in WSGI spec but the iterable has to be closed if hasattr(iterable, "close"): iterable.close()
def __init__(self, get_response, config=None): self.get_response = get_response self.config = config or settings.README_METRICS_CONFIG assert isinstance(self.config, MetricsApiConfig) self.metrics_core = Metrics(self.config)
def __init__(self, config: MetricsApiConfig, app: Flask = None): self.config = config self.metrics_core = Metrics(config) if app is not None: self.init_app(app)