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.
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/PKG-INFO +44 -98
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/README.md +43 -97
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pyproject.toml +7 -16
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/basic_log.py +3 -5
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/size_rotating.py +3 -5
- pythonlogs-4.0.4/pythonLogs/thread_safety.py +135 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/timed_rotating.py +3 -5
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/LICENSE +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/.env.example +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/__init__.py +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/constants.py +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/factory.py +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/log_utils.py +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/memory_utils.py +0 -0
- {pythonlogs-4.0.2 → pythonlogs-4.0.4}/pythonLogs/settings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pythonLogs
|
|
3
|
-
Version: 4.0.
|
|
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
|
-
|
|
515
|
-
|
|
478
|
+
# Flexible Configuration Options
|
|
479
|
+
You can use either enums (for type safety) or strings (for simplicity):
|
|
516
480
|
|
|
517
481
|
```python
|
|
518
|
-
import
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
#
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
#
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
#
|
|
544
|
-
|
|
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
|
-
|
|
485
|
-
|
|
448
|
+
# Flexible Configuration Options
|
|
449
|
+
You can use either enums (for type safety) or strings (for simplicity):
|
|
486
450
|
|
|
487
451
|
```python
|
|
488
|
-
import
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
#
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
#
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
#
|
|
514
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|