prismalog 0.1.0__py3-none-any.whl → 0.1.2__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 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 your own application-specific arguments
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
- - ``LOG_COLORED_CONSOLE``: Whether to use colored console output
32
- - ``LOG_DISABLE_ROTATION``: Whether to disable log rotation
33
- - ``LOG_EXIT_ON_CRITICAL``: Whether to exit on critical logs
34
- - ``LOG_TEST_MODE``: Whether logger is in test mode
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 here
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 with rotation support
12
- - CriticalExitHandler: Optional handler that exits the program on critical errors
13
- - ColoredLogger: Main logger class with enhanced functionality
14
- - get_logger: Factory function to obtain properly configured loggers
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
- - Up to 29K msgs/sec in multiprocessing mode
18
- - Colored console output for improved readability
19
- - Automatic log file rotation based on size
20
- - Process-safe and thread-safe logging
21
- - Special handling for critical errors
22
- - Configurable verbosity levels for different modules
23
- - Zero external dependencies
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
- >>> logger = get_logger("my_module")
28
- >>> logger.info("Application started")
29
- >>> logger.debug("Detailed debugging information")
30
- >>> logger.error("Something went wrong")
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
- new_filename = os.path.join(log_dir, f"app_{timestamp}_{unique_suffix}.log")
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(ColoredFormatter(fmt=log_format, colored=LoggingConfig.get("colored_console", True)))
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
- cls._log_file_path = log_file_path # Store the determined path
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
- # --- Formatter from Config ---
507
- default_format = "%(asctime)s - %(filename)s - %(name)s - [%(levelname)s] - %(message)s"
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
- # Use the config setting for 'colored'
514
- handler.setFormatter(ColoredFormatter(log_format, colored=use_file_color))
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) # Use milliseconds as unique ID
575
- cls._log_file_path = os.path.join(log_dir, f"app_{timestamp}_{unique_suffix}.log")
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,14 +686,13 @@ 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
- self.logger.debug(msg, *args, **kwargs)
691
+ self.logger.debug(msg, *args, **kwargs, stacklevel=2)
655
692
 
656
693
  def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
657
694
  """Logs an info message."""
658
- self.logger.info(msg, *args, **kwargs)
695
+ self.logger.info(msg, *args, **kwargs, stacklevel=2)
659
696
 
660
697
  def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
661
698
  """
@@ -666,7 +703,7 @@ class ColoredLogger:
666
703
  *args: Variable length argument list
667
704
  **kwargs: Arbitrary keyword arguments
668
705
  """
669
- self.logger.warning(msg, *args, **kwargs)
706
+ self.logger.warning(msg, *args, **kwargs, stacklevel=2)
670
707
 
671
708
  def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
672
709
  """
@@ -677,7 +714,7 @@ class ColoredLogger:
677
714
  *args: Variable length argument list
678
715
  **kwargs: Arbitrary keyword arguments
679
716
  """
680
- self.logger.error(msg, *args, **kwargs)
717
+ self.logger.error(msg, *args, **kwargs, stacklevel=2)
681
718
 
682
719
  def critical(self, msg: str, *args: Any, **kwargs: Any) -> None:
683
720
  """
@@ -690,14 +727,13 @@ class ColoredLogger:
690
727
  *args: Variable length argument list
691
728
  **kwargs: Arbitrary keyword arguments
692
729
  """
693
- self.logger.critical(msg, *args, **kwargs)
730
+ self.logger.critical(msg, *args, **kwargs, stacklevel=2)
694
731
 
695
732
  def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
696
733
  """Logs an exception message."""
697
- self.logger.exception(msg, *args, **kwargs)
734
+ self.logger.exception(msg, *args, **kwargs, stacklevel=2)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prismalog
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: High-performance colored, multi-process logging library for Python
5
5
  Author-email: Alexey Obukhov <alexey.obukhov@hotmail.com>
6
6
  License: MIT
@@ -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=hdl2syPBXO7zZYltcFlXJQlcdxY7YkBgZ2OdoNHOspE,35546
5
+ prismalog/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ prismalog-0.1.2.dist-info/licenses/LICENSE,sha256=A1PgraGM1LCUsPVtfZk1lOVRM1TrxvGqfleCPm0hKKk,1071
7
+ prismalog-0.1.2.dist-info/METADATA,sha256=tRjXK_LgTKeCsyeCWxNsqxd7zJCTnlp7boYJ6oqAEWM,7725
8
+ prismalog-0.1.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
9
+ prismalog-0.1.2.dist-info/top_level.txt,sha256=k65xN7XJxN3Z3UOJTQpDUkQnmRDv763tkgVneLrEesU,10
10
+ prismalog-0.1.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.1)
2
+ Generator: setuptools (80.7.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,