Module facetorch.logger

Functions

def get_logger(name: str = 'facetorch') ‑> logging.Logger
Expand source code
def get_logger(name: str = "facetorch") -> logging.Logger:
    """Return a shared logger without changing its configured state.

    Module-level timing hooks use this accessor so importing a component cannot
    override logging that an application configured explicitly.
    """

    return logging.getLogger(name)

Return a shared logger without changing its configured state.

Module-level timing hooks use this accessor so importing a component cannot override logging that an application configured explicitly.

Classes

class CustomJsonFormatter (*args,
json_default: Callable | None = None,
json_encoder: Callable | None = None,
json_serializer: Callable = <function dumps>,
json_indent: int | str | None = None,
json_ensure_ascii: bool = True,
**kwargs)
Expand source code
class CustomJsonFormatter(JsonFormatter):
    def add_fields(self, log_record, record, message_dict):
        super().add_fields(log_record, record, message_dict)
        # Remove taskName field if it exists and is None
        if "taskName" in log_record and log_record["taskName"] is None:
            del log_record["taskName"]

JSON formatter using the standard library's json for encoding

Args
-----=
args
see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]
json_default
a function for encoding non-standard objects
json_encoder
custom JSON encoder
json_serializer
a json.dumps-compatible callable that will be used to serialize the log record.
json_indent
indent parameter for the json_serializer
json_ensure_ascii
ensure_ascii parameter for the json_serializer
kwargs
see [BaseJsonFormatter][pythonjsonlogger.core.BaseJsonFormatter]

Ancestors

  • pythonjsonlogger.json.JsonFormatter
  • pythonjsonlogger.core.BaseJsonFormatter
  • logging.Formatter

Methods

def add_fields(self, log_record, record, message_dict)
Expand source code
def add_fields(self, log_record, record, message_dict):
    super().add_fields(log_record, record, message_dict)
    # Remove taskName field if it exists and is None
    if "taskName" in log_record and log_record["taskName"] is None:
        del log_record["taskName"]

Extract fields from a LogRecord for logging

This method can be overridden to implement custom logic for adding fields.

Args
-----=
log_data
data that will be logged
record
the record to extract data from
message_dict
dictionary that was logged instead of a message. e.g logger.info({"is_this_message_dict": True})

Changed in 4.0: log_record renamed to log_data

class LoggerJsonFile (name: str = 'facetorch',
level: int = 50,
path_file: str | None = None,
json_format: str = '%(asctime)s %(levelname)s %(message)s',
max_bytes: int = 10485760,
backup_count: int = 3)
Expand source code
class LoggerJsonFile:
    def __init__(
        self,
        name: str = "facetorch",
        level: int = logging.CRITICAL,
        path_file: Optional[str] = None,
        json_format: str = "%(asctime)s %(levelname)s %(message)s",
        max_bytes: int = 10 * 1024 * 1024,
        backup_count: int = 3,
    ):
        """Logger in json format that writes to a file and console.

        Args:
            name (str): Name of the logger.
            level (str): Level of the logger.
            path_file (str): Path to the log file.
            json_format (str): Format of the log record.
            max_bytes (int): Rotate file logs after this many bytes.
            backup_count (int): Number of rotated log files to retain.

        Attributes:
            logger (logging.Logger): Logger object.

        """
        self.name = name
        self.level = level
        self.path_file = path_file
        self.json_format = json_format
        if isinstance(max_bytes, bool) or not isinstance(max_bytes, int) or max_bytes < 1:
            raise ConfigurationError("max_bytes must be a positive integer.")
        if (
            isinstance(backup_count, bool)
            or not isinstance(backup_count, int)
            or backup_count < 1
        ):
            raise ConfigurationError("backup_count must be a positive integer.")
        self.max_bytes = max_bytes
        self.backup_count = backup_count

        self.logger = logging.getLogger(self.name)
        self.configure()

    def configure(self):
        """Configure JSON console/file handlers idempotently."""
        self.logger.setLevel(self.level)

        formatter = CustomJsonFormatter(fmt=self.json_format)
        stream_handlers = [
            handler
            for handler in self.logger.handlers
            if getattr(handler, "_facetorch_stream_handler", False)
        ]
        if not stream_handlers:
            json_handler = logging.StreamHandler()
            json_handler._facetorch_stream_handler = True
            self.logger.addHandler(json_handler)
            stream_handlers = [json_handler]
        for handler in stream_handlers:
            handler.setLevel(self.level)
            handler.setFormatter(formatter)

        normalized_path = (
            os.path.abspath(os.fspath(self.path_file))
            if self.path_file is not None
            else None
        )
        managed_file_handlers = [
            handler
            for handler in self.logger.handlers
            if getattr(handler, "_facetorch_file_handler", False)
        ]
        for handler in managed_file_handlers:
            if normalized_path is None or handler.baseFilename != normalized_path:
                self.logger.removeHandler(handler)
                handler.close()

        if normalized_path is not None:
            parent = os.path.dirname(normalized_path)
            if parent:
                try:
                    os.makedirs(parent, exist_ok=True)
                except OSError as exc:
                    raise ConfigurationError(
                        f"Cannot create log directory {parent!r}. Choose a writable "
                        "path_file or leave file logging disabled."
                    ) from exc
            file_handlers = [
                handler
                for handler in self.logger.handlers
                if getattr(handler, "_facetorch_file_handler", False)
                and handler.baseFilename == normalized_path
            ]
            for handler in file_handlers[1:]:
                self.logger.removeHandler(handler)
                handler.close()
            file_handlers = file_handlers[:1]
            if not file_handlers:
                path_file_handler = RotatingFileHandler(
                    normalized_path,
                    mode="a",
                    maxBytes=self.max_bytes,
                    backupCount=self.backup_count,
                )
                path_file_handler._facetorch_file_handler = True
                path_file_handler.setLevel(self.level)
                path_file_handler.setFormatter(formatter)
                self.logger.addHandler(path_file_handler)
            else:
                for handler in file_handlers:
                    handler.maxBytes = self.max_bytes
                    handler.backupCount = self.backup_count
                    handler.setLevel(self.level)
                    handler.setFormatter(formatter)

        self.logger.propagate = False

Logger in json format that writes to a file and console.

Args
-----=
name : str
Name of the logger.
level : str
Level of the logger.
path_file : str
Path to the log file.
json_format : str
Format of the log record.
max_bytes : int
Rotate file logs after this many bytes.
backup_count : int
Number of rotated log files to retain.
Attributes
-----=
logger : logging.Logger
Logger object.

Methods

def configure(self)
Expand source code
def configure(self):
    """Configure JSON console/file handlers idempotently."""
    self.logger.setLevel(self.level)

    formatter = CustomJsonFormatter(fmt=self.json_format)
    stream_handlers = [
        handler
        for handler in self.logger.handlers
        if getattr(handler, "_facetorch_stream_handler", False)
    ]
    if not stream_handlers:
        json_handler = logging.StreamHandler()
        json_handler._facetorch_stream_handler = True
        self.logger.addHandler(json_handler)
        stream_handlers = [json_handler]
    for handler in stream_handlers:
        handler.setLevel(self.level)
        handler.setFormatter(formatter)

    normalized_path = (
        os.path.abspath(os.fspath(self.path_file))
        if self.path_file is not None
        else None
    )
    managed_file_handlers = [
        handler
        for handler in self.logger.handlers
        if getattr(handler, "_facetorch_file_handler", False)
    ]
    for handler in managed_file_handlers:
        if normalized_path is None or handler.baseFilename != normalized_path:
            self.logger.removeHandler(handler)
            handler.close()

    if normalized_path is not None:
        parent = os.path.dirname(normalized_path)
        if parent:
            try:
                os.makedirs(parent, exist_ok=True)
            except OSError as exc:
                raise ConfigurationError(
                    f"Cannot create log directory {parent!r}. Choose a writable "
                    "path_file or leave file logging disabled."
                ) from exc
        file_handlers = [
            handler
            for handler in self.logger.handlers
            if getattr(handler, "_facetorch_file_handler", False)
            and handler.baseFilename == normalized_path
        ]
        for handler in file_handlers[1:]:
            self.logger.removeHandler(handler)
            handler.close()
        file_handlers = file_handlers[:1]
        if not file_handlers:
            path_file_handler = RotatingFileHandler(
                normalized_path,
                mode="a",
                maxBytes=self.max_bytes,
                backupCount=self.backup_count,
            )
            path_file_handler._facetorch_file_handler = True
            path_file_handler.setLevel(self.level)
            path_file_handler.setFormatter(formatter)
            self.logger.addHandler(path_file_handler)
        else:
            for handler in file_handlers:
                handler.maxBytes = self.max_bytes
                handler.backupCount = self.backup_count
                handler.setLevel(self.level)
                handler.setFormatter(formatter)

    self.logger.propagate = False

Configure JSON console/file handlers idempotently.