|
| 1 | +import logging |
| 2 | + |
| 3 | +import structlog |
| 4 | + |
| 5 | +STRUCTURED_LOGGING_CONFIGURED = False |
| 6 | + |
| 7 | + |
| 8 | +def flatten_extra_field( |
| 9 | + logger: logging.Logger, log_method: str, event_dict: structlog.types.EventDict |
| 10 | +): |
| 11 | + """This flattens the `extra` dict that is allowed in stdlib's logging so |
| 12 | + that it appears as top level items in the log message.""" |
| 13 | + if "extra" in event_dict: |
| 14 | + for key, value in event_dict["extra"].items(): |
| 15 | + event_dict[key] = value |
| 16 | + del event_dict["extra"] |
| 17 | + return event_dict |
| 18 | + |
| 19 | + |
| 20 | +def ensure_event_type( |
| 21 | + logger: logging.Logger, log_method: str, event_dict: structlog.types.EventDict |
| 22 | +): |
| 23 | + """This allows us to add an event_type to logs so we can do filtering in the |
| 24 | + log UI""" |
| 25 | + |
| 26 | + if "event_type" not in event_dict: |
| 27 | + event_dict["event_type"] = "default" |
| 28 | + |
| 29 | + return event_dict |
| 30 | + |
| 31 | + |
| 32 | +def configure_structured_logging() -> None: |
| 33 | + """Configure structured logging for the application. This was taken almost |
| 34 | + directly from the structlog docs. See: |
| 35 | +
|
| 36 | + https://www.structlog.org/en/stable/standard-library.html#rendering-using-structlog-based-formatters-within-logging |
| 37 | +
|
| 38 | + This sets up a structured handler at the root logger. It does not configure |
| 39 | + the level of the root logger, so it is up to the user to enable the |
| 40 | + appropriate level so that logs appear on stdout. |
| 41 | +
|
| 42 | + This should be called at the start of an application. |
| 43 | + """ |
| 44 | + global STRUCTURED_LOGGING_CONFIGURED |
| 45 | + if STRUCTURED_LOGGING_CONFIGURED: |
| 46 | + return |
| 47 | + STRUCTURED_LOGGING_CONFIGURED = True |
| 48 | + |
| 49 | + timestamper = structlog.processors.TimeStamper(fmt="iso", utc=True) |
| 50 | + shared_processors = [ |
| 51 | + flatten_extra_field, |
| 52 | + structlog.stdlib.add_logger_name, |
| 53 | + structlog.stdlib.add_log_level, |
| 54 | + timestamper, |
| 55 | + structlog.processors.StackInfoRenderer(), |
| 56 | + ensure_event_type, |
| 57 | + ] |
| 58 | + |
| 59 | + structlog.configure( |
| 60 | + processors=shared_processors |
| 61 | + + [ |
| 62 | + structlog.stdlib.ProcessorFormatter.wrap_for_formatter, |
| 63 | + ], |
| 64 | + logger_factory=structlog.stdlib.LoggerFactory(), |
| 65 | + cache_logger_on_first_use=True, |
| 66 | + ) |
| 67 | + |
| 68 | + formatter = structlog.stdlib.ProcessorFormatter( |
| 69 | + # These run ONLY on `logging` entries that do NOT originate within |
| 70 | + # structlog. |
| 71 | + foreign_pre_chain=shared_processors, |
| 72 | + # These run on ALL entries after the pre_chain is done. |
| 73 | + processors=[ |
| 74 | + # Remove _record & _from_structlog. |
| 75 | + structlog.stdlib.ProcessorFormatter.remove_processors_meta, |
| 76 | + structlog.processors.JSONRenderer(), |
| 77 | + ], |
| 78 | + ) |
| 79 | + |
| 80 | + handler = logging.StreamHandler() |
| 81 | + # Use OUR `ProcessorFormatter` to format all `logging` entries. |
| 82 | + handler.setFormatter(formatter) |
| 83 | + root_logger = logging.getLogger() |
| 84 | + root_logger.addHandler(handler) |
0 commit comments