Module: file_log

Expand source code
# Copyright (C) 2023-present The Project Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
from dataclasses import dataclass
from typing import Iterable
from concurrent_log_handler import ConcurrentRotatingFileHandler
from cl.runtime.log.log import Log
from cl.runtime.primitive.datetime_util import DatetimeUtil
from cl.runtime.settings.log_settings import LogSettings
from cl.runtime.settings.project_settings import ProjectSettings


def _get_log_filename() -> str:
    """Generate log filename during import and use it throughout the session."""

    # TODO: Refactor to use a unique directory name instead
    # Generate log file name
    log_settings = LogSettings.instance()
    log_filename_format = log_settings.filename_format
    match log_filename_format:
        case "prefix":
            # Filename is the prefix with .log extension
            result = f"{log_settings.filename_prefix}.log"
        case "prefix-timestamp":
            # UTC timestamp to millisecond precision for the log file name
            log_timestamp = DatetimeUtil.now()
            # Serialize assuming millisecond precision
            log_timestamp_str = (
                log_timestamp.strftime("%Y-%m-%d-%H-%M-%S") + f"-{int(round(log_timestamp.microsecond / 1000)):03d}"
            )
            result = f"{log_settings.filename_prefix}-{log_timestamp_str}.log"
        case _:
            valid_choices = ["prefix", "prefix-timestamp"]
            raise RuntimeError(
                f"Unknown log filename format: {log_filename_format}, " f"valid choices are {', '.join(valid_choices)}"
            )

    # Create log directory and filename relative to project root
    project_root = ProjectSettings.get_project_root()
    log_dir = os.path.join(project_root, "logs")
    result = os.path.join(log_dir, result)

    # Create log directory if does not exist
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    return result


log_filename = _get_log_filename()
"""Generate log filename during import and use it throughout the session."""


@dataclass(slots=True, kw_only=True)
class FileLog(Log):
    """File log with concurrency multiprocess write capability."""

    filename: str = log_filename
    """Log filename with extension is generated on import."""

    def get_log_handlers(self) -> Iterable[logging.Handler]:
        """Return an iterable of log handlers to be added to the logger."""

        # Max log file in bytes, after this size is reached older records will be erased
        max_log_file_size_bytes = 1024 * 1024 * 10  # 10MB

        # Set up file handler
        file_log_handler = ConcurrentRotatingFileHandler(
            self.filename,
            maxBytes=max_log_file_size_bytes,
            backupCount=0,  # Do not create backup files because each file has a timestamp
        )

        # Configure the logging format
        file_log_formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(custom_field)s"
        )
        file_log_handler.setFormatter(file_log_formatter)

        console_handler = logging.StreamHandler()
        console_handler.setFormatter(file_log_formatter)

        # TODO: Add another handler that saves records
        return [file_log_handler, console_handler]

Global variables

var log_filename

Generate log filename during import and use it throughout the session.

Classes

class FileLog (*, log_id: str = None, level: str = 'INFO', filename: str = 'C:\Kyrill\Projects\Hackaton 2024\tradeentry\tradeentry-build\logs\main-2024-11-06-22-46-56-425.log')

File log with concurrency multiprocess write capability.

Expand source code
@dataclass(slots=True, kw_only=True)
class FileLog(Log):
    """File log with concurrency multiprocess write capability."""

    filename: str = log_filename
    """Log filename with extension is generated on import."""

    def get_log_handlers(self) -> Iterable[logging.Handler]:
        """Return an iterable of log handlers to be added to the logger."""

        # Max log file in bytes, after this size is reached older records will be erased
        max_log_file_size_bytes = 1024 * 1024 * 10  # 10MB

        # Set up file handler
        file_log_handler = ConcurrentRotatingFileHandler(
            self.filename,
            maxBytes=max_log_file_size_bytes,
            backupCount=0,  # Do not create backup files because each file has a timestamp
        )

        # Configure the logging format
        file_log_formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(levelname)s - %(message)s - %(custom_field)s"
        )
        file_log_handler.setFormatter(file_log_formatter)

        console_handler = logging.StreamHandler()
        console_handler.setFormatter(file_log_formatter)

        # TODO: Add another handler that saves records
        return [file_log_handler, console_handler]

Ancestors

Static methods

def default() -> Self

Inherited from: Log.default

Default log is initialized from settings and cannot be modified in code.

def get_key_type() -> Type

Inherited from: Log.get_key_type

Return key type even when called from a record.

Fields

var filename -> str

Log filename with extension is generated on import.

var level -> str

Inherited from: Log.level

Log level using logging module conventions (lower, upper or mixed case can be used).

var log_id -> str

Inherited from: Log.log_id

Unique log identifier.

Methods

def get_key(self) -> LogKey

Inherited from: Log.get_key

Return a new key object whose fields populated from self, do not return self.

def get_log_handlers(self) -> Iterable[logging.Handler]

Inherited from: Log.get_log_handlers

Return an iterable of log handlers to be added to the logger.

def get_logger(self, name: str) -> logging.Logger

Inherited from: Log.get_logger

Get logger for the specified name, invoke with name as the argument.

def init_all(self) -> None

Inherited from: Log.init_all

Invoke ‘init’ for each class in the order from base to derived, then validate against schema.