Skip to content

Commit a3dcf2e

Browse files
committed
feat: v3.0.3
1 parent bbd8aa8 commit a3dcf2e

File tree

10 files changed

+113
-67
lines changed

10 files changed

+113
-67
lines changed

README.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77
[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A//actions-badge.atrox.dev/ddc/ddcLogs/badge?ref=main&style=plastic&label=build&logo=none)](https://actions-badge.atrox.dev/ddc/ddcLogs/goto?ref=main)
88

99

10-
# Logs
11-
+ Parameters for all classes are declared as OPTIONAL falling back to [.env](./ddcLogs/.env.example) file variables
12-
+ Default settings can be checked here: [settings.py](ddcLogs/settings.py)
1310

11+
# Logs
12+
+ Parameters for all classes are declared as OPTIONAL
13+
+ If any [.env](./ddcLogs/.env.example) variable is omitted, it falls back to default values here: [settings.py](ddcLogs/settings.py)
14+
+ Timezone parameter can also accept `localtime`, default to `UTC`
15+
+ This parameter is only to display the timezone datetime inside the log file
16+
+ For timed rotation, only UTC and localtime are supported, meaning it will rotate at UTC or localtime
17+
+ env variable to change between UTC and localtime is `LOG_ROLL_OVER_AT_UTC` and default to True
1418

1519

1620

@@ -31,12 +35,13 @@ logger = BasicLog(
3135
appname = "app",
3236
encoding = "UTF-8",
3337
datefmt = "%Y-%m-%dT%H:%M:%S",
34-
utc = True,
38+
timezone = "America/Sao_Paulo",
3539
showlocation = False, # This will show the filename and the line number where the message originated
3640
).init()
3741
logger.warning("This is a warning example")
3842
```
39-
43+
#### Example of output
44+
`[2024-10-08T19:08:56.918-0300]:[WARNING]:[app]:This is a warning example`
4045

4146

4247
# SizeRotatingLog
@@ -55,13 +60,14 @@ logger = SizeRotatingLog(
5560
daystokeep = 7,
5661
encoding = "UTF-8",
5762
datefmt = "%Y-%m-%dT%H:%M:%S",
58-
utc = True,
63+
timezone = "America/Chicago",
5964
streamhandler = True, # Add stream handler along with file handler
6065
showlocation = False # This will show the filename and the line number where the message originated
6166
).init()
6267
logger.warning("This is a warning example")
6368
```
64-
69+
#### Example of output
70+
`[2024-10-08T19:08:56.918-0500]:[WARNING]:[app]:This is a warning example`
6571

6672

6773

@@ -85,19 +91,17 @@ logger = TimedRotatingLog(
8591
daystokeep = 7,
8692
encoding = "UTF-8",
8793
datefmt = "%Y-%m-%dT%H:%M:%S",
88-
utc = True,
94+
timezone = "UTC",
8995
streamhandler = True, # Add stream handler along with file handler
9096
showlocation = False # This will show the filename and the line number where the message originated
9197
).init()
9298
logger.warning("This is a warning example")
9399
```
100+
#### Example of output
101+
`[2024-10-08T19:08:56.918-0000]:[WARNING]:[app]:This is a warning example`
94102

95103

96104

97-
### Example of output
98-
`[2024-10-08T19:08:56.918]:[WARNING]:[app]:This is a warning example`
99-
100-
101105

102106
# Source Code
103107
### Build
@@ -120,6 +124,7 @@ Released under the [MIT License](LICENSE)
120124

121125

122126

127+
123128
# Buy me a cup of coffee
124129
+ [GitHub Sponsor](https://github.com/sponsors/ddc)
125130
+ [ko-fi](https://ko-fi.com/ddcsta)

ddcLogs/.env.example

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
LOG_LEVEL=DEBUG
2-
LOG_UTC=True
2+
LOG_TIMEZONE=UTC
33
LOG_ENCODING=UTF-8
44
LOG_APPNAME=app
55
LOG_FILENAME=app.log
6-
LOG_DIRECTORY=./logs
6+
LOG_DIRECTORY=/app/logs
77
LOG_DAYS_TO_KEEP=30
88
LOG_STREAM_HANDLER=True
99
LOG_SHOW_LOCATION=False
1010
LOG_DATE_FORMAT=%Y-%m-%dT%H:%M:%S
1111

12-
# TimedRotatingLog
13-
LOG_ROTATING_WHEN=midnight
14-
LOG_ROTATING_FILE_SUFIX=%Y%m%d
15-
1612
# SizeRotatingLog
1713
LOG_MAX_FILE_SIZE_MB=10
14+
15+
# TimedRotatingLog
16+
LOG_ROTATE_WHEN=midnight
17+
LOG_ROTATE_AT_UTC=True
18+
LOG_ROTATE_FILE_SUFIX=%Y%m%d

ddcLogs/basic_log.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# -*- encoding: utf-8 -*-
22
import logging
3-
import time
43
from typing import Optional
5-
from .log_utils import get_format, get_level
6-
from .settings import LogSettings
4+
from ddcLogs.log_utils import (
5+
get_format,
6+
get_level,
7+
get_timezone,
8+
)
9+
from ddcLogs.settings import LogSettings
710

811

912
class BasicLog:
@@ -13,26 +16,23 @@ def __init__(
1316
appname: Optional[str] = None,
1417
encoding: Optional[str] = None,
1518
datefmt: Optional[str] = None,
16-
utc: Optional[bool] = None,
19+
timezone: Optional[str] = None,
1720
showlocation: Optional[bool] = None,
1821
):
1922
_settings = LogSettings()
2023
self.level = get_level(level or _settings.level)
2124
self.appname = appname or _settings.appname
2225
self.encoding = encoding or _settings.encoding
2326
self.datefmt = datefmt or _settings.date_format
24-
self.utc = utc or _settings.utc
27+
self.timezone = timezone or _settings.timezone
2528
self.showlocation = showlocation or _settings.show_location
2629

2730
def init(self):
28-
if self.utc:
29-
logging.Formatter.converter = time.gmtime
30-
31-
formatt = get_format(self.showlocation, self.appname)
32-
logging.basicConfig(level=self.level,
33-
datefmt=self.datefmt,
34-
encoding=self.encoding,
35-
format=formatt)
3631
logger = logging.getLogger(self.appname)
3732
logger.setLevel(self.level)
33+
logging.Formatter.converter = get_timezone(self.timezone)
34+
_format = get_format(self.showlocation, self.appname, self.timezone)
35+
logging.basicConfig(datefmt=self.datefmt,
36+
encoding=self.encoding,
37+
format=_format)
3838
return logger

ddcLogs/log_utils.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import sys
88
import time
99
from datetime import datetime, timedelta
10+
from time import struct_time
11+
from typing import Any, Callable
12+
import pytz
1013

1114

1215
def get_stream_handler(
@@ -24,17 +27,16 @@ def get_logger_and_formatter(
2427
name: str,
2528
datefmt: str,
2629
show_location: bool,
27-
utc: bool,
30+
timezone: str,
2831
) -> [logging.Logger, logging.Formatter]:
2932

3033
logger = logging.getLogger(name)
3134
for handler in logger.handlers[:]:
3235
logger.removeHandler(handler)
3336

34-
formatt = get_format(show_location, name)
37+
formatt = get_format(show_location, name, timezone)
3538
formatter = logging.Formatter(formatt, datefmt=datefmt)
36-
if utc:
37-
formatter.converter = time.gmtime
39+
formatter.converter = get_timezone(timezone)
3840
return logger, formatter
3941

4042

@@ -79,7 +81,8 @@ def remove_old_logs(logs_dir: str, days_to_keep: int) -> None:
7981

8082
def list_files(directory: str, ends_with: str) -> tuple:
8183
"""
82-
List all files in the given directory and returns them in a list sorted by creation time in ascending order
84+
List all files in the given directory
85+
and returns them in a list sorted by creation time in ascending order
8386
:param directory:
8487
:param ends_with:
8588
:return: tuple
@@ -219,7 +222,7 @@ def get_log_path(directory: str, filename: str) -> str:
219222
return log_file_path
220223

221224

222-
def get_format(show_location: bool, name: str | None) -> str:
225+
def get_format(show_location: bool, name: str, timezone: str) -> str:
223226
_debug_fmt = ""
224227
_logger_name = ""
225228

@@ -229,7 +232,12 @@ def get_format(show_location: bool, name: str | None) -> str:
229232
if show_location:
230233
_debug_fmt = "[%(filename)s:%(funcName)s:%(lineno)d]:"
231234

232-
fmt = f"[%(asctime)s.%(msecs)03d]:[%(levelname)s]:{_logger_name}{_debug_fmt}%(message)s"
235+
if timezone == "localtime":
236+
utc_offset = time.strftime("%z")
237+
else:
238+
utc_offset = datetime.now(pytz.timezone(timezone)).strftime("%z")
239+
240+
fmt = f"[%(asctime)s.%(msecs)03d{utc_offset}]:[%(levelname)s]:{_logger_name}{_debug_fmt}%(message)s"
233241
return fmt
234242

235243

@@ -265,3 +273,15 @@ def gzip_file(source, output_partial_name) -> gzip:
265273
except OSError as e:
266274
write_stderr(f"Unable to delete_file old source log file | {source} | {repr(e)}")
267275
raise e
276+
277+
278+
def get_timezone(time_zone: str) -> (Callable[[float | None, Any], struct_time] |
279+
Callable[[Any], struct_time]):
280+
281+
match time_zone.lower():
282+
case "utc":
283+
return time.gmtime
284+
case "localtime":
285+
return time.localtime
286+
case _:
287+
return lambda *args: datetime.now(tz=pytz.timezone(time_zone)).timetuple()

ddcLogs/settings.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class LogLevel(str, Enum):
1919

2020

2121
class LogSettings(BaseSettings):
22-
""" settings defined here with fallback to reading ENV variables """
22+
""" If any ENV variable is omitted, it falls back to default values here """
2323
load_dotenv()
2424

2525
level: Optional[LogLevel] = Field(default=LogLevel.INFO)
@@ -29,15 +29,16 @@ class LogSettings(BaseSettings):
2929
encoding: Optional[str] = Field(default="UTF-8")
3030
date_format: Optional[str] = Field(default="%Y-%m-%dT%H:%M:%S")
3131
days_to_keep: Optional[int] = Field(default=30)
32-
utc: Optional[bool] = Field(default=True)
32+
timezone: Optional[str] = Field(default="UTC")
3333
stream_handler: Optional[bool] = Field(default=True) # Add stream handler along with file handler
3434
show_location: Optional[bool] = Field(default=False) # This will show the filename and the line number where the message originated
3535

3636
# SizeRotatingLog
3737
max_file_size_mb: Optional[int] = Field(default=10)
3838

3939
# TimedRotatingLog
40-
rotating_when: Optional[str] = Field(default="midnight")
41-
rotating_file_sufix: Optional[str] = Field(default="%Y%m%d")
40+
rotate_when: Optional[str] = Field(default="midnight")
41+
rotate_at_utc: Optional[bool] = Field(default=True)
42+
rotate_file_sufix: Optional[str] = Field(default="%Y%m%d")
4243

4344
model_config = SettingsConfigDict(env_prefix="LOG_", env_file=".env", extra="allow")

ddcLogs/size_rotating.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging.handlers
33
import os
44
from typing import Optional
5-
from .log_utils import (
5+
from ddcLogs.log_utils import (
66
check_directory_permissions,
77
check_filename_instance,
88
get_level, get_log_path,
@@ -12,7 +12,7 @@
1212
list_files,
1313
remove_old_logs, write_stderr,
1414
)
15-
from .settings import LogSettings
15+
from ddcLogs.settings import LogSettings
1616

1717

1818
class SizeRotatingLog:
@@ -26,7 +26,7 @@ def __init__(
2626
daystokeep: Optional[int] = None,
2727
encoding: Optional[str] = None,
2828
datefmt: Optional[str] = None,
29-
utc: Optional[bool] = None,
29+
timezone: Optional[str] = None,
3030
streamhandler: Optional[bool] = None,
3131
showlocation: Optional[bool] = None,
3232
):
@@ -39,7 +39,7 @@ def __init__(
3939
self.daystokeep = daystokeep or _settings.days_to_keep
4040
self.encoding = encoding or _settings.encoding
4141
self.datefmt = datefmt or _settings.date_format
42-
self.utc = utc or _settings.utc
42+
self.timezone = timezone or _settings.timezone
4343
self.streamhandler = streamhandler or _settings.stream_handler
4444
self.showlocation = showlocation or _settings.show_location
4545

@@ -51,7 +51,7 @@ def init(self):
5151
logger, formatter = get_logger_and_formatter(self.appname,
5252
self.datefmt,
5353
self.showlocation,
54-
self.utc)
54+
self.timezone)
5555
logger.setLevel(self.level)
5656

5757
for file in self.filenames:

ddcLogs/timed_rotating.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
import logging.handlers
33
import os
44
from typing import Optional
5-
from .log_utils import (
5+
from ddcLogs.log_utils import (
66
check_directory_permissions,
77
check_filename_instance,
88
get_level,
99
get_log_path,
1010
get_logger_and_formatter,
1111
get_stream_handler,
1212
gzip_file,
13-
remove_old_logs
13+
remove_old_logs,
1414
)
15-
from .settings import LogSettings
15+
from ddcLogs.settings import LogSettings
1616

1717

1818
class TimedRotatingLog:
@@ -33,23 +33,25 @@ def __init__(
3333
daystokeep: Optional[int] = None,
3434
encoding: Optional[str] = None,
3535
datefmt: Optional[str] = None,
36-
utc: Optional[bool] = None,
36+
timezone: Optional[str] = None,
3737
streamhandler: Optional[bool] = None,
3838
showlocation: Optional[bool] = None,
39+
rotateatutc: Optional[bool] = None,
3940
):
4041
_settings = LogSettings()
4142
self.level = get_level(level or _settings.level)
4243
self.appname = appname or _settings.appname
4344
self.directory = directory or _settings.directory
4445
self.filenames = filenames or (_settings.filename,)
45-
self.when = when or _settings.rotating_when
46-
self.sufix = sufix or _settings.rotating_file_sufix
46+
self.when = when or _settings.rotate_when
47+
self.sufix = sufix or _settings.rotate_file_sufix
4748
self.daystokeep = daystokeep or _settings.days_to_keep
4849
self.encoding = encoding or _settings.encoding
4950
self.datefmt = datefmt or _settings.date_format
50-
self.utc = utc or _settings.utc
51+
self.timezone = timezone or _settings.timezone
5152
self.streamhandler = streamhandler or _settings.stream_handler
5253
self.showlocation = showlocation or _settings.show_location
54+
self.rotateatutc = rotateatutc or _settings.rotate_at_utc
5355

5456
def init(self):
5557
check_filename_instance(self.filenames)
@@ -58,7 +60,7 @@ def init(self):
5860
logger, formatter = get_logger_and_formatter(self.appname,
5961
self.datefmt,
6062
self.showlocation,
61-
self.utc)
63+
self.timezone)
6264
logger.setLevel(self.level)
6365

6466
for file in self.filenames:
@@ -68,7 +70,7 @@ def init(self):
6870
filename=log_file_path,
6971
encoding=self.encoding,
7072
when=self.when,
71-
utc=self.utc,
73+
utc=self.rotateatutc,
7274
backupCount=self.daystokeep
7375
)
7476
file_handler.suffix = self.sufix
@@ -86,7 +88,6 @@ def init(self):
8688

8789
return logger
8890

89-
9091
class GZipRotatorTimed:
9192
def __init__(self, dir_logs: str, days_to_keep: int):
9293
self.dir = dir_logs

0 commit comments

Comments
 (0)