Skip to content

Commit 0a0b9b3

Browse files
authored
V4.0.2 (#3)
* V4.0.2 * V4.0.2 --------- Co-authored-by: ddc <[email protected]>
1 parent 440e208 commit 0a0b9b3

File tree

12 files changed

+1521
-482
lines changed

12 files changed

+1521
-482
lines changed

.github/workflows/tests.yml

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ name: Run Tests
22

33
on:
44
push:
5-
branches:
6-
- "**" # including all branches before excluding master
7-
- "!master"
8-
- "!main"
5+
branches: ["**"]
6+
97

108
jobs:
11-
tests:
9+
test:
1210
name: Test Python ${{ matrix.python-version }}
1311
runs-on: ubuntu-latest
1412
strategy:
@@ -41,3 +39,34 @@ jobs:
4139
with:
4240
token: ${{ secrets.CODECOV_TOKEN }}
4341
slug: ddc/pythonLogs
42+
43+
build:
44+
name: Build Test Package
45+
runs-on: ubuntu-latest
46+
needs: test
47+
steps:
48+
- uses: actions/checkout@v4
49+
50+
- name: Set up Python
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: "3.13"
54+
55+
- name: Install Poetry
56+
uses: snok/install-poetry@v1
57+
with:
58+
virtualenvs-create: true
59+
virtualenvs-in-project: true
60+
61+
- name: Install build dependencies only
62+
run: poetry install --only main --no-interaction --no-ansi
63+
64+
- name: Build package
65+
run: poetry build
66+
67+
- name: Upload artifacts
68+
uses: actions/upload-artifact@v4
69+
with:
70+
name: python-packages
71+
path: dist/
72+
retention-days: 7

.github/workflows/workflow.yml

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,73 +7,6 @@ on:
77

88

99
jobs:
10-
test:
11-
name: Test Python ${{ matrix.python-version }}
12-
runs-on: ubuntu-latest
13-
if: "!startsWith(github.ref, 'refs/tags/')"
14-
strategy:
15-
fail-fast: false
16-
matrix:
17-
python-version: ["3.10", "3.11", "3.12", "3.13"]
18-
steps:
19-
- uses: actions/checkout@v4
20-
21-
- name: Set up Python ${{ matrix.python-version }}
22-
uses: actions/setup-python@v5
23-
with:
24-
python-version: ${{ matrix.python-version }}
25-
26-
- name: Install Poetry
27-
uses: snok/install-poetry@v1
28-
with:
29-
virtualenvs-create: true
30-
virtualenvs-in-project: true
31-
32-
- name: Install dependencies
33-
run: poetry install --with test --no-interaction --no-ansi
34-
35-
- name: Run tests with coverage
36-
run: poetry run poe tests
37-
38-
- name: Upload coverage reports to Codecov
39-
if: matrix.python-version == '3.13'
40-
uses: codecov/codecov-action@v5
41-
with:
42-
token: ${{ secrets.CODECOV_TOKEN }}
43-
slug: ddc/pythonLogs
44-
45-
build:
46-
name: Build Test Package
47-
runs-on: ubuntu-latest
48-
needs: test
49-
if: "!startsWith(github.ref, 'refs/tags/')"
50-
steps:
51-
- uses: actions/checkout@v4
52-
53-
- name: Set up Python
54-
uses: actions/setup-python@v5
55-
with:
56-
python-version: "3.13"
57-
58-
- name: Install Poetry
59-
uses: snok/install-poetry@v1
60-
with:
61-
virtualenvs-create: true
62-
virtualenvs-in-project: true
63-
64-
- name: Install build dependencies only
65-
run: poetry install --only main --no-interaction --no-ansi
66-
67-
- name: Build package
68-
run: poetry build
69-
70-
- name: Upload artifacts
71-
uses: actions/upload-artifact@v4
72-
with:
73-
name: python-packages
74-
path: dist/
75-
retention-days: 7
76-
7710
release:
7811
name: Build and Release
7912
runs-on: ubuntu-latest

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
[![Support me on GitHub](https://img.shields.io/badge/Support_me_on_GitHub-154c79?style=for-the-badge&logo=github)](https://github.com/sponsors/ddc)
1313

14-
A modern, high-performance Python logging library with automatic file rotation, context manager support, and memory optimization.
14+
High-performance Python logging library with file rotation and optimized caching for better performance
1515

1616

1717
## Table of Contents
@@ -384,25 +384,36 @@ error_logger.error("Database connection failed")
384384
audit_logger.info("User admin logged in")
385385
```
386386

387-
## Env Variables (Optional)
387+
## 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
391+
```python
392+
from pythonLogs import timed_rotating_logger
393+
log = timed_rotating_logger()
394+
```
395+
388396
```
389397
LOG_LEVEL=DEBUG
390-
LOG_TIMEZONE=America/Chicago
398+
LOG_TIMEZONE=UTC
391399
LOG_ENCODING=UTF-8
392400
LOG_APPNAME=app
393401
LOG_FILENAME=app.log
394402
LOG_DIRECTORY=/app/logs
395403
LOG_DAYS_TO_KEEP=30
404+
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
396405
LOG_STREAM_HANDLER=True
397406
LOG_SHOW_LOCATION=False
398-
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
407+
LOG_MAX_LOGGERS=50
408+
LOG_LOGGER_TTL_SECONDS=1800
399409
400410
# SizeRotatingLog
401411
LOG_MAX_FILE_SIZE_MB=10
402412
403413
# TimedRotatingLog
404414
LOG_ROTATE_WHEN=midnight
405415
LOG_ROTATE_AT_UTC=True
416+
LOG_ROTATE_FILE_SUFIX="%Y%m%d"
406417
```
407418

408419

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ build-backend = "poetry.core.masonry.api"
55

66
[tool.poetry]
77
name = "pythonLogs"
8-
version = "4.0.1"
9-
description = "A modern, high-performance Python logging library with automatic file rotation, factory pattern for easy logger creation, and optimized caching for better performance."
8+
version = "4.0.2"
9+
description = "High-performance Python logging library with file rotation and optimized caching for better performance"
1010
license = "MIT"
1111
readme = "README.md"
1212
authors = ["Daniel Costa <[email protected]>"]
@@ -34,6 +34,7 @@ classifiers = [
3434

3535
[tool.poetry.dependencies]
3636
python = "^3.10"
37+
pydantic = "^2.11.7"
3738
pydantic-settings = "^2.10.1"
3839
python-dotenv = "^1.1.1"
3940

@@ -48,6 +49,12 @@ pytest = "^8.4.1"
4849
[tool.poetry.group.test]
4950
optional = true
5051

52+
53+
[tool.black]
54+
line-length = 120
55+
skip-string-normalization = true
56+
57+
5158
[tool.pytest.ini_options]
5259
markers = [
5360
"slow: marks tests as slow (deselect with '-m \"not slow\"')"
@@ -60,6 +67,12 @@ omit = [
6067
]
6168

6269

70+
[tool.coverage.report]
71+
exclude_lines = [
72+
"pragma: no cover",
73+
]
74+
75+
6376
[tool.poe.tasks]
6477
_test = "coverage run -m pytest -v"
6578
_coverage_report = "coverage report"

pythonLogs/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ LOG_APPNAME=app
55
LOG_FILENAME=app.log
66
LOG_DIRECTORY=/app/logs
77
LOG_DAYS_TO_KEEP=30
8+
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
89
LOG_STREAM_HANDLER=True
910
LOG_SHOW_LOCATION=False
10-
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
11+
LOG_MAX_LOGGERS=50
12+
LOG_LOGGER_TTL_SECONDS=1800
1113

1214
# SizeRotatingLog
1315
LOG_MAX_FILE_SIZE_MB=10
1416

1517
# TimedRotatingLog
1618
LOG_ROTATE_WHEN=midnight
1719
LOG_ROTATE_AT_UTC=True
20+
LOG_ROTATE_FILE_SUFIX="%Y%m%d"

pythonLogs/factory.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- encoding: utf-8 -*-
2+
import atexit
23
import logging
34
import threading
45
import time
@@ -28,6 +29,23 @@ class LoggerFactory:
2829
# Memory optimization settings
2930
_max_loggers = 100 # Maximum number of cached loggers
3031
_logger_ttl = 3600 # Logger TTL in seconds (1 hour)
32+
_initialized = False # Flag to track if memory limits have been initialized
33+
_atexit_registered = False # Flag to track if atexit cleanup is registered
34+
35+
@classmethod
36+
def _ensure_initialized(cls) -> None:
37+
"""Ensure memory limits are initialized from settings on first use."""
38+
if not cls._initialized:
39+
from pythonLogs.settings import get_log_settings
40+
settings = get_log_settings()
41+
cls._max_loggers = settings.max_loggers
42+
cls._logger_ttl = settings.logger_ttl_seconds
43+
cls._initialized = True
44+
45+
# Register atexit cleanup on first use
46+
if not cls._atexit_registered:
47+
atexit.register(cls._atexit_cleanup)
48+
cls._atexit_registered = True
3149

3250
@classmethod
3351
def get_or_create_logger(
@@ -54,6 +72,9 @@ def get_or_create_logger(
5472

5573
# Thread-safe check-and-create operation
5674
with cls._registry_lock:
75+
# Initialize memory limits from settings on first use
76+
cls._ensure_initialized()
77+
5778
# Clean up expired loggers first
5879
cls._cleanup_expired_loggers()
5980

@@ -114,7 +135,7 @@ def _enforce_size_limit(cls) -> None:
114135

115136
@classmethod
116137
def set_memory_limits(cls, max_loggers: int = 100, ttl_seconds: int = 3600) -> None:
117-
"""Configure memory management limits for the logger registry.
138+
"""Configure memory management limits for the logger registry at runtime.
118139
119140
Args:
120141
max_loggers: Maximum number of cached loggers
@@ -123,10 +144,20 @@ def set_memory_limits(cls, max_loggers: int = 100, ttl_seconds: int = 3600) -> N
123144
with cls._registry_lock:
124145
cls._max_loggers = max_loggers
125146
cls._logger_ttl = ttl_seconds
147+
cls._initialized = True # Mark as manually configured
126148
# Clean up immediately with new settings
127149
cls._cleanup_expired_loggers()
128150
cls._enforce_size_limit()
129151

152+
@classmethod
153+
def _atexit_cleanup(cls) -> None:
154+
"""Cleanup function registered with atexit to ensure proper resource cleanup."""
155+
try:
156+
cls.clear_registry()
157+
except Exception:
158+
# Silently ignore exceptions during shutdown cleanup
159+
pass
160+
130161
@staticmethod
131162
def _cleanup_logger(logger: logging.Logger) -> None:
132163
"""Clean up logger resources by closing all handlers."""

pythonLogs/settings.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ class LogSettings(BaseSettings):
2323
"""If any ENV variable is omitted, it falls back to default values here"""
2424

2525
level: Optional[LogLevel] = Field(default=LogLevel.INFO)
26+
timezone: Optional[str] = Field(default=DEFAULT_TIMEZONE)
27+
encoding: Optional[str] = Field(default=DEFAULT_ENCODING)
2628
appname: Optional[str] = Field(default="app")
27-
directory: Optional[str] = Field(default="/app/logs")
2829
filename: Optional[str] = Field(default="app.log")
29-
encoding: Optional[str] = Field(default=DEFAULT_ENCODING)
30-
date_format: Optional[str] = Field(default=DEFAULT_DATE_FORMAT)
30+
directory: Optional[str] = Field(default="/app/logs")
3131
days_to_keep: Optional[int] = Field(default=DEFAULT_BACKUP_COUNT)
32-
timezone: Optional[str] = Field(default=DEFAULT_TIMEZONE)
32+
date_format: Optional[str] = Field(default=DEFAULT_DATE_FORMAT)
3333
stream_handler: Optional[bool] = Field(default=True)
3434
show_location: Optional[bool] = Field(default=False)
35+
# Memory management
36+
max_loggers: Optional[int] = Field(default=100)
37+
logger_ttl_seconds: Optional[int] = Field(default=3600)
3538

3639
# SizeRotatingLog
3740
max_file_size_mb: Optional[int] = Field(default=10)

0 commit comments

Comments
 (0)