Skip to content

Logging module

encoder

Custom encoder based on the pydantic encoder with additional types enabled. (see JSON_ENCODERS)

Note

This is necessary since the Pydantic JSON encoder cannot handle Pattern fields as of v1.7.3. The code exists already on the master branch and should be part of the next release.

LOGGER_NAME

The name of the Cyber Range Kyoushi Simulation logger

configure_logging(logging_config)

Configures the logging system based on the passed LoggingConfig

Parameters:

Name Type Description Default
logging_config LoggingConfig

The logging configuration object

required
Source code in simulation/logging.py
def configure_logging(logging_config: LoggingConfig):
    """Configures the logging system based on the passed
    [`LoggingConfig`][cr_kyoushi.simulation.config.LoggingConfig]

    Args:
        logging_config: The logging configuration object
    """
    # ensure we start from default config
    structlog.reset_defaults()

    timestamper = structlog.processors.TimeStamper(
        fmt=logging_config.timestamp.format,
        utc=logging_config.timestamp.utc,
        key=logging_config.timestamp.key,
    )
    # shared processors for standard lib and structlog
    shared_processors: List[
        Callable[
            [Any, str, MutableMapping[str, Any]],
            Union[Mapping[str, Any], str, bytes, Tuple[Any, ...]],
        ]
    ] = [
        structlog.stdlib.add_log_level,
        timestamper,
        structlog.stdlib.PositionalArgumentsFormatter(),
        structlog.processors.StackInfoRenderer(),
        structlog.processors.format_exc_info,
    ]

    # processor only for structlog
    processors: List[
        Callable[
            [Any, str, MutableMapping[str, Any]],
            Union[Mapping[str, Any], str, bytes, Tuple[Any, ...]],
        ]
    ] = [structlog.stdlib.filter_by_level]
    processors.extend(shared_processors)
    # the processor formatter wrapper must be last as it changes the
    processors.append(structlog.stdlib.ProcessorFormatter.wrap_for_formatter)

    handlers = {}
    # configure console logging
    if logging_config.console.enabled:
        handlers["console"] = {
            "level": "DEBUG",
            "class": "logging.StreamHandler",
            "formatter": logging_config.console.format,
        }

    # configure file logging
    if logging_config.file.enabled:
        handlers["file"] = {
            "level": "DEBUG",
            "class": "logging.handlers.WatchedFileHandler",
            "filename": str(logging_config.file.path.absolute()),
            "formatter": logging_config.file.format,
        }

    # configure standard lib logging
    logging.config.dictConfig(
        {
            "version": 1,
            "disable_existing_loggers": False,
            "formatters": {
                LogFormat.PLAIN: {
                    "()": structlog.stdlib.ProcessorFormatter,
                    "processor": structlog.dev.ConsoleRenderer(colors=False),
                    "foreign_pre_chain": shared_processors,
                },
                LogFormat.COLORED: {
                    "()": structlog.stdlib.ProcessorFormatter,
                    "processor": structlog.dev.ConsoleRenderer(colors=True),
                    "foreign_pre_chain": shared_processors,
                },
                LogFormat.JSON: {
                    "()": structlog.stdlib.ProcessorFormatter,
                    # when logging to json we rename the "event" field to "message"
                    "processor": rename_event_key_wrapper(
                        structlog.processors.JSONRenderer(
                            serializer=json.dumps,
                            sort_keys=True,
                            default=encoder,
                        )
                    ),
                    "foreign_pre_chain": shared_processors,
                },
            },
            "handlers": handlers,
            "loggers": {LOGGER_NAME: {"handlers": handlers.keys(), "propagate": True}},
        }
    )

    # apply structlog config
    structlog.configure(
        processors=processors,
        context_class=dict,
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )

    # set the log level
    logger = structlog.get_logger(LOGGER_NAME)
    logger.setLevel(logging_config.level)

get_logger()

Convenience function for getting the Cyber Range Kyoushi Simulation logger.

Source code in simulation/logging.py
def get_logger() -> structlog.stdlib.BoundLogger:
    """Convenience function for getting the Cyber Range Kyoushi Simulation logger."""
    return structlog.stdlib.get_logger(LOGGER_NAME)

rename_event_key(logger, method_name, event_dict, field_name)

Processor for renaming the event key to message.

We do this since this is more compatible with the ecs schema.

Source code in simulation/logging.py
def rename_event_key(
    logger: logging.Logger,
    method_name: str,
    event_dict: MutableMapping[str, Any],
    field_name: str,
) -> MutableMapping[str, Any]:
    """Processor for renaming the event key to message.

    We do this since this is more compatible with the ecs schema.
    """
    event_dict[field_name] = event_dict.pop("event")
    return event_dict

rename_event_key_wrapper(func, field_name='message')

Processor wrapper that renames the event field before calling the given processor.

Parameters:

Name Type Description Default
func Callable[[logging.Logger, str, MutableMapping[str, Any]], Any]

The processor to be wrapped

required
field_name str

The field name to use instead of event

'message'

Returns:

Type Description
Callable[[logging.Logger, str, MutableMapping[str, Any]], MutableMapping[str, Any]]

The wrapped processor

Source code in simulation/logging.py
def rename_event_key_wrapper(
    func: Callable[[logging.Logger, str, MutableMapping[str, Any]], Any],
    field_name: str = "message",
) -> Callable[
    [logging.Logger, str, MutableMapping[str, Any]], MutableMapping[str, Any]
]:
    """Processor wrapper that renames the event field before calling the given processor.

    Args:
        func: The processor to be wrapped
        field_name: The field name to use instead of `event`

    Returns:
        The wrapped processor
    """

    def _rename_event_key(
        logger: logging.Logger, method_name: str, event_dict: MutableMapping[str, Any]
    ) -> MutableMapping[str, Any]:
        event_dict = rename_event_key(logger, method_name, event_dict, field_name)
        return func(logger, method_name, event_dict)

    return _rename_event_key