pythonLogs 4.0.2__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.2
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
@@ -54,11 +54,14 @@ High-performance Python logging library with file rotation and optimized caching
54
54
  - [Timed Rotating Logger](#timed-rotating-logger)
55
55
  - [Context Manager Support](#context-manager-support)
56
56
  - [Advanced Factory Features](#advanced-factory-features)
57
+ - [Environment Variables](#env-variables-optional--production)
57
58
  - [Memory Management](#memory-management)
59
+ - [Flexible Configuration Options](#flexible-configuration-options)
58
60
  - [Migration Guide](#migration-guide)
59
- - [Performance Improvements](#performance-improvements)
60
- - [Environment Variables](#env-variables-optional)
61
61
  - [Development](#source-code)
62
+ - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
63
+ - [License](#license)
64
+ - [Buy me a cup of coffee](#buy-me-a-cup-of-coffee)
62
65
 
63
66
 
64
67
 
@@ -264,6 +267,7 @@ logger.warning("This is a warning example")
264
267
 
265
268
  # Context Manager Support
266
269
 
270
+ Slow, but if you want immediate, deterministic cleanup for a specific scope.\
267
271
  All logger types support context managers for automatic resource cleanup and exception safety:
268
272
 
269
273
  ## Basic Usage
@@ -333,44 +337,6 @@ assert logger1 is logger2
333
337
  clear_logger_registry()
334
338
  ```
335
339
 
336
- ## Flexible Configuration Options
337
- You can use either enums (for type safety) or strings (for simplicity):
338
-
339
- ```python
340
- from pythonLogs import LogLevel, RotateWhen, LoggerType
341
-
342
- # Option 1: Type-safe enums (recommended)
343
- LogLevel.DEBUG # "DEBUG"
344
- LogLevel.INFO # "INFO"
345
- LogLevel.WARNING # "WARNING"
346
- LogLevel.ERROR # "ERROR"
347
- LogLevel.CRITICAL # "CRITICAL"
348
-
349
- # Option 2: String values (case-insensitive)
350
- "debug" # Same as LogLevel.DEBUG
351
- "info" # Same as LogLevel.INFO
352
- "warning" # Same as LogLevel.WARNING
353
- "warn" # Same as LogLevel.WARN (alias)
354
- "error" # Same as LogLevel.ERROR
355
- "critical" # Same as LogLevel.CRITICAL
356
- "crit" # Same as LogLevel.CRIT (alias)
357
- # Also supports: "DEBUG", "Info", "Warning", etc.
358
-
359
- # RotateWhen values
360
- RotateWhen.MIDNIGHT # "midnight"
361
- RotateWhen.HOURLY # "H"
362
- RotateWhen.DAILY # "D"
363
- RotateWhen.MONDAY # "W0"
364
- # ... through SUNDAY # "W6"
365
- # String equivalents: "midnight", "H", "D", "W0"-"W6"
366
-
367
- # LoggerType values
368
- LoggerType.BASIC # "basic"
369
- LoggerType.SIZE_ROTATING # "size_rotating"
370
- LoggerType.TIMED_ROTATING # "timed_rotating"
371
- # String equivalents: "basic", "size_rotating", "timed_rotating"
372
- ```
373
-
374
340
  ## Production Setup Example
375
341
  ```python
376
342
  from pythonLogs import size_rotating_logger, timed_rotating_logger, LogLevel, RotateWhen
@@ -415,9 +381,9 @@ audit_logger.info("User admin logged in")
415
381
  ```
416
382
 
417
383
  ## Env Variables (Optional | Production)
418
- .env variables can be used by leaving all options blank when calling the function
419
- If not specified inside the .env file, it will use the dafault value
420
- This is a good approach for production environments, since options can be changed easily
384
+ The .env variables file can be used by leaving all options blank when calling the function.\
385
+ If not specified inside the .env file, it will use the dafault value.\
386
+ This is a good approach for production environments, since options can be changed easily.
421
387
  ```python
422
388
  from pythonLogs import timed_rotating_logger
423
389
  log = timed_rotating_logger()
@@ -447,8 +413,6 @@ LOG_ROTATE_FILE_SUFIX="%Y%m%d"
447
413
  ```
448
414
 
449
415
 
450
-
451
-
452
416
  # Memory Management
453
417
 
454
418
  The library includes comprehensive memory management features to prevent memory leaks and optimize resource usage:
@@ -511,37 +475,42 @@ registered = LoggerFactory.get_registered_loggers()
511
475
  print(f"Currently registered: {list(registered.keys())}")
512
476
  ```
513
477
 
514
- ## Thread-Safe Operations
515
- All memory management operations are thread-safe and can be used safely in multi-threaded applications:
478
+ # Flexible Configuration Options
479
+ You can use either enums (for type safety) or strings (for simplicity):
516
480
 
517
481
  ```python
518
- import threading
519
- from pythonLogs import size_rotating_logger, clear_logger_registry
520
-
521
- def worker_function(worker_id):
522
- # Each thread can safely create and use loggers
523
- logger = size_rotating_logger(
524
- name=f"worker_{worker_id}",
525
- directory="/app/logs"
526
- )
527
-
528
- with logger as log:
529
- log.info(f"Worker {worker_id} started")
530
- # Automatic cleanup per thread
531
-
532
- # Create multiple threads - all operations are thread-safe
533
- threads = []
534
- for i in range(10):
535
- thread = threading.Thread(target=worker_function, args=(i,))
536
- threads.append(thread)
537
- thread.start()
538
-
539
- # Wait for completion and clean up
540
- for thread in threads:
541
- thread.join()
542
-
543
- # Safe to clear registry from main thread
544
- clear_logger_registry()
482
+ from pythonLogs import LogLevel, RotateWhen, LoggerType
483
+
484
+ # Option 1: Type-safe enums (recommended)
485
+ LogLevel.DEBUG # "DEBUG"
486
+ LogLevel.INFO # "INFO"
487
+ LogLevel.WARNING # "WARNING"
488
+ LogLevel.ERROR # "ERROR"
489
+ LogLevel.CRITICAL # "CRITICAL"
490
+
491
+ # Option 2: String values (case-insensitive)
492
+ "debug" # Same as LogLevel.DEBUG
493
+ "info" # Same as LogLevel.INFO
494
+ "warning" # Same as LogLevel.WARNING
495
+ "warn" # Same as LogLevel.WARN (alias)
496
+ "error" # Same as LogLevel.ERROR
497
+ "critical" # Same as LogLevel.CRITICAL
498
+ "crit" # Same as LogLevel.CRIT (alias)
499
+ # Also supports: "DEBUG", "Info", "Warning", etc.
500
+
501
+ # RotateWhen values
502
+ RotateWhen.MIDNIGHT # "midnight"
503
+ RotateWhen.HOURLY # "H"
504
+ RotateWhen.DAILY # "D"
505
+ RotateWhen.MONDAY # "W0"
506
+ # ... through SUNDAY # "W6"
507
+ # String equivalents: "midnight", "H", "D", "W0"-"W6"
508
+
509
+ # LoggerType values
510
+ LoggerType.BASIC # "basic"
511
+ LoggerType.SIZE_ROTATING # "size_rotating"
512
+ LoggerType.TIMED_ROTATING # "timed_rotating"
513
+ # String equivalents: "basic", "size_rotating", "timed_rotating"
545
514
  ```
546
515
 
547
516
 
@@ -578,25 +547,6 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
578
547
  - 🔧 **Cleaner API** without manual `.init()` calls
579
548
  - 📚 **Centralized configuration** through factory pattern
580
549
 
581
- # Performance Improvements
582
-
583
- ## Benchmarks
584
- The factory pattern with optimizations provides significant performance improvements:
585
-
586
- | Feature | Improvement | Benefit |
587
- |---------|-------------|---------|
588
- | Logger Registry | 90%+ faster | Cached logger instances |
589
- | Settings Caching | ~85% faster | Reused configuration objects |
590
- | Directory Validation | ~75% faster | Cached permission checks |
591
- | Timezone Operations | ~60% faster | Cached timezone functions |
592
-
593
- ## Performance Test Results
594
- ```python
595
- # Create 100 loggers - Performance comparison
596
- # Legacy method: ~0.045 seconds
597
- # Factory pattern: ~0.004 seconds
598
- # Improvement: 91% faster ⚡
599
- ```
600
550
 
601
551
  # Source Code
602
552
  ### Build
@@ -605,7 +555,6 @@ poetry build -f wheel
605
555
  ```
606
556
 
607
557
 
608
-
609
558
  # Run Tests and Get Coverage Report using Poe
610
559
  ```shell
611
560
  poetry update --with test
@@ -613,13 +562,10 @@ poe test
613
562
  ```
614
563
 
615
564
 
616
-
617
565
  # License
618
566
  Released under the [MIT License](LICENSE)
619
567
 
620
568
 
621
-
622
-
623
569
  # Buy me a cup of coffee
624
570
  + [GitHub Sponsor](https://github.com/sponsors/ddc)
625
571
  + [ko-fi](https://ko-fi.com/ddcsta)
@@ -24,11 +24,14 @@ High-performance Python logging library with file rotation and optimized caching
24
24
  - [Timed Rotating Logger](#timed-rotating-logger)
25
25
  - [Context Manager Support](#context-manager-support)
26
26
  - [Advanced Factory Features](#advanced-factory-features)
27
+ - [Environment Variables](#env-variables-optional--production)
27
28
  - [Memory Management](#memory-management)
29
+ - [Flexible Configuration Options](#flexible-configuration-options)
28
30
  - [Migration Guide](#migration-guide)
29
- - [Performance Improvements](#performance-improvements)
30
- - [Environment Variables](#env-variables-optional)
31
31
  - [Development](#source-code)
32
+ - [Run Tests and Get Coverage Report using Poe](#run-tests-and-get-coverage-report-using-poe)
33
+ - [License](#license)
34
+ - [Buy me a cup of coffee](#buy-me-a-cup-of-coffee)
32
35
 
33
36
 
34
37
 
@@ -234,6 +237,7 @@ logger.warning("This is a warning example")
234
237
 
235
238
  # Context Manager Support
236
239
 
240
+ Slow, but if you want immediate, deterministic cleanup for a specific scope.\
237
241
  All logger types support context managers for automatic resource cleanup and exception safety:
238
242
 
239
243
  ## Basic Usage
@@ -303,44 +307,6 @@ assert logger1 is logger2
303
307
  clear_logger_registry()
304
308
  ```
305
309
 
306
- ## Flexible Configuration Options
307
- You can use either enums (for type safety) or strings (for simplicity):
308
-
309
- ```python
310
- from pythonLogs import LogLevel, RotateWhen, LoggerType
311
-
312
- # Option 1: Type-safe enums (recommended)
313
- LogLevel.DEBUG # "DEBUG"
314
- LogLevel.INFO # "INFO"
315
- LogLevel.WARNING # "WARNING"
316
- LogLevel.ERROR # "ERROR"
317
- LogLevel.CRITICAL # "CRITICAL"
318
-
319
- # Option 2: String values (case-insensitive)
320
- "debug" # Same as LogLevel.DEBUG
321
- "info" # Same as LogLevel.INFO
322
- "warning" # Same as LogLevel.WARNING
323
- "warn" # Same as LogLevel.WARN (alias)
324
- "error" # Same as LogLevel.ERROR
325
- "critical" # Same as LogLevel.CRITICAL
326
- "crit" # Same as LogLevel.CRIT (alias)
327
- # Also supports: "DEBUG", "Info", "Warning", etc.
328
-
329
- # RotateWhen values
330
- RotateWhen.MIDNIGHT # "midnight"
331
- RotateWhen.HOURLY # "H"
332
- RotateWhen.DAILY # "D"
333
- RotateWhen.MONDAY # "W0"
334
- # ... through SUNDAY # "W6"
335
- # String equivalents: "midnight", "H", "D", "W0"-"W6"
336
-
337
- # LoggerType values
338
- LoggerType.BASIC # "basic"
339
- LoggerType.SIZE_ROTATING # "size_rotating"
340
- LoggerType.TIMED_ROTATING # "timed_rotating"
341
- # String equivalents: "basic", "size_rotating", "timed_rotating"
342
- ```
343
-
344
310
  ## Production Setup Example
345
311
  ```python
346
312
  from pythonLogs import size_rotating_logger, timed_rotating_logger, LogLevel, RotateWhen
@@ -385,9 +351,9 @@ audit_logger.info("User admin logged in")
385
351
  ```
386
352
 
387
353
  ## Env Variables (Optional | Production)
388
- .env variables can be used by leaving all options blank when calling the function
389
- If not specified inside the .env file, it will use the dafault value
390
- This is a good approach for production environments, since options can be changed easily
354
+ The .env variables file can be used by leaving all options blank when calling the function.\
355
+ If not specified inside the .env file, it will use the dafault value.\
356
+ This is a good approach for production environments, since options can be changed easily.
391
357
  ```python
392
358
  from pythonLogs import timed_rotating_logger
393
359
  log = timed_rotating_logger()
@@ -417,8 +383,6 @@ LOG_ROTATE_FILE_SUFIX="%Y%m%d"
417
383
  ```
418
384
 
419
385
 
420
-
421
-
422
386
  # Memory Management
423
387
 
424
388
  The library includes comprehensive memory management features to prevent memory leaks and optimize resource usage:
@@ -481,37 +445,42 @@ registered = LoggerFactory.get_registered_loggers()
481
445
  print(f"Currently registered: {list(registered.keys())}")
482
446
  ```
483
447
 
484
- ## Thread-Safe Operations
485
- All memory management operations are thread-safe and can be used safely in multi-threaded applications:
448
+ # Flexible Configuration Options
449
+ You can use either enums (for type safety) or strings (for simplicity):
486
450
 
487
451
  ```python
488
- import threading
489
- from pythonLogs import size_rotating_logger, clear_logger_registry
490
-
491
- def worker_function(worker_id):
492
- # Each thread can safely create and use loggers
493
- logger = size_rotating_logger(
494
- name=f"worker_{worker_id}",
495
- directory="/app/logs"
496
- )
497
-
498
- with logger as log:
499
- log.info(f"Worker {worker_id} started")
500
- # Automatic cleanup per thread
501
-
502
- # Create multiple threads - all operations are thread-safe
503
- threads = []
504
- for i in range(10):
505
- thread = threading.Thread(target=worker_function, args=(i,))
506
- threads.append(thread)
507
- thread.start()
508
-
509
- # Wait for completion and clean up
510
- for thread in threads:
511
- thread.join()
512
-
513
- # Safe to clear registry from main thread
514
- clear_logger_registry()
452
+ from pythonLogs import LogLevel, RotateWhen, LoggerType
453
+
454
+ # Option 1: Type-safe enums (recommended)
455
+ LogLevel.DEBUG # "DEBUG"
456
+ LogLevel.INFO # "INFO"
457
+ LogLevel.WARNING # "WARNING"
458
+ LogLevel.ERROR # "ERROR"
459
+ LogLevel.CRITICAL # "CRITICAL"
460
+
461
+ # Option 2: String values (case-insensitive)
462
+ "debug" # Same as LogLevel.DEBUG
463
+ "info" # Same as LogLevel.INFO
464
+ "warning" # Same as LogLevel.WARNING
465
+ "warn" # Same as LogLevel.WARN (alias)
466
+ "error" # Same as LogLevel.ERROR
467
+ "critical" # Same as LogLevel.CRITICAL
468
+ "crit" # Same as LogLevel.CRIT (alias)
469
+ # Also supports: "DEBUG", "Info", "Warning", etc.
470
+
471
+ # RotateWhen values
472
+ RotateWhen.MIDNIGHT # "midnight"
473
+ RotateWhen.HOURLY # "H"
474
+ RotateWhen.DAILY # "D"
475
+ RotateWhen.MONDAY # "W0"
476
+ # ... through SUNDAY # "W6"
477
+ # String equivalents: "midnight", "H", "D", "W0"-"W6"
478
+
479
+ # LoggerType values
480
+ LoggerType.BASIC # "basic"
481
+ LoggerType.SIZE_ROTATING # "size_rotating"
482
+ LoggerType.TIMED_ROTATING # "timed_rotating"
483
+ # String equivalents: "basic", "size_rotating", "timed_rotating"
515
484
  ```
516
485
 
517
486
 
@@ -548,25 +517,6 @@ timed_logger = timed_rotating_logger(level=LogLevel.WARNING, name="app", directo
548
517
  - 🔧 **Cleaner API** without manual `.init()` calls
549
518
  - 📚 **Centralized configuration** through factory pattern
550
519
 
551
- # Performance Improvements
552
-
553
- ## Benchmarks
554
- The factory pattern with optimizations provides significant performance improvements:
555
-
556
- | Feature | Improvement | Benefit |
557
- |---------|-------------|---------|
558
- | Logger Registry | 90%+ faster | Cached logger instances |
559
- | Settings Caching | ~85% faster | Reused configuration objects |
560
- | Directory Validation | ~75% faster | Cached permission checks |
561
- | Timezone Operations | ~60% faster | Cached timezone functions |
562
-
563
- ## Performance Test Results
564
- ```python
565
- # Create 100 loggers - Performance comparison
566
- # Legacy method: ~0.045 seconds
567
- # Factory pattern: ~0.004 seconds
568
- # Improvement: 91% faster ⚡
569
- ```
570
520
 
571
521
  # Source Code
572
522
  ### Build
@@ -575,7 +525,6 @@ poetry build -f wheel
575
525
  ```
576
526
 
577
527
 
578
-
579
528
  # Run Tests and Get Coverage Report using Poe
580
529
  ```shell
581
530
  poetry update --with test
@@ -583,13 +532,10 @@ poe test
583
532
  ```
584
533
 
585
534
 
586
-
587
535
  # License
588
536
  Released under the [MIT License](LICENSE)
589
537
 
590
538
 
591
-
592
-
593
539
  # Buy me a cup of coffee
594
540
  + [GitHub Sponsor](https://github.com/sponsors/ddc)
595
541
  + [ko-fi](https://ko-fi.com/ddcsta)
@@ -2,10 +2,9 @@
2
2
  requires = ["poetry-core>=2.0.0,<3.0.0"]
3
3
  build-backend = "poetry.core.masonry.api"
4
4
 
5
-
6
5
  [tool.poetry]
7
6
  name = "pythonLogs"
8
- version = "4.0.2"
7
+ version = "4.0.4"
9
8
  description = "High-performance Python logging library with file rotation and optimized caching for better performance"
10
9
  license = "MIT"
11
10
  readme = "README.md"
@@ -31,51 +30,43 @@ classifiers = [
31
30
  "Natural Language :: English",
32
31
  ]
33
32
 
34
-
35
33
  [tool.poetry.dependencies]
36
34
  python = "^3.10"
37
35
  pydantic = "^2.11.7"
38
36
  pydantic-settings = "^2.10.1"
39
37
  python-dotenv = "^1.1.1"
40
38
 
41
-
42
39
  [tool.poetry.group.test.dependencies]
43
40
  coverage = "^7.9.2"
44
41
  poethepoet = "^0.36.0"
45
42
  psutil = "^7.0.0"
46
43
  pytest = "^8.4.1"
47
44
 
45
+ [tool.poe.tasks]
46
+ _test = "coverage run -m pytest -v"
47
+ _coverage_report = "coverage report"
48
+ _coverage_xml = "coverage xml"
49
+ tests = ["_test", "_coverage_report", "_coverage_xml"]
50
+ test = ["tests"]
48
51
 
49
52
  [tool.poetry.group.test]
50
53
  optional = true
51
54
 
52
-
53
55
  [tool.black]
54
56
  line-length = 120
55
57
  skip-string-normalization = true
56
58
 
57
-
58
59
  [tool.pytest.ini_options]
59
60
  markers = [
60
61
  "slow: marks tests as slow (deselect with '-m \"not slow\"')"
61
62
  ]
62
63
 
63
-
64
64
  [tool.coverage.run]
65
65
  omit = [
66
66
  "tests/*",
67
67
  ]
68
68
 
69
-
70
69
  [tool.coverage.report]
71
70
  exclude_lines = [
72
71
  "pragma: no cover",
73
72
  ]
74
-
75
-
76
- [tool.poe.tasks]
77
- _test = "coverage run -m pytest -v"
78
- _coverage_report = "coverage report"
79
- _coverage_xml = "coverage xml"
80
- tests = ["_test", "_coverage_report", "_coverage_xml"]
81
- test = ["tests"]
@@ -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