Skip to content

Commit 42bc342

Browse files
committed
V5.0.1
1 parent 5a7fc13 commit 42bc342

27 files changed

+441
-261
lines changed

pythonLogs/.env.example

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# pythonLogs Environment Configuration
2+
# Copy this file to .env and modify values as needed
3+
4+
# Basic Logger Settings
15
LOG_LEVEL=DEBUG
26
LOG_TIMEZONE=UTC
37
LOG_ENCODING=UTF-8
@@ -8,13 +12,21 @@ LOG_DAYS_TO_KEEP=30
812
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
913
LOG_STREAM_HANDLER=True
1014
LOG_SHOW_LOCATION=False
15+
16+
# Memory Management Settings
1117
LOG_MAX_LOGGERS=50
1218
LOG_LOGGER_TTL_SECONDS=1800
1319

14-
# SizeRotatingLog
20+
# SizeRotatingLog Settings
1521
LOG_MAX_FILE_SIZE_MB=10
1622

17-
# TimedRotatingLog
23+
# TimedRotatingLog Settings
1824
LOG_ROTATE_WHEN=midnight
1925
LOG_ROTATE_AT_UTC=True
20-
LOG_ROTATE_FILE_SUFIX="%Y%m%d"
26+
LOG_ROTATE_FILE_SUFIX=%Y%m%d
27+
28+
# Available LOG_LEVEL values: DEBUG, INFO, WARNING, ERROR, CRITICAL
29+
# Available LOG_TIMEZONE values: UTC, localtime, or any valid timezone (e.g., America/New_York)
30+
# Available LOG_ROTATE_WHEN values: midnight, S, M, H, D, W0-W6, daily, hourly, weekly
31+
# LOG_STREAM_HANDLER: Set to True to enable console output, False to disable
32+
# LOG_SHOW_LOCATION: Set to True to include filename:function:line in log messages

pythonLogs/__init__.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- encoding: utf-8 -*-
21
import logging
32
from importlib.metadata import version
43
from typing import Literal, NamedTuple
@@ -14,15 +13,15 @@
1413
LoggerType,
1514
shutdown_logger,
1615
size_rotating_logger,
17-
timed_rotating_logger
16+
timed_rotating_logger,
1817
)
1918
from pythonLogs.memory_utils import (
2019
clear_directory_cache,
2120
clear_formatter_cache,
2221
force_garbage_collection,
2322
get_memory_stats,
2423
optimize_lru_cache_sizes,
25-
set_directory_cache_limit
24+
set_directory_cache_limit,
2625
)
2726
from pythonLogs.size_rotating import SizeRotatingLog
2827
from pythonLogs.timed_rotating import TimedRotatingLog
@@ -77,18 +76,14 @@ class VersionInfo(NamedTuple):
7776

7877
__version__ = _version
7978
__version_info__: VersionInfo = VersionInfo(
80-
major=__version__[0],
81-
minor=__version__[1],
82-
micro=__version__[2],
83-
releaselevel="final",
84-
serial=0
79+
major=__version__[0], minor=__version__[1], micro=__version__[2], releaselevel="final", serial=0
8580
)
8681
__req_python_version__: VersionInfo = VersionInfo(
8782
major=_req_python_version[0],
8883
minor=_req_python_version[1],
8984
micro=_req_python_version[2],
9085
releaselevel="final",
91-
serial=0
86+
serial=0,
9287
)
9388

9489
logging.getLogger(__name__).addHandler(logging.NullHandler())

pythonLogs/basic_log.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# -*- encoding: utf-8 -*-
21
import logging
32
from typing import Optional
43
from pythonLogs.log_utils import get_format, get_level, get_timezone_function
5-
from pythonLogs.memory_utils import cleanup_logger_handlers, register_logger_weakref
4+
from pythonLogs.log_utils import cleanup_logger_handlers
5+
from pythonLogs.memory_utils import register_logger_weakref
66
from pythonLogs.settings import get_log_settings
77
from pythonLogs.thread_safety import auto_thread_safe
88

@@ -34,14 +34,14 @@ def init(self):
3434
logger.setLevel(self.level)
3535
logging.Formatter.converter = get_timezone_function(self.timezone)
3636
_format = get_format(self.showlocation, self.appname, self.timezone)
37-
37+
3838
# Only add handler if logger doesn't have any handlers
3939
if not logger.handlers:
4040
handler = logging.StreamHandler()
4141
formatter = logging.Formatter(_format, datefmt=self.datefmt)
4242
handler.setFormatter(formatter)
4343
logger.addHandler(handler)
44-
44+
4545
self.logger = logger
4646
# Register weak reference for memory tracking
4747
register_logger_weakref(logger)

pythonLogs/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- encoding: utf-8 -*-
21
import logging
32
from enum import Enum
43

@@ -20,6 +19,7 @@
2019

2120
class LogLevel(str, Enum):
2221
"""Log levels"""
22+
2323
CRITICAL = "CRITICAL"
2424
CRIT = "CRIT"
2525
ERROR = "ERROR"
@@ -31,6 +31,7 @@ class LogLevel(str, Enum):
3131

3232
class RotateWhen(str, Enum):
3333
"""Rotation timing options for TimedRotatingLog"""
34+
3435
MIDNIGHT = "midnight"
3536
MONDAY = "W0"
3637
TUESDAY = "W1"

pythonLogs/factory.py

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- encoding: utf-8 -*-
21
import atexit
32
import logging
43
import threading
@@ -8,14 +7,16 @@
87
from typing import Dict, Optional, Tuple, Union
98
from pythonLogs.basic_log import BasicLog
109
from pythonLogs.constants import LogLevel, RotateWhen
11-
from pythonLogs.memory_utils import cleanup_logger_handlers
10+
from pythonLogs.log_utils import cleanup_logger_handlers
11+
from pythonLogs.settings import get_log_settings
1212
from pythonLogs.size_rotating import SizeRotatingLog
1313
from pythonLogs.timed_rotating import TimedRotatingLog
1414

1515

1616
@dataclass
1717
class LoggerConfig:
1818
"""Configuration class to group logger parameters"""
19+
1920
level: Optional[Union[LogLevel, str]] = None
2021
name: Optional[str] = None
2122
directory: Optional[str] = None
@@ -34,6 +35,7 @@ class LoggerConfig:
3435

3536
class LoggerType(str, Enum):
3637
"""Available logger types"""
38+
3739
BASIC = "basic"
3840
SIZE_ROTATING = "size_rotating"
3941
TIMED_ROTATING = "timed_rotating"
@@ -56,12 +58,11 @@ class LoggerFactory:
5658
def _ensure_initialized(cls) -> None:
5759
"""Ensure memory limits are initialized from settings on first use."""
5860
if not cls._initialized:
59-
from pythonLogs.settings import get_log_settings
6061
settings = get_log_settings()
6162
cls._max_loggers = settings.max_loggers
6263
cls._logger_ttl = settings.logger_ttl_seconds
6364
cls._initialized = True
64-
65+
6566
# Register atexit cleanup on first use
6667
if not cls._atexit_registered:
6768
atexit.register(cls._atexit_cleanup)
@@ -71,30 +72,30 @@ def _ensure_initialized(cls) -> None:
7172
def get_or_create_logger(
7273
cls,
7374
logger_type: Union[LoggerType, str],
74-
name: Optional[str] = None, **kwargs,
75+
name: Optional[str] = None,
76+
**kwargs,
7577
) -> logging.Logger:
7678
"""
77-
Get an existing logger from registry or create new one.
79+
Get an existing logger from registry or create a new one.
7880
Loggers are cached by name for performance.
79-
81+
8082
Args:
8183
logger_type: Type of logger to create
8284
name: Logger name (used as cache key)
8385
**kwargs: Additional logger configuration
84-
86+
8587
Returns:
8688
Cached or newly created logger instance
8789
"""
8890
# Use the default name if none provided
8991
if name is None:
90-
from pythonLogs.settings import get_log_settings
9192
name = get_log_settings().appname
9293

9394
# Thread-safe check-and-create operation
9495
with cls._registry_lock:
9596
# Initialize memory limits from settings on first use
9697
cls._ensure_initialized()
97-
98+
9899
# Clean up expired loggers first
99100
cls._cleanup_expired_loggers()
100101

@@ -156,7 +157,7 @@ def _enforce_size_limit(cls) -> None:
156157
@classmethod
157158
def set_memory_limits(cls, max_loggers: int = 100, ttl_seconds: int = 3600) -> None:
158159
"""Configure memory management limits for the logger registry at runtime.
159-
160+
160161
Args:
161162
max_loggers: Maximum number of cached loggers
162163
ttl_seconds: Time-to-live for cached loggers in seconds
@@ -177,7 +178,7 @@ def _atexit_cleanup(cls) -> None:
177178
except Exception:
178179
# Silently ignore exceptions during shutdown cleanup
179180
pass
180-
181+
181182
@staticmethod
182183
def _cleanup_logger(logger: logging.Logger) -> None:
183184
"""Clean up logger resources by closing all handlers."""
@@ -186,10 +187,10 @@ def _cleanup_logger(logger: logging.Logger) -> None:
186187
@classmethod
187188
def shutdown_logger(cls, name: str) -> bool:
188189
"""Shutdown and remove a specific logger from registry.
189-
190+
190191
Args:
191192
name: Logger name to shut down
192-
193+
193194
Returns:
194195
True if logger was found and shutdown, False otherwise
195196
"""
@@ -206,24 +207,34 @@ def get_registered_loggers(cls) -> dict[str, logging.Logger]:
206207
with cls._registry_lock:
207208
return {name: logger for name, (logger, _) in cls._logger_registry.items()}
208209

210+
@classmethod
211+
def get_memory_limits(cls) -> dict[str, int]:
212+
"""Get current memory management limits.
213+
214+
Returns:
215+
Dictionary with current max_loggers and ttl_seconds settings
216+
"""
217+
with cls._registry_lock:
218+
return {
219+
'max_loggers': cls._max_loggers,
220+
'ttl_seconds': cls._logger_ttl
221+
}
222+
209223
@staticmethod
210224
def create_logger(
211-
logger_type: Union[LoggerType, str],
212-
config: Optional[LoggerConfig] = None,
213-
**kwargs
225+
logger_type: Union[LoggerType, str], config: Optional[LoggerConfig] = None, **kwargs
214226
) -> logging.Logger:
215-
216227
"""
217228
Factory method to create loggers based on type.
218-
229+
219230
Args:
220231
logger_type: Type of logger to create (LoggerType enum or string)
221232
config: LoggerConfig object with logger parameters
222233
**kwargs: Individual logger parameters (for backward compatibility)
223-
234+
224235
Returns:
225236
Configured logger instance
226-
237+
227238
Raises:
228239
ValueError: If invalid logger_type is provided
229240
"""
@@ -237,7 +248,7 @@ def create_logger(
237248
# Merge config and kwargs (kwargs take precedence for backward compatibility)
238249
if config is None:
239250
config = LoggerConfig()
240-
251+
241252
# Create a new config with kwargs overriding config values
242253
final_config = LoggerConfig(
243254
level=kwargs.get('level', config.level),
@@ -253,7 +264,7 @@ def create_logger(
253264
when=kwargs.get('when', config.when),
254265
sufix=kwargs.get('sufix', config.sufix),
255266
rotateatutc=kwargs.get('rotateatutc', config.rotateatutc),
256-
daystokeep=kwargs.get('daystokeep', config.daystokeep)
267+
daystokeep=kwargs.get('daystokeep', config.daystokeep),
257268
)
258269

259270
# Convert enum values to strings for logger classes
@@ -269,7 +280,8 @@ def create_logger(
269280
encoding=final_config.encoding,
270281
datefmt=final_config.datefmt,
271282
timezone=final_config.timezone,
272-
showlocation=final_config.showlocation, )
283+
showlocation=final_config.showlocation,
284+
)
273285

274286
case LoggerType.SIZE_ROTATING:
275287
logger_instance = SizeRotatingLog(
@@ -283,7 +295,8 @@ def create_logger(
283295
datefmt=final_config.datefmt,
284296
timezone=final_config.timezone,
285297
streamhandler=final_config.streamhandler,
286-
showlocation=final_config.showlocation, )
298+
showlocation=final_config.showlocation,
299+
)
287300

288301
case LoggerType.TIMED_ROTATING:
289302
logger_instance = TimedRotatingLog(
@@ -299,7 +312,8 @@ def create_logger(
299312
timezone=final_config.timezone,
300313
streamhandler=final_config.streamhandler,
301314
showlocation=final_config.showlocation,
302-
rotateatutc=final_config.rotateatutc, )
315+
rotateatutc=final_config.rotateatutc,
316+
)
303317

304318
case _:
305319
raise ValueError(f"Unsupported logger type: {logger_type}")
@@ -315,7 +329,6 @@ def create_basic_logger(
315329
timezone: Optional[str] = None,
316330
showlocation: Optional[bool] = None,
317331
) -> logging.Logger:
318-
319332
"""Convenience method for creating a basic logger"""
320333
return LoggerFactory.create_logger(
321334
LoggerType.BASIC,
@@ -324,7 +337,8 @@ def create_basic_logger(
324337
encoding=encoding,
325338
datefmt=datefmt,
326339
timezone=timezone,
327-
showlocation=showlocation, )
340+
showlocation=showlocation,
341+
)
328342

329343
@staticmethod
330344
def create_size_rotating_logger(
@@ -340,7 +354,6 @@ def create_size_rotating_logger(
340354
streamhandler: Optional[bool] = None,
341355
showlocation: Optional[bool] = None,
342356
) -> logging.Logger:
343-
344357
"""Convenience method for creating a size rotating logger"""
345358
return LoggerFactory.create_logger(
346359
LoggerType.SIZE_ROTATING,
@@ -354,7 +367,8 @@ def create_size_rotating_logger(
354367
datefmt=datefmt,
355368
timezone=timezone,
356369
streamhandler=streamhandler,
357-
showlocation=showlocation, )
370+
showlocation=showlocation,
371+
)
358372

359373
@staticmethod
360374
def create_timed_rotating_logger(
@@ -365,14 +379,13 @@ def create_timed_rotating_logger(
365379
when: Optional[Union[RotateWhen, str]] = None,
366380
sufix: Optional[str] = None,
367381
daystokeep: Optional[int] = None,
368-
encoding:Optional[str] = None,
382+
encoding: Optional[str] = None,
369383
datefmt: Optional[str] = None,
370384
timezone: Optional[str] = None,
371385
streamhandler: Optional[bool] = None,
372386
showlocation: Optional[bool] = None,
373387
rotateatutc: Optional[bool] = None,
374388
) -> logging.Logger:
375-
376389
"""Convenience method for creating a timed rotating logger"""
377390
return LoggerFactory.create_logger(
378391
LoggerType.TIMED_ROTATING,
@@ -388,7 +401,8 @@ def create_timed_rotating_logger(
388401
timezone=timezone,
389402
streamhandler=streamhandler,
390403
showlocation=showlocation,
391-
rotateatutc=rotateatutc, )
404+
rotateatutc=rotateatutc,
405+
)
392406

393407

394408
# Convenience functions for backward compatibility and easier usage

0 commit comments

Comments
 (0)