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
- Log
- LogKey
- KeyMixin
- RecordMixin
- abc.ABC
- typing.Generic
Static methods
def default() -> Self
-
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
-
Log level using logging module conventions (lower, upper or mixed case can be used).
var log_id -> str
-
Unique log identifier.
Methods
def get_key(self) -> LogKey
-
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
-
Invoke ‘init’ for each class in the order from base to derived, then validate against schema.