import logging import sys import uuid import structlog from flask import request from structlog_sentry import SentryProcessor, SentryJsonProcessor from werkzeug.serving import WSGIRequestHandler from core_notifications.settings import Settings logger = structlog.get_logger() def configure_logging(): """ Configures logging with Structlog for 2 cases: 1. JSON logging for final dev/stg/prod envs. 2. Prettified console logging for local development, running tests in pipelines etc. """ if Settings.JSON_LOGS: shared_processors = [ structlog.contextvars.merge_contextvars, structlog.stdlib.add_log_level, structlog.stdlib.add_logger_name, SentryJsonProcessor(level=logging.ERROR), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, ] structlog.configure( processors=[ structlog.stdlib.filter_by_level, *shared_processors, structlog.stdlib.PositionalArgumentsFormatter(), structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) formatter = structlog.stdlib.ProcessorFormatter( processor=structlog.processors.JSONRenderer(), foreign_pre_chain=shared_processors, ) else: shared_processors = [ structlog.contextvars.merge_contextvars, structlog.stdlib.add_log_level, structlog.stdlib.add_logger_name, SentryProcessor(level=logging.ERROR), structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M.%S"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.ExceptionPrettyPrinter(), ] structlog.configure( processors=[ structlog.stdlib.filter_by_level, *shared_processors, structlog.stdlib.PositionalArgumentsFormatter(), structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) formatter = structlog.stdlib.ProcessorFormatter( processor=structlog.dev.ConsoleRenderer(), foreign_pre_chain=shared_processors, ) handler = logging.StreamHandler(stream=sys.stdout) handler.setLevel(Settings.LOGLEVEL) handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(Settings.LOGLEVEL) # Override the built-in werkzeug requests logging to match our format werkzeug_logger = logging.getLogger("werkzeug") WSGIRequestHandler.log = lambda self, type, message, *args: getattr( werkzeug_logger, type )( "%s" % (message % args), ) def register_logging_context_hooks(app): def bing_generic_data(response=None): structlog.contextvars.clear_contextvars() structlog.contextvars.bind_contextvars( path=request.path, endpoint=request.endpoint, request_id=str(uuid.uuid4()), ip=request.remote_addr, ) return response app.before_request(bing_generic_data) def register_logging_events_hooks(app): def log_request(response=None): logger.info(f"{request.method} {request.path} {response.status_code}") return response app.after_request(log_request)