pythonLogs 4.0.3__tar.gz → 4.0.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pythonLogs
3
- Version: 4.0.3
3
+ Version: 4.0.4
4
4
  Summary: High-performance Python logging library with file rotation and optimized caching for better performance
5
5
  License: MIT
6
6
  Keywords: python3,python-3,python,log,logging,logger,logutils,log-utils,pythonLogs
@@ -58,7 +58,6 @@ High-performance Python logging library with file rotation and optimized caching
58
58
  - [Memory Management](#memory-management)
59
59
  - [Flexible Configuration Options](#flexible-configuration-options)
60
60
  - [Migration Guide](#migration-guide)
61
- - [Performance Improvements](#performance-improvements)
62
61
  - [Development](#source-code)
63
62
  - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
64
63
  - [License](#license)
@@ -476,40 +475,6 @@ registered = LoggerFactory.get_registered_loggers()
476
475
  print(f"Currently registered: {list(registered.keys())}")
477
476
  ```
478
477
 
479
- ## Thread-Safe Operations
480
- All memory management operations are thread-safe and can be used safely in multi-threaded applications:
481
-
482
- ```python
483
- import threading
484
- from pythonLogs import size_rotating_logger, clear_logger_registry
485
-
486
- def worker_function(worker_id):
487
- # Each thread can safely create and use loggers
488
- logger = size_rotating_logger(
489
- name=f"worker_{worker_id}",
490
- directory="/app/logs"
491
- )
492
-
493
- with logger as log:
494
- log.info(f"Worker {worker_id} started")
495
- # Automatic cleanup per thread
496
-
497
- # Create multiple threads - all operations are thread-safe
498
- threads = []
499
- for i in range(10):
500
- thread = threading.Thread(target=worker_function, args=(i,))
501
- threads.append(thread)
502
- thread.start()
503
-
504
- # Wait for completion and clean up
505
- for thread in threads:
506
- thread.join()
507
-
508
- # Safe to clear registry from main thread
509
- clear_logger_registry()
510
- ```
511
-
512
-
513
478
  # Flexible Configuration Options
514
479
  You can use either enums (for type safety) or strings (for simplicity):
515
480
 
@@ -582,25 +547,6 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
582
547
  - 🔧 **Cleaner API** without manual `.init()` calls
583
548
  - 📚 **Centralized configuration** through factory pattern
584
549
 
585
- # Performance Improvements
586
-
587
- ## Benchmarks
588
- The factory pattern with optimizations provides significant performance improvements:
589
-
590
- | Feature | Improvement | Benefit |
591
- |---------|-------------|---------|
592
- | Logger Registry | 90%+ faster | Cached logger instances |
593
- | Settings Caching | ~85% faster | Reused configuration objects |
594
- | Directory Validation | ~75% faster | Cached permission checks |
595
- | Timezone Operations | ~60% faster | Cached timezone functions |
596
-
597
- ## Performance Test Results
598
- ```python
599
- # Create 100 loggers - Performance comparison
600
- # Legacy method: ~0.045 seconds
601
- # Factory pattern: ~0.004 seconds
602
- # Improvement: 91% faster ⚡
603
- ```
604
550
 
605
551
  # Source Code
606
552
  ### Build
@@ -609,7 +555,6 @@ poetry build -f wheel
609
555
  ```
610
556
 
611
557
 
612
-
613
558
  # Run Tests and Get Coverage Report using Poe
614
559
  ```shell
615
560
  poetry update --with test
@@ -617,13 +562,10 @@ poe test
617
562
  ```
618
563
 
619
564
 
620
-
621
565
  # License
622
566
  Released under the [MIT License](LICENSE)
623
567
 
624
568
 
625
-
626
-
627
569
  # Buy me a cup of coffee
628
570
  + [GitHub Sponsor](https://github.com/sponsors/ddc)
629
571
  + [ko-fi](https://ko-fi.com/ddcsta)
@@ -28,7 +28,6 @@ High-performance Python logging library with file rotation and optimized caching
28
28
  - [Memory Management](#memory-management)
29
29
  - [Flexible Configuration Options](#flexible-configuration-options)
30
30
  - [Migration Guide](#migration-guide)
31
- - [Performance Improvements](#performance-improvements)
32
31
  - [Development](#source-code)
33
32
  - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
34
33
  - [License](#license)
@@ -446,40 +445,6 @@ registered = LoggerFactory.get_registered_loggers()
446
445
  print(f"Currently registered: {list(registered.keys())}")
447
446
  ```
448
447
 
449
- ## Thread-Safe Operations
450
- All memory management operations are thread-safe and can be used safely in multi-threaded applications:
451
-
452
- ```python
453
- import threading
454
- from pythonLogs import size_rotating_logger, clear_logger_registry
455
-
456
- def worker_function(worker_id):
457
- # Each thread can safely create and use loggers
458
- logger = size_rotating_logger(
459
- name=f"worker_{worker_id}",
460
- directory="/app/logs"
461
- )
462
-
463
- with logger as log:
464
- log.info(f"Worker {worker_id} started")
465
- # Automatic cleanup per thread
466
-
467
- # Create multiple threads - all operations are thread-safe
468
- threads = []
469
- for i in range(10):
470
- thread = threading.Thread(target=worker_function, args=(i,))
471
- threads.append(thread)
472
- thread.start()
473
-
474
- # Wait for completion and clean up
475
- for thread in threads:
476
- thread.join()
477
-
478
- # Safe to clear registry from main thread
479
- clear_logger_registry()
480
- ```
481
-
482
-
483
448
  # Flexible Configuration Options
484
449
  You can use either enums (for type safety) or strings (for simplicity):
485
450
 
@@ -552,25 +517,6 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
552
517
  - 🔧 **Cleaner API** without manual `.init()` calls
553
518
  - 📚 **Centralized configuration** through factory pattern
554
519
 
555
- # Performance Improvements
556
-
557
- ## Benchmarks
558
- The factory pattern with optimizations provides significant performance improvements:
559
-
560
- | Feature | Improvement | Benefit |
561
- |---------|-------------|---------|
562
- | Logger Registry | 90%+ faster | Cached logger instances |
563
- | Settings Caching | ~85% faster | Reused configuration objects |
564
- | Directory Validation | ~75% faster | Cached permission checks |
565
- | Timezone Operations | ~60% faster | Cached timezone functions |
566
-
567
- ## Performance Test Results
568
- ```python
569
- # Create 100 loggers - Performance comparison
570
- # Legacy method: ~0.045 seconds
571
- # Factory pattern: ~0.004 seconds
572
- # Improvement: 91% faster ⚡
573
- ```
574
520
 
575
521
  # Source Code
576
522
  ### Build
@@ -579,7 +525,6 @@ poetry build -f wheel
579
525
  ```
580
526
 
581
527
 
582
-
583
528
  # Run Tests and Get Coverage Report using Poe
584
529
  ```shell
585
530
  poetry update --with test
@@ -587,13 +532,10 @@ poe test
587
532
  ```
588
533
 
589
534
 
590
-
591
535
  # License
592
536
  Released under the [MIT License](LICENSE)
593
537
 
594
538
 
595
-
596
-
597
539
  # Buy me a cup of coffee
598
540
  + [GitHub Sponsor](https://github.com/sponsors/ddc)
599
541
  + [ko-fi](https://ko-fi.com/ddcsta)
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "pythonLogs"
7
- version = "4.0.3"
7
+ version = "4.0.4"
8
8
  description = "High-performance Python logging library with file rotation and optimized caching for better performance"
9
9
  license = "MIT"
10
10
  readme = "README.md"
@@ -1,12 +1,13 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  import logging
3
- import threading
4
3
  from typing import Optional
5
4
  from pythonLogs.log_utils import get_format, get_level, get_timezone_function
6
5
  from pythonLogs.memory_utils import cleanup_logger_handlers, register_logger_weakref
7
6
  from pythonLogs.settings import get_log_settings
7
+ from pythonLogs.thread_safety import auto_thread_safe
8
8
 
9
9
 
10
+ @auto_thread_safe(['init', '_cleanup_logger'])
10
11
  class BasicLog:
11
12
  """Basic logger with context manager support for automatic resource cleanup."""
12
13
 
@@ -27,8 +28,6 @@ class BasicLog:
27
28
  self.timezone = timezone or _settings.timezone
28
29
  self.showlocation = showlocation or _settings.show_location
29
30
  self.logger = None
30
- # Instance-level lock for thread safety
31
- self._lock = threading.Lock()
32
31
 
33
32
  def init(self):
34
33
  logger = logging.getLogger(self.appname)
@@ -54,8 +53,7 @@ class BasicLog:
54
53
 
55
54
  def _cleanup_logger(self, logger: logging.Logger) -> None:
56
55
  """Clean up logger resources by closing all handlers with thread safety."""
57
- with self._lock:
58
- cleanup_logger_handlers(logger)
56
+ cleanup_logger_handlers(logger)
59
57
 
60
58
  @staticmethod
61
59
  def cleanup_logger(logger: logging.Logger) -> None:
@@ -2,7 +2,6 @@
2
2
  import logging.handlers
3
3
  import os
4
4
  import re
5
- import threading
6
5
  from typing import Optional
7
6
  from pythonLogs.constants import MB_TO_BYTES
8
7
  from pythonLogs.log_utils import (
@@ -18,8 +17,10 @@ from pythonLogs.log_utils import (
18
17
  )
19
18
  from pythonLogs.memory_utils import cleanup_logger_handlers, register_logger_weakref
20
19
  from pythonLogs.settings import get_log_settings
20
+ from pythonLogs.thread_safety import auto_thread_safe
21
21
 
22
22
 
23
+ @auto_thread_safe(['init', '_cleanup_logger'])
23
24
  class SizeRotatingLog:
24
25
  """Size-based rotating logger with context manager support for automatic resource cleanup."""
25
26
  def __init__(
@@ -49,8 +50,6 @@ class SizeRotatingLog:
49
50
  self.streamhandler = streamhandler or _settings.stream_handler
50
51
  self.showlocation = showlocation or _settings.show_location
51
52
  self.logger = None
52
- # Instance-level lock for thread safety
53
- self._lock = threading.Lock()
54
53
 
55
54
  def init(self):
56
55
  check_filename_instance(self.filenames)
@@ -98,8 +97,7 @@ class SizeRotatingLog:
98
97
 
99
98
  def _cleanup_logger(self, logger: logging.Logger) -> None:
100
99
  """Clean up logger resources by closing all handlers with thread safety."""
101
- with self._lock:
102
- cleanup_logger_handlers(logger)
100
+ cleanup_logger_handlers(logger)
103
101
 
104
102
  @staticmethod
105
103
  def cleanup_logger(logger: logging.Logger) -> None:
@@ -0,0 +1,135 @@
1
+ # -*- encoding: utf-8 -*-
2
+ import functools
3
+ import threading
4
+ from typing import Any, Callable, Dict, TypeVar, Type
5
+
6
+ F = TypeVar('F', bound=Callable[..., Any])
7
+
8
+
9
+ class ThreadSafeMeta(type):
10
+ """Metaclass that automatically adds thread safety to class methods."""
11
+
12
+ def __new__(mcs, name: str, bases: tuple, namespace: Dict[str, Any], **kwargs):
13
+ # Create the class first
14
+ cls = super().__new__(mcs, name, bases, namespace)
15
+
16
+ # Add a class-level lock if not already present
17
+ if not hasattr(cls, '_lock'):
18
+ cls._lock = threading.RLock()
19
+
20
+ # Get methods that should be thread-safe (exclude private/dunder methods)
21
+ thread_safe_methods = getattr(cls, '_thread_safe_methods', None)
22
+ if thread_safe_methods is None:
23
+ # Auto-detect public methods that modify state
24
+ thread_safe_methods = [
25
+ method_name for method_name in namespace
26
+ if (callable(getattr(cls, method_name, None)) and
27
+ not method_name.startswith('_') and
28
+ method_name not in ['__enter__', '__exit__', '__init__'])
29
+ ]
30
+
31
+ # Wrap each method with automatic locking
32
+ for method_name in thread_safe_methods:
33
+ if hasattr(cls, method_name):
34
+ original_method = getattr(cls, method_name)
35
+ if callable(original_method):
36
+ wrapped_method = thread_safe(original_method)
37
+ setattr(cls, method_name, wrapped_method)
38
+
39
+ return cls
40
+
41
+
42
+ def thread_safe(func: F) -> F:
43
+ """Decorator that automatically adds thread safety to methods."""
44
+
45
+ @functools.wraps(func)
46
+ def wrapper(self, *args, **kwargs):
47
+ # Use instance lock if available, otherwise class lock
48
+ lock = getattr(self, '_lock', None)
49
+ if lock is None:
50
+ # Check if class has lock, if not create one
51
+ if not hasattr(self.__class__, '_lock'):
52
+ self.__class__._lock = threading.RLock()
53
+ lock = self.__class__._lock
54
+
55
+ with lock:
56
+ return func(self, *args, **kwargs)
57
+
58
+ return wrapper
59
+
60
+
61
+ def auto_thread_safe(thread_safe_methods: list = None):
62
+ """Class decorator that adds automatic thread safety to specified methods."""
63
+
64
+ def decorator(cls: Type) -> Type:
65
+ # Add lock to class if not present
66
+ if not hasattr(cls, '_lock'):
67
+ cls._lock = threading.RLock()
68
+
69
+ # Store thread-safe methods list
70
+ if thread_safe_methods:
71
+ cls._thread_safe_methods = thread_safe_methods
72
+
73
+ # Get methods to make thread-safe
74
+ methods_to_wrap = thread_safe_methods or [
75
+ method_name for method_name in dir(cls)
76
+ if (callable(getattr(cls, method_name, None)) and
77
+ not method_name.startswith('_') and
78
+ method_name not in ['__enter__', '__exit__', '__init__'])
79
+ ]
80
+
81
+ # Wrap each method
82
+ for method_name in methods_to_wrap:
83
+ if hasattr(cls, method_name):
84
+ original_method = getattr(cls, method_name)
85
+ if callable(original_method) and not hasattr(original_method, '_thread_safe_wrapped'):
86
+ wrapped_method = thread_safe(original_method)
87
+ wrapped_method._thread_safe_wrapped = True
88
+ setattr(cls, method_name, wrapped_method)
89
+
90
+ return cls
91
+
92
+ return decorator
93
+
94
+
95
+ class AutoThreadSafe:
96
+ """Base class that provides automatic thread safety for all public methods."""
97
+
98
+ def __init__(self):
99
+ if not hasattr(self, '_lock'):
100
+ self._lock = threading.RLock()
101
+
102
+ def __init_subclass__(cls, **kwargs):
103
+ super().__init_subclass__(**kwargs)
104
+
105
+ # Add class-level lock
106
+ if not hasattr(cls, '_lock'):
107
+ cls._lock = threading.RLock()
108
+
109
+ # Auto-wrap public methods
110
+ for attr_name in dir(cls):
111
+ if not attr_name.startswith('_'):
112
+ attr = getattr(cls, attr_name)
113
+ if callable(attr) and not hasattr(attr, '_thread_safe_wrapped'):
114
+ wrapped_attr = thread_safe(attr)
115
+ wrapped_attr._thread_safe_wrapped = True
116
+ setattr(cls, attr_name, wrapped_attr)
117
+
118
+
119
+ def synchronized_method(func: F) -> F:
120
+ """Decorator for individual methods that need thread safety."""
121
+ return thread_safe(func)
122
+
123
+
124
+ class ThreadSafeContext:
125
+ """Context manager for thread-safe operations."""
126
+
127
+ def __init__(self, lock: threading.Lock):
128
+ self.lock = lock
129
+
130
+ def __enter__(self):
131
+ self.lock.acquire()
132
+ return self
133
+
134
+ def __exit__(self, exc_type, exc_val, exc_tb):
135
+ self.lock.release()
@@ -1,7 +1,6 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  import logging.handlers
3
3
  import os
4
- import threading
5
4
  from typing import Optional
6
5
  from pythonLogs.log_utils import (
7
6
  check_directory_permissions,
@@ -15,8 +14,10 @@ from pythonLogs.log_utils import (
15
14
  )
16
15
  from pythonLogs.memory_utils import cleanup_logger_handlers, register_logger_weakref
17
16
  from pythonLogs.settings import get_log_settings
17
+ from pythonLogs.thread_safety import auto_thread_safe
18
18
 
19
19
 
20
+ @auto_thread_safe(['init', '_cleanup_logger'])
20
21
  class TimedRotatingLog:
21
22
  """
22
23
  Time-based rotating logger with context manager support for automatic resource cleanup.
@@ -60,8 +61,6 @@ class TimedRotatingLog:
60
61
  self.showlocation = showlocation or _settings.show_location
61
62
  self.rotateatutc = rotateatutc or _settings.rotate_at_utc
62
63
  self.logger = None
63
- # Instance-level lock for thread safety
64
- self._lock = threading.Lock()
65
64
 
66
65
  def init(self):
67
66
  check_filename_instance(self.filenames)
@@ -107,8 +106,7 @@ class TimedRotatingLog:
107
106
 
108
107
  def _cleanup_logger(self, logger: logging.Logger) -> None:
109
108
  """Clean up logger resources by closing all handlers with thread safety."""
110
- with self._lock:
111
- cleanup_logger_handlers(logger)
109
+ cleanup_logger_handlers(logger)
112
110
 
113
111
  @staticmethod
114
112
  def cleanup_logger(logger: logging.Logger) -> None:
File without changes