prismalog 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prismalog/argparser.py +17 -4
- prismalog/config.py +62 -12
- prismalog/log.py +93 -72
- {prismalog-0.1.0.dist-info → prismalog-0.1.1.dist-info}/METADATA +1 -1
- prismalog-0.1.1.dist-info/RECORD +10 -0
- {prismalog-0.1.0.dist-info → prismalog-0.1.1.dist-info}/WHEEL +1 -1
- prismalog-0.1.0.dist-info/RECORD +0 -10
- {prismalog-0.1.0.dist-info → prismalog-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {prismalog-0.1.0.dist-info → prismalog-0.1.1.dist-info}/top_level.txt +0 -0
prismalog/argparser.py
CHANGED
@@ -20,6 +20,8 @@ Available Arguments:
|
|
20
20
|
--log-level Set the default logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
21
21
|
--log-dir Directory where log files will be stored
|
22
22
|
--log-format Format string for log messages
|
23
|
+
--log-datefmt Format string for log timestamps
|
24
|
+
--log-filename Base filename prefix for log files
|
23
25
|
--no-color Disable colored console output
|
24
26
|
--disable-rotation Disable log file rotation
|
25
27
|
--exit-on-critical Exit program on critical errors
|
@@ -36,7 +38,7 @@ Usage Examples:
|
|
36
38
|
# Create parser with standard logging arguments
|
37
39
|
parser = get_argument_parser(description="My Application")
|
38
40
|
|
39
|
-
# Add
|
41
|
+
# Add own application-specific arguments
|
40
42
|
parser.add_argument("--my-option", help="Application-specific option")
|
41
43
|
|
42
44
|
# Parse arguments
|
@@ -73,7 +75,7 @@ class LoggingArgumentParser:
|
|
73
75
|
@staticmethod
|
74
76
|
def add_arguments(parser: Optional[argparse.ArgumentParser] = None) -> argparse.ArgumentParser:
|
75
77
|
"""
|
76
|
-
Add standard prismalog arguments to an existing parser.
|
78
|
+
Add standard prismalog arguments to an existing parser using LoggingConfig.
|
77
79
|
|
78
80
|
Args:
|
79
81
|
parser: An existing ArgumentParser instance. If None, a new one is created.
|
@@ -99,6 +101,13 @@ class LoggingArgumentParser:
|
|
99
101
|
|
100
102
|
parser.add_argument("--log-format", help="Format string for log messages")
|
101
103
|
|
104
|
+
parser.add_argument(
|
105
|
+
"--log-filename",
|
106
|
+
"--logging-filename",
|
107
|
+
type=str,
|
108
|
+
help="Base filename for generated log files",
|
109
|
+
)
|
110
|
+
|
102
111
|
# Boolean flags
|
103
112
|
parser.add_argument(
|
104
113
|
"--no-color",
|
@@ -143,13 +152,13 @@ class LoggingArgumentParser:
|
|
143
152
|
@staticmethod
|
144
153
|
def extract_logging_args(args: argparse.Namespace) -> Dict[str, Any]:
|
145
154
|
"""
|
146
|
-
Extract logging-related arguments from parsed args.
|
155
|
+
Extract logging-related arguments from parsed args based on LoggingConfig defaults.
|
147
156
|
|
148
157
|
Args:
|
149
158
|
args: The parsed args from ArgumentParser.parse_args()
|
150
159
|
|
151
160
|
Returns:
|
152
|
-
Dictionary with only the logging-related arguments
|
161
|
+
Dictionary with only the logging-related arguments mapped to LoggingConfig keys.
|
153
162
|
"""
|
154
163
|
# Define mappings from CLI arg names to config keys
|
155
164
|
key_mappings = {
|
@@ -162,6 +171,10 @@ class LoggingArgumentParser:
|
|
162
171
|
"exit_on_critical": "exit_on_critical",
|
163
172
|
"rotation_size_mb": "rotation_size_mb",
|
164
173
|
"backup_count": "backup_count",
|
174
|
+
"log_filename": "log_filename",
|
175
|
+
"logging_filename": "log_filename",
|
176
|
+
"log_datefmt": "datefmt",
|
177
|
+
"test_mode": "test_mode",
|
165
178
|
}
|
166
179
|
|
167
180
|
# Convert args to dictionary
|
prismalog/config.py
CHANGED
@@ -14,7 +14,7 @@ Key features:
|
|
14
14
|
- Singleton pattern to ensure configuration consistency
|
15
15
|
|
16
16
|
The configuration system follows this priority order (highest to lowest):
|
17
|
-
1. Programmatic settings via direct API calls
|
17
|
+
1. Programmatic settings via direct API calls or kwargs to initialize()
|
18
18
|
2. Command-line arguments
|
19
19
|
3. Configuration files (YAML)
|
20
20
|
4. Environment variables (with support for CI/CD environments)
|
@@ -24,18 +24,35 @@ Environment Variables:
|
|
24
24
|
Standard environment variables use the ``LOG_`` prefix:
|
25
25
|
|
26
26
|
- ``LOG_DIR``: Directory for log files
|
27
|
-
- ``LOG_LEVEL``: Default logging level
|
27
|
+
- ``LOG_LEVEL``: Default logging level (e.g., INFO, DEBUG)
|
28
28
|
- ``LOG_ROTATION_SIZE``: Size in MB for log rotation
|
29
29
|
- ``LOG_BACKUP_COUNT``: Number of backup log files
|
30
|
-
- ``LOG_FORMAT``: Log message format
|
31
|
-
- ``
|
32
|
-
- ``
|
33
|
-
- ``
|
34
|
-
- ``
|
30
|
+
- ``LOG_FORMAT``: Log message format string
|
31
|
+
- ``LOG_DATEFMT``: Log message date format string
|
32
|
+
- ``LOG_FILENAME``: Base filename prefix for log files (default: 'app')
|
33
|
+
- ``LOG_COLORED_CONSOLE``: Whether to use colored console output (true/false)
|
34
|
+
- ``LOG_DISABLE_ROTATION``: Whether to disable log rotation (true/false)
|
35
|
+
- ``LOG_EXIT_ON_CRITICAL``: Whether to exit on critical logs (true/false)
|
36
|
+
- ``LOG_TEST_MODE``: Whether logger is in test mode (true/false)
|
35
37
|
|
36
38
|
For GitHub Actions, the same variables are supported with ``GITHUB_`` prefix:
|
37
39
|
|
38
|
-
- ``GITHUB_LOG_DIR``, ``GITHUB_LOG_LEVEL``, etc.
|
40
|
+
- ``GITHUB_LOG_DIR``, ``GITHUB_LOG_LEVEL``, ``GITHUB_LOG_FILENAME``, ``GITHUB_LOG_DATEFMT``, etc.
|
41
|
+
|
42
|
+
Command-Line Arguments:
|
43
|
+
The following arguments can be parsed if `use_cli_args=True` during initialization:
|
44
|
+
|
45
|
+
- ``--log-config`` / ``--log-conf``: Path to logging configuration file (YAML)
|
46
|
+
- ``--log-level`` / ``--logging-level``: Default logging level (DEBUG, INFO, etc.)
|
47
|
+
- ``--log-dir`` / ``--logging-dir``: Directory for log files
|
48
|
+
- ``--log-format`` / ``--logging-format``: Format string for log messages
|
49
|
+
- ``--log-datefmt`` / ``--logging-datefmt``: Format string for log timestamps
|
50
|
+
- ``--log-filename`` / ``--logging-filename``: Prefix for log filenames
|
51
|
+
- ``--no-color`` / ``--no-colors``: Disable colored console output
|
52
|
+
- ``--disable-rotation``: Disable log file rotation
|
53
|
+
- ``--exit-on-critical``: Exit the program on critical errors
|
54
|
+
- ``--rotation-size``: Log file rotation size in MB
|
55
|
+
- ``--backup-count``: Number of backup log files to keep
|
39
56
|
|
40
57
|
Type Conversion:
|
41
58
|
String values from configuration files and environment variables are automatically
|
@@ -48,11 +65,12 @@ Usage examples:
|
|
48
65
|
# Basic initialization with defaults
|
49
66
|
LoggingConfig.initialize()
|
50
67
|
|
51
|
-
# Initialization with configuration file
|
52
|
-
LoggingConfig.initialize(config_file="logging_config.yaml")
|
68
|
+
# Initialization with configuration file and CLI args enabled
|
69
|
+
LoggingConfig.initialize(config_file="logging_config.yaml", use_cli_args=True)
|
53
70
|
|
54
71
|
# Accessing configuration values
|
55
72
|
log_dir = LoggingConfig.get("log_dir")
|
73
|
+
filename_prefix = LoggingConfig.get_filename_prefix()
|
56
74
|
|
57
75
|
# Setting configuration values programmatically
|
58
76
|
LoggingConfig.set("colored_console", False)
|
@@ -63,7 +81,7 @@ Usage examples:
|
|
63
81
|
|
64
82
|
import argparse
|
65
83
|
import os
|
66
|
-
from typing import Any, Dict, Optional, Type
|
84
|
+
from typing import Any, Dict, Optional, Type, cast
|
67
85
|
|
68
86
|
|
69
87
|
class LoggingConfig:
|
@@ -105,6 +123,8 @@ class LoggingConfig:
|
|
105
123
|
"rotation_size_mb": 10,
|
106
124
|
"backup_count": 5,
|
107
125
|
"log_format": "%(asctime)s - %(filename)s - %(name)s - [%(levelname)s] - %(message)s",
|
126
|
+
"datefmt": "%Y-%m-%d %H:%M:%S.%f",
|
127
|
+
"log_filename": "app", # Default log filename prefix
|
108
128
|
"colored_console": True,
|
109
129
|
"disable_rotation": False,
|
110
130
|
"exit_on_critical": False, # Whether to exit the program on critical logs
|
@@ -112,7 +132,7 @@ class LoggingConfig:
|
|
112
132
|
}
|
113
133
|
|
114
134
|
_instance = None
|
115
|
-
_config: Dict[str, Any] = {} # Add type annotation
|
135
|
+
_config: Dict[str, Any] = {} # Add type annotation
|
116
136
|
_initialized = False
|
117
137
|
_debug_mode = False
|
118
138
|
|
@@ -406,6 +426,8 @@ class LoggingConfig:
|
|
406
426
|
"rotation_size_mb": ["LOG_ROTATION_SIZE", "GITHUB_LOG_ROTATION_SIZE"],
|
407
427
|
"backup_count": ["LOG_BACKUP_COUNT", "GITHUB_LOG_BACKUP_COUNT"],
|
408
428
|
"log_format": ["LOG_FORMAT", "GITHUB_LOG_FORMAT"],
|
429
|
+
"datefmt": ["LOG_DATEFMT", "GITHUB_LOG_DATEFMT"],
|
430
|
+
"log_filename": ["LOG_FILENAME", "GITHUB_LOG_FILENAME"],
|
409
431
|
"colored_console": ["LOG_COLORED_CONSOLE", "GITHUB_LOG_COLORED_CONSOLE"],
|
410
432
|
"disable_rotation": ["LOG_DISABLE_ROTATION", "GITHUB_LOG_DISABLE_ROTATION"],
|
411
433
|
"exit_on_critical": ["LOG_EXIT_ON_CRITICAL", "GITHUB_LOG_EXIT_ON_CRITICAL"],
|
@@ -497,6 +519,9 @@ class LoggingConfig:
|
|
497
519
|
- --log-config, --log-conf: Path to logging configuration file
|
498
520
|
- --log-level, --logging-level: Default logging level
|
499
521
|
- --log-dir, --logging-dir: Directory for log files
|
522
|
+
- --log-format, --logging-format: Format string for log messages
|
523
|
+
- --log-datefmt, --logging-datefmt: Format string for log timestamps
|
524
|
+
- --log-filename, --logging-filename: Prefix for log filenames
|
500
525
|
|
501
526
|
Args:
|
502
527
|
parser: An existing ArgumentParser to add arguments to
|
@@ -531,6 +556,20 @@ class LoggingConfig:
|
|
531
556
|
help="Format string for log messages (e.g. '%(asctime)s - %(message)s')",
|
532
557
|
)
|
533
558
|
|
559
|
+
parser.add_argument(
|
560
|
+
"--log-datefmt",
|
561
|
+
"--logging-datefmt",
|
562
|
+
dest="datefmt",
|
563
|
+
help="Format string for log timestamps (e.g. '%%Y-%%m-%%d %%H:%%M:%%S.%%f')",
|
564
|
+
)
|
565
|
+
|
566
|
+
parser.add_argument(
|
567
|
+
"--log-filename",
|
568
|
+
"--logging-filename",
|
569
|
+
dest="log_filename",
|
570
|
+
help="Prefix for log filenames",
|
571
|
+
)
|
572
|
+
|
534
573
|
return parser
|
535
574
|
|
536
575
|
@classmethod
|
@@ -781,3 +820,14 @@ class LoggingConfig:
|
|
781
820
|
cls.debug_print("LoggingConfig reset to default values")
|
782
821
|
|
783
822
|
return cls
|
823
|
+
|
824
|
+
@classmethod
|
825
|
+
def get_filename_prefix(cls) -> str:
|
826
|
+
"""
|
827
|
+
Get the configured log filename prefix.
|
828
|
+
|
829
|
+
Returns:
|
830
|
+
The configured prefix string, defaulting to 'app'.
|
831
|
+
"""
|
832
|
+
value = cls.get("log_filename", "app")
|
833
|
+
return cast(str, value)
|
prismalog/log.py
CHANGED
@@ -7,27 +7,46 @@ Python's standard logging with colored output, automatic log rotation, and
|
|
7
7
|
improved handling of critical errors.
|
8
8
|
|
9
9
|
Key components:
|
10
|
-
- ColoredFormatter: Adds color-coding to console output based on log levels
|
11
|
-
- MultiProcessingLog: Thread-safe and process-safe log handler
|
12
|
-
|
13
|
-
-
|
14
|
-
|
10
|
+
- ColoredFormatter: Adds color-coding to console output based on log levels.
|
11
|
+
- MultiProcessingLog: Thread-safe and process-safe log handler using a shared
|
12
|
+
lock and RotatingFileHandler for file output and rotation.
|
13
|
+
- CriticalExitHandler: Optional handler that exits the program on critical errors
|
14
|
+
if configured via LoggingConfig.
|
15
|
+
- ColoredLogger: Main logger class wrapping the standard logger, providing
|
16
|
+
easy access to configured handlers and levels.
|
17
|
+
- get_logger: Factory function to obtain properly configured logger instances,
|
18
|
+
handling initialization and configuration application.
|
15
19
|
|
16
20
|
Features:
|
17
|
-
-
|
18
|
-
|
19
|
-
-
|
20
|
-
|
21
|
-
-
|
22
|
-
|
23
|
-
-
|
21
|
+
- Performance: Optimized for speed, especially when using %(created)f timestamp format.
|
22
|
+
(Note: Performance figures depend heavily on configuration and environment).
|
23
|
+
- Colored Console Output: Improves readability using ANSI color codes. Configurable
|
24
|
+
via LoggingConfig ('colored_console').
|
25
|
+
- Automatic Log File Rotation: Based on size ('rotation_size_mb') and backup count
|
26
|
+
('backup_count'). Can be disabled ('disable_rotation').
|
27
|
+
- Process-Safe & Thread-Safe File Logging: Uses `MultiProcessingLog` with a
|
28
|
+
`multiprocessing.Lock` to prevent corruption.
|
29
|
+
- Critical Error Handling: Option to exit the application on critical logs via
|
30
|
+
`CriticalExitHandler` (controlled by 'exit_on_critical').
|
31
|
+
- Configurable Verbosity: Set default levels ('default_level') and per-module levels
|
32
|
+
('external_loggers') via LoggingConfig.
|
33
|
+
- Flexible Configuration: Leverages LoggingConfig for settings from defaults, files,
|
34
|
+
environment variables, and CLI arguments.
|
24
35
|
|
25
36
|
Example:
|
26
|
-
>>> from prismalog import get_logger
|
27
|
-
>>>
|
28
|
-
>>>
|
29
|
-
>>>
|
30
|
-
>>> logger
|
37
|
+
>>> from prismalog import get_logger, LoggingConfig
|
38
|
+
>>> # Initialize configuration (optional, happens automatically on first get_logger)
|
39
|
+
>>> # LoggingConfig.initialize(config_file="logging.yaml", use_cli_args=True)
|
40
|
+
>>>
|
41
|
+
>>> logger = get_logger(__name__) # Use module name
|
42
|
+
>>> logger.info("Application started successfully.")
|
43
|
+
>>> logger.debug("Detailed debugging information for developers.")
|
44
|
+
>>> try:
|
45
|
+
... 1 / 0
|
46
|
+
... except ZeroDivisionError:
|
47
|
+
... logger.error("An error occurred during calculation.", exc_info=True) # Log exception info
|
48
|
+
>>> logger.critical("A critical failure occurred, application might exit if configured.")
|
49
|
+
|
31
50
|
"""
|
32
51
|
|
33
52
|
import logging
|
@@ -111,6 +130,29 @@ class ColoredFormatter(logging.Formatter):
|
|
111
130
|
|
112
131
|
return result
|
113
132
|
|
133
|
+
def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str:
|
134
|
+
"""
|
135
|
+
Format the creation time of a LogRecord.
|
136
|
+
|
137
|
+
Overrides the default formatTime to provide support for microseconds
|
138
|
+
using the '%f' directive in the date format string.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
record: The log record whose creation time is to be formatted.
|
142
|
+
datefmt: The format string for the date/time. If None, a default
|
143
|
+
format ("%Y-%m-%d %H:%M:%S") is used.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
The formatted date/time string.
|
147
|
+
"""
|
148
|
+
dt = datetime.fromtimestamp(record.created)
|
149
|
+
if datefmt:
|
150
|
+
# Support %f for microseconds
|
151
|
+
str_datefmt = dt.strftime(datefmt)
|
152
|
+
else:
|
153
|
+
str_datefmt = dt.strftime("%Y-%m-%d %H:%M:%S")
|
154
|
+
return str_datefmt
|
155
|
+
|
114
156
|
|
115
157
|
class MultiProcessingLog(logging.Handler):
|
116
158
|
"""
|
@@ -143,6 +185,9 @@ class MultiProcessingLog(logging.Handler):
|
|
143
185
|
self.backupCount = backupCount # pylint: disable=invalid-name
|
144
186
|
self._handler: Optional[RotatingFileHandler] = None # Add type annotation
|
145
187
|
|
188
|
+
# Determine and store prefix during initialization
|
189
|
+
self.filename_prefix: str = LoggingConfig.get_filename_prefix()
|
190
|
+
|
146
191
|
# Update the class-level active log file
|
147
192
|
with self.__class__.file_lock:
|
148
193
|
self.__class__.active_log_file = filename
|
@@ -249,7 +294,8 @@ class MultiProcessingLog(logging.Handler):
|
|
249
294
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
250
295
|
unique_suffix = str(os.getpid() % 10000) # Use last 4 digits of PID for uniqueness
|
251
296
|
log_dir = os.path.dirname(self.filename)
|
252
|
-
|
297
|
+
|
298
|
+
new_filename = os.path.join(log_dir, f"{self.filename_prefix}_{timestamp}_{unique_suffix}.log")
|
253
299
|
|
254
300
|
# Update the filename used by this instance
|
255
301
|
self.filename = new_filename
|
@@ -427,12 +473,19 @@ class ColoredLogger:
|
|
427
473
|
def _add_handlers_to_logger(self, logger: logging.Logger) -> None:
|
428
474
|
"""Add necessary handlers to the logger."""
|
429
475
|
|
430
|
-
# Get format string from config
|
476
|
+
# Get format string and date format from config
|
431
477
|
log_format = LoggingConfig.get("log_format", "%(asctime)s - %(name)s - [%(levelname)s] - %(message)s")
|
478
|
+
datefmt = LoggingConfig.get("datefmt", "%Y-%m-%d %H:%M:%S.%f")
|
432
479
|
|
433
480
|
# Console Handler
|
434
481
|
ch = logging.StreamHandler(sys.stdout)
|
435
|
-
ch.setFormatter(
|
482
|
+
ch.setFormatter(
|
483
|
+
ColoredFormatter(
|
484
|
+
fmt=log_format,
|
485
|
+
datefmt=datefmt,
|
486
|
+
colored=LoggingConfig.get("colored_console", True),
|
487
|
+
)
|
488
|
+
)
|
436
489
|
ch.setLevel(self._configured_level)
|
437
490
|
logger.addHandler(ch)
|
438
491
|
|
@@ -441,10 +494,6 @@ class ColoredLogger:
|
|
441
494
|
self.__class__._file_handler = self.__class__.setup_file_handler()
|
442
495
|
|
443
496
|
if self.__class__._file_handler:
|
444
|
-
# Set the same format for file handler
|
445
|
-
self.__class__._file_handler.setFormatter(
|
446
|
-
ColoredFormatter(fmt=log_format, colored=LoggingConfig.get("colored_file", False))
|
447
|
-
)
|
448
497
|
logger.addHandler(self.__class__._file_handler)
|
449
498
|
|
450
499
|
@classmethod
|
@@ -462,23 +511,21 @@ class ColoredLogger:
|
|
462
511
|
if cls._file_handler and not log_file_path:
|
463
512
|
return cls._file_handler
|
464
513
|
|
465
|
-
# --- Determine Log File Path ---
|
466
514
|
if log_file_path is None:
|
467
|
-
# Get log directory from config, default to "logs"
|
468
515
|
log_dir = LoggingConfig.get("log_dir", "logs")
|
469
516
|
os.makedirs(log_dir, exist_ok=True)
|
470
517
|
|
471
|
-
# Generate filename (keeping existing logic)
|
472
518
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
473
519
|
unique_suffix = str(os.getpid() % 1000)
|
474
|
-
log_file_path = os.path.join(log_dir, f"app_{timestamp}_{unique_suffix}.log")
|
475
520
|
|
476
|
-
|
521
|
+
filename_prefix = LoggingConfig.get_filename_prefix()
|
522
|
+
LoggingConfig.debug_print(f"Determined filename prefix: '{filename_prefix}'")
|
523
|
+
|
524
|
+
log_file_path = os.path.join(log_dir, f"{filename_prefix}_{timestamp}_{unique_suffix}.log")
|
525
|
+
|
526
|
+
cls._log_file_path = log_file_path
|
477
527
|
|
478
|
-
# --- Rotation Settings from Config ---
|
479
528
|
disable_rotation = LoggingConfig.get("disable_rotation", False)
|
480
|
-
# Also check env var for compatibility if needed, though config should be primary
|
481
|
-
# disable_rotation = disable_rotation or os.environ.get("LOG_DISABLE_ROTATION") == "1"
|
482
529
|
|
483
530
|
handler: MultiProcessingLog # Type hint
|
484
531
|
|
@@ -488,12 +535,10 @@ class ColoredLogger:
|
|
488
535
|
else:
|
489
536
|
# Get rotation size from config, default 10MB
|
490
537
|
rotation_size_mb = LoggingConfig.get("rotation_size_mb", 10)
|
491
|
-
# Ensure minimum size (e.g., 1KB)
|
492
538
|
rotation_size_bytes = max(1024, int(rotation_size_mb * 1024 * 1024))
|
493
539
|
|
494
540
|
# Get backup count from config, default 5
|
495
541
|
backup_count = LoggingConfig.get("backup_count", 5)
|
496
|
-
# Ensure minimum count (e.g., 1)
|
497
542
|
backup_count = max(1, backup_count)
|
498
543
|
|
499
544
|
LoggingConfig.debug_print(
|
@@ -503,18 +548,17 @@ class ColoredLogger:
|
|
503
548
|
)
|
504
549
|
handler = MultiProcessingLog(log_file_path, "a", rotation_size_bytes, backup_count)
|
505
550
|
|
506
|
-
|
507
|
-
|
551
|
+
default_format = (
|
552
|
+
"%(asctime)s - %(filename)s - %(process)d - %(thread)d - %(name)s - [%(levelname)s] - %(message)s"
|
553
|
+
)
|
508
554
|
log_format = LoggingConfig.get("log_format", default_format)
|
555
|
+
datefmt = LoggingConfig.get("datefmt", "%Y-%m-%d %H:%M:%S.%f")
|
509
556
|
|
510
|
-
# Get color setting for file handler from config, default to False
|
511
557
|
use_file_color = LoggingConfig.get("colored_file", False)
|
512
558
|
|
513
|
-
|
514
|
-
handler.setFormatter(
|
559
|
+
formatter = ColoredFormatter(fmt=log_format, datefmt=datefmt, colored=use_file_color)
|
560
|
+
handler.setFormatter(formatter)
|
515
561
|
|
516
|
-
# --- Level ---
|
517
|
-
# File handler always logs at DEBUG level as per original design
|
518
562
|
handler.setLevel(logging.DEBUG)
|
519
563
|
|
520
564
|
return handler
|
@@ -558,27 +602,27 @@ class ColoredLogger:
|
|
558
602
|
pass
|
559
603
|
cls._file_handler = None
|
560
604
|
|
561
|
-
# For test_logger_reset test, ensure a different path is generated
|
562
605
|
if new_file:
|
563
|
-
# ensure CLI args have been processed
|
564
606
|
log_dir = LoggingConfig.get("log_dir", "logs")
|
565
607
|
|
566
|
-
# Make absolute path if needed
|
567
608
|
if not os.path.isabs(log_dir):
|
568
609
|
log_dir = os.path.abspath(log_dir)
|
569
610
|
|
570
611
|
os.makedirs(log_dir, exist_ok=True)
|
571
612
|
|
572
|
-
# Use time.time() to ensure uniqueness, even for fast successive calls
|
573
613
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
574
|
-
unique_suffix = str(int(time.time() * 1000) % 10000)
|
575
|
-
|
614
|
+
unique_suffix = str(int(time.time() * 1000) % 10000)
|
615
|
+
|
616
|
+
filename = LoggingConfig.get_filename_prefix()
|
617
|
+
LoggingConfig.debug_print(
|
618
|
+
f"Using log_filename after reset: '{filename}' from LoggingConfig.get_filename_prefix()"
|
619
|
+
)
|
620
|
+
|
621
|
+
cls._log_file_path = os.path.join(log_dir, f"{filename}_{timestamp}_{unique_suffix}.log")
|
576
622
|
|
577
|
-
# Create a new file handler
|
578
623
|
if cls._file_handler is None:
|
579
624
|
cls._file_handler = cls.setup_file_handler(cls._log_file_path)
|
580
625
|
|
581
|
-
# Reinitialize loggers that were previously registered
|
582
626
|
for name in logger_names:
|
583
627
|
get_logger(name)
|
584
628
|
|
@@ -597,13 +641,10 @@ class ColoredLogger:
|
|
597
641
|
logger_instance = cls._initialized_loggers[name]
|
598
642
|
|
599
643
|
if isinstance(level, int):
|
600
|
-
# If it's already an integer level, use it directly
|
601
644
|
level_value = level
|
602
645
|
else:
|
603
|
-
# If it's a string, use map_level to convert it
|
604
646
|
level_value = LoggingConfig.map_level(level)
|
605
647
|
|
606
|
-
# Update the level in both the wrapper and the underlying logger
|
607
648
|
logger_instance.level = level_value
|
608
649
|
logger_instance.logger.setLevel(level_value)
|
609
650
|
|
@@ -625,8 +666,6 @@ class ColoredLogger:
|
|
625
666
|
Returns:
|
626
667
|
The configured log level as an integer
|
627
668
|
"""
|
628
|
-
# This abstracts the implementation details from the tests
|
629
|
-
# For tests, report the configured level, not the actual logger level
|
630
669
|
return self._configured_level
|
631
670
|
|
632
671
|
@level.setter
|
@@ -639,7 +678,6 @@ class ColoredLogger:
|
|
639
678
|
"""
|
640
679
|
self._configured_level = value
|
641
680
|
|
642
|
-
# Update console handlers only
|
643
681
|
if hasattr(self, "logger") and self.logger:
|
644
682
|
for handler in self.logger.handlers:
|
645
683
|
is_stream_handler = isinstance(handler, logging.StreamHandler)
|
@@ -648,7 +686,6 @@ class ColoredLogger:
|
|
648
686
|
if is_stream_handler and is_not_multiprocessing_log:
|
649
687
|
handler.setLevel(value)
|
650
688
|
|
651
|
-
# Logger methods - delegate to the underlying logger
|
652
689
|
def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
|
653
690
|
"""Logs a debug message."""
|
654
691
|
self.logger.debug(msg, *args, **kwargs)
|
@@ -697,7 +734,6 @@ class ColoredLogger:
|
|
697
734
|
self.logger.exception(msg, *args, **kwargs)
|
698
735
|
|
699
736
|
|
700
|
-
# At module level
|
701
737
|
_EXTERNAL_LOGGERS_CONFIGURED = False
|
702
738
|
|
703
739
|
|
@@ -706,16 +742,12 @@ def configure_external_loggers(external_loggers: Dict[str, str]) -> None:
|
|
706
742
|
external_loggers = LoggingConfig.get("external_loggers", {})
|
707
743
|
|
708
744
|
for logger_name, level in external_loggers.items():
|
709
|
-
# Get the logger for this package
|
710
745
|
logger = logging.getLogger(logger_name)
|
711
746
|
|
712
|
-
# Convert level string to logging constant
|
713
747
|
level_value = LoggingConfig.map_level(level)
|
714
748
|
|
715
|
-
# Set the level
|
716
749
|
logger.setLevel(level_value)
|
717
750
|
|
718
|
-
# Disable propagation to avoid duplicate messages
|
719
751
|
logger.propagate = False
|
720
752
|
|
721
753
|
LoggingConfig.debug_print(f"Set external logger '{logger_name}' to level {level}")
|
@@ -744,12 +776,10 @@ def create_logger(
|
|
744
776
|
logger = logging.getLogger(name)
|
745
777
|
logger.setLevel(level or logging.INFO)
|
746
778
|
|
747
|
-
# Console handler
|
748
779
|
console_handler = StreamHandler(sys.stdout)
|
749
780
|
console_handler.setFormatter(ColoredFormatter(fmt=format_string or "%(message)s"))
|
750
781
|
logger.addHandler(console_handler)
|
751
782
|
|
752
|
-
# File handler
|
753
783
|
if log_dir:
|
754
784
|
os.makedirs(log_dir, exist_ok=True)
|
755
785
|
file_path = os.path.join(log_dir, f"{name}.log")
|
@@ -777,12 +807,10 @@ def init_root_logger(
|
|
777
807
|
root_logger = logging.getLogger()
|
778
808
|
root_logger.setLevel(level or logging.INFO)
|
779
809
|
|
780
|
-
# Console handler
|
781
810
|
console_handler = StreamHandler(sys.stdout)
|
782
811
|
console_handler.setFormatter(ColoredFormatter(fmt=format_string or "%(message)s", colored=colored_console))
|
783
812
|
root_logger.addHandler(console_handler)
|
784
813
|
|
785
|
-
# File handler
|
786
814
|
if log_dir:
|
787
815
|
os.makedirs(log_dir, exist_ok=True)
|
788
816
|
file_path = os.path.join(log_dir, "root.log")
|
@@ -862,16 +890,13 @@ def get_logger(name: str, verbose: Optional[str] = None) -> Union[ColoredLogger,
|
|
862
890
|
"""
|
863
891
|
global _EXTERNAL_LOGGERS_CONFIGURED
|
864
892
|
|
865
|
-
# Configure external loggers only once
|
866
893
|
if not _EXTERNAL_LOGGERS_CONFIGURED:
|
867
894
|
configure_external_loggers(LoggingConfig.get("external_loggers", {}))
|
868
895
|
_EXTERNAL_LOGGERS_CONFIGURED = True
|
869
896
|
|
870
|
-
# Check if logger already exists
|
871
897
|
if name in ColoredLogger._initialized_loggers:
|
872
898
|
existing_logger = ColoredLogger._initialized_loggers[name]
|
873
899
|
|
874
|
-
# If explicit verbose parameter is provided, always apply it
|
875
900
|
if verbose is not None:
|
876
901
|
original_level = existing_logger.level
|
877
902
|
ColoredLogger.update_logger_level(name, verbose)
|
@@ -883,7 +908,6 @@ def get_logger(name: str, verbose: Optional[str] = None) -> Union[ColoredLogger,
|
|
883
908
|
)
|
884
909
|
return existing_logger
|
885
910
|
|
886
|
-
# Check if there's a specific config for this logger in external_loggers
|
887
911
|
external_loggers = LoggingConfig.get("external_loggers", {})
|
888
912
|
if name in external_loggers:
|
889
913
|
original_level = existing_logger.level
|
@@ -896,7 +920,6 @@ def get_logger(name: str, verbose: Optional[str] = None) -> Union[ColoredLogger,
|
|
896
920
|
)
|
897
921
|
return existing_logger
|
898
922
|
|
899
|
-
# Check if there's a module-specific level that should be applied
|
900
923
|
module_levels = LoggingConfig.get("module_levels", {})
|
901
924
|
if name in module_levels:
|
902
925
|
original_level = existing_logger.level
|
@@ -910,7 +933,6 @@ def get_logger(name: str, verbose: Optional[str] = None) -> Union[ColoredLogger,
|
|
910
933
|
|
911
934
|
return existing_logger
|
912
935
|
|
913
|
-
# Use explicit level, then check external_loggers config, then check module_levels, then use default
|
914
936
|
if verbose is None:
|
915
937
|
external_loggers = LoggingConfig.get("external_loggers", {})
|
916
938
|
module_levels = LoggingConfig.get("module_levels", {})
|
@@ -922,6 +944,5 @@ def get_logger(name: str, verbose: Optional[str] = None) -> Union[ColoredLogger,
|
|
922
944
|
else:
|
923
945
|
verbose = LoggingConfig.get("default_level", "INFO")
|
924
946
|
|
925
|
-
# Create new logger
|
926
947
|
logger = ColoredLogger(name, verbose)
|
927
948
|
return logger
|
@@ -0,0 +1,10 @@
|
|
1
|
+
prismalog/__init__.py,sha256=Hr3ZNOGJLb8YvDTxViYuNUOKTlKridjFVmubnoCk1c4,998
|
2
|
+
prismalog/argparser.py,sha256=p5sXHKyACYA2JBLoxT4pcfNvI_R5exUgyx6x9d6RYqw,7648
|
3
|
+
prismalog/config.py,sha256=3pUeLoQgZitIa_5h0CP-By7CZrkvHZvw4r5wpUfaSz8,32024
|
4
|
+
prismalog/log.py,sha256=hkLmMrlX1Ym9_l3L2cAHt-JnJtJpnrEpbvlykr6Rwqg,35462
|
5
|
+
prismalog/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
prismalog-0.1.1.dist-info/licenses/LICENSE,sha256=A1PgraGM1LCUsPVtfZk1lOVRM1TrxvGqfleCPm0hKKk,1071
|
7
|
+
prismalog-0.1.1.dist-info/METADATA,sha256=5TkAlAuIOjNFUmmDZGIaIEqH44ZtKyqYhlSK6kQR23k,7725
|
8
|
+
prismalog-0.1.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
9
|
+
prismalog-0.1.1.dist-info/top_level.txt,sha256=k65xN7XJxN3Z3UOJTQpDUkQnmRDv763tkgVneLrEesU,10
|
10
|
+
prismalog-0.1.1.dist-info/RECORD,,
|
prismalog-0.1.0.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
prismalog/__init__.py,sha256=Hr3ZNOGJLb8YvDTxViYuNUOKTlKridjFVmubnoCk1c4,998
|
2
|
-
prismalog/argparser.py,sha256=M6WVCFvqtTKwxKZ89EtTKuo9jN0Egh785o0sj82WD_0,7105
|
3
|
-
prismalog/config.py,sha256=V5g5YjEZEqznJPUUQdWeeQgiU5R3gm21oONHRhv0gC4,29596
|
4
|
-
prismalog/log.py,sha256=b-zlMs2Y-KI7RuGO8KxFKFuAI0QMN6AiH6sM5vGZNE8,34573
|
5
|
-
prismalog/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
prismalog-0.1.0.dist-info/licenses/LICENSE,sha256=A1PgraGM1LCUsPVtfZk1lOVRM1TrxvGqfleCPm0hKKk,1071
|
7
|
-
prismalog-0.1.0.dist-info/METADATA,sha256=FOOAHtX1MW4nDojHKB8-qxQtwx_a9c376FFzlpJ52v8,7725
|
8
|
-
prismalog-0.1.0.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
|
9
|
-
prismalog-0.1.0.dist-info/top_level.txt,sha256=k65xN7XJxN3Z3UOJTQpDUkQnmRDv763tkgVneLrEesU,10
|
10
|
-
prismalog-0.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|