Skip to content

Commit a198a42

Browse files
committed
update log.py, uses pyproject.toml by default
1 parent 03972dc commit a198a42

File tree

4 files changed

+135
-105
lines changed

4 files changed

+135
-105
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 0.9.0
4+
* [Feature] Updated `log.py` module in template
5+
* [API Change] `pkgmt new` uses `pyproject.toml` by default
6+
37
## 0.8.4dev
48

59
* [Feature] Add aliased group to `cli.py`

src/pkgmt/assets/template/src/package_name/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def hello(name):
7777
def log(name):
7878
"""Log a message"""
7979
# flake8: noqa
80-
from $package_name.log import configure_file_and_print_logger, get_logger
80+
from $package_name.log import configure_multi_renderer_logger, get_logger
8181

82-
configure_file_and_print_logger()
82+
configure_multi_renderer_logger()
8383
logger = get_logger()
8484
logger.info(f"Hello, {name}!", name=name)
8585

src/pkgmt/assets/template/src/package_name/log.py

Lines changed: 124 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,148 @@
1-
"""
2-
A sample logger (requires structlog, tested with structlog==25.1.0)
3-
4-
Usage
5-
-----
6-
7-
>>> from $package_name.log import configure_file_and_print_logger, get_logger
8-
>>> configure_file_and_print_logger("app.log")
9-
>>> # OR
10-
>>> configure_print_logger()
11-
>>> logger = get_logger()
12-
>>> logger.info("Hello, world!")
13-
"""
14-
151
import logging
16-
from typing import Any, TextIO
17-
import os
2+
from typing import Any
183
from pathlib import Path
19-
4+
from copy import deepcopy
205

216
import structlog
227
from structlog import WriteLogger, PrintLogger
238

249

25-
class CustomLogger:
10+
class MultiRendererLoggerFactory:
11+
def __init__(
12+
self,
13+
*,
14+
console_processors: list,
15+
json_processors: list,
16+
console_renderer: Any,
17+
json_renderer: Any,
18+
console_logger: Any,
19+
json_logger: Any,
20+
):
21+
self._console_processors = console_processors
22+
self._json_processors = json_processors
23+
self._console_renderer = console_renderer
24+
self._json_renderer = json_renderer
25+
self._console_logger = console_logger
26+
self._json_logger = json_logger
27+
28+
def __call__(self, *args: Any) -> "MultiRendererLogger":
29+
return MultiRendererLogger(
30+
console_processors=self._console_processors,
31+
json_processors=self._json_processors,
32+
console_renderer=self._console_renderer,
33+
json_renderer=self._json_renderer,
34+
console_logger=self._console_logger,
35+
json_logger=self._json_logger,
36+
)
37+
38+
39+
class MultiRendererLogger:
2640
"""
27-
A custom logger that writes to a file and prints to the console
41+
A custom logger that writes JSON to a file and pretty prints to the console
2842
"""
2943

30-
def __init__(self, file: TextIO | None = None):
31-
self._file = file
32-
self._write_logger = WriteLogger(self._file)
33-
self._print_logger = PrintLogger()
34-
35-
def msg(self, message: str) -> None:
36-
self._write_logger.msg(message)
37-
self._print_logger.msg(message)
44+
def __init__(
45+
self,
46+
*,
47+
console_processors: list,
48+
json_processors: list,
49+
console_renderer: Any,
50+
json_renderer: Any,
51+
console_logger: Any,
52+
json_logger: Any,
53+
):
54+
self._console_processors = console_processors
55+
self._json_processors = json_processors
56+
self._console_renderer = console_renderer
57+
self._json_renderer = json_renderer
58+
self._console_logger = console_logger
59+
self._json_logger = json_logger
60+
61+
def msg(self, **kwargs) -> None:
62+
kwargs_json = deepcopy(kwargs)
63+
kwargs_console = deepcopy(kwargs)
64+
65+
for processor in self._console_processors:
66+
processor(self, "logger", kwargs_console)
67+
68+
for processor in self._json_processors:
69+
processor(self, "logger", kwargs_json)
70+
71+
message_console = self._console_renderer(self, "logger", kwargs_console)
72+
message_json = self._json_renderer(self, "logger", kwargs_json)
73+
74+
self._console_logger.msg(message_console)
75+
self._json_logger.msg(message_json)
3876

3977
log = debug = info = warn = warning = msg
4078
fatal = failure = err = error = critical = exception = msg
4179

4280

43-
class CustomLoggerFactory:
44-
def __init__(self, file: TextIO | None = None):
45-
self._file = file
46-
47-
def __call__(self, *args: Any) -> CustomLogger:
48-
return CustomLogger(self._file)
81+
def configure_multi_renderer_logger(
82+
file_path: str = "app.log",
83+
level: int = logging.INFO,
84+
colors: bool = True,
85+
) -> None:
86+
"""
87+
Configure a logger that writes JSON formatted logs to a file while using
88+
the console renderer for terminal output.
4989
90+
Parameters
91+
----------
92+
file_path : str, optional
93+
Path to the log file, by default "app.log"
94+
"""
95+
processors = [
96+
structlog.contextvars.merge_contextvars,
97+
structlog.processors.add_log_level,
98+
structlog.processors.StackInfoRenderer(),
99+
# to add filename, function name, and line number to the log record
100+
structlog.processors.CallsiteParameterAdder(
101+
[
102+
structlog.processors.CallsiteParameter.FILENAME,
103+
structlog.processors.CallsiteParameter.FUNC_NAME,
104+
structlog.processors.CallsiteParameter.LINENO,
105+
],
106+
),
107+
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
108+
# NOTE: we don't pass a renderer here, since we'll use a different
109+
# renderer for the console and the file
110+
]
111+
112+
json_processors = [
113+
# dict tracebacks renders tracebacks as a dict
114+
# structlog.processors.dict_tracebacks,
115+
# format exc info renders tracebacks as a string
116+
structlog.processors.format_exc_info,
117+
]
118+
119+
console_processors = []
120+
121+
if colors:
122+
console_processors.append(structlog.dev.set_exc_info)
123+
else:
124+
console_processors.append(structlog.processors.format_exc_info)
50125

51-
def configure_file_and_print_logger(file_path: str = "app.log") -> None:
52126
structlog.configure(
53-
processors=[
54-
structlog.contextvars.merge_contextvars,
55-
structlog.processors.add_log_level,
56-
structlog.processors.StackInfoRenderer(),
57-
structlog.dev.set_exc_info,
58-
# to add filename, function name, and line number to the log record
59-
structlog.processors.CallsiteParameterAdder(
60-
[
61-
structlog.processors.CallsiteParameter.FILENAME,
62-
structlog.processors.CallsiteParameter.FUNC_NAME,
63-
structlog.processors.CallsiteParameter.LINENO,
64-
],
65-
),
66-
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
67-
structlog.processors.JSONRenderer(),
68-
],
69-
wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET),
127+
wrapper_class=structlog.make_filtering_bound_logger(level),
70128
context_class=dict,
71-
logger_factory=CustomLoggerFactory(open(file_path, "at")),
72-
cache_logger_on_first_use=False,
73-
)
74-
75-
76-
def configure_file_logger(file_path: str = "app.log") -> None:
77-
structlog.configure(
78-
processors=[
79-
structlog.contextvars.merge_contextvars,
80-
structlog.processors.add_log_level,
81-
structlog.processors.StackInfoRenderer(),
82-
structlog.dev.set_exc_info,
83-
structlog.processors.CallsiteParameterAdder(
84-
[
85-
structlog.processors.CallsiteParameter.FILENAME,
86-
structlog.processors.CallsiteParameter.FUNC_NAME,
87-
structlog.processors.CallsiteParameter.LINENO,
88-
],
129+
logger_factory=MultiRendererLoggerFactory(
130+
console_processors=console_processors,
131+
json_processors=json_processors,
132+
console_renderer=structlog.dev.ConsoleRenderer(
133+
colors=colors,
134+
exception_formatter=structlog.dev.RichTracebackFormatter(
135+
show_locals=False,
136+
max_frames=3,
137+
width=80,
138+
),
89139
),
90-
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
91-
structlog.processors.JSONRenderer(),
92-
],
93-
wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET),
94-
context_class=dict,
95-
logger_factory=structlog.WriteLoggerFactory(file=Path(file_path).open("at")),
96-
cache_logger_on_first_use=False,
97-
)
98-
99-
100-
def configure_print_logger() -> None:
101-
structlog.configure(
102-
processors=[
103-
structlog.contextvars.merge_contextvars,
104-
structlog.processors.add_log_level,
105-
structlog.processors.StackInfoRenderer(),
106-
structlog.dev.set_exc_info,
107-
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
108-
structlog.dev.ConsoleRenderer(),
109-
],
110-
wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET),
111-
context_class=dict,
112-
logger_factory=structlog.PrintLoggerFactory(),
140+
json_renderer=structlog.processors.JSONRenderer(),
141+
json_logger=WriteLogger(Path(file_path).open("at")),
142+
console_logger=PrintLogger(),
143+
),
113144
cache_logger_on_first_use=False,
114-
)
115-
116-
117-
def configure_no_logging() -> None:
118-
structlog.configure(
119-
logger_factory=structlog.PrintLoggerFactory(open(os.devnull, "w")),
145+
processors=processors,
120146
)
121147

122148

src/pkgmt/cli.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ def check_links(only_404):
4343
@cli.command()
4444
@click.argument("name")
4545
@click.option(
46-
"--use-pyproject-toml",
46+
"--use-setup-py",
4747
is_flag=True,
4848
default=False,
49-
help="Use pyproject.toml instead of setup.py",
49+
help="Use setup.py instead of pyproject.toml",
5050
)
51-
def new(name, use_pyproject_toml):
51+
def new(name, use_setup_py):
5252
"""Create new package"""
53-
new_.package(name, use_setup_py=not use_pyproject_toml)
53+
new_.package(name, use_setup_py=use_setup_py)
5454

5555

5656
@cli.command()
@@ -204,7 +204,7 @@ def format(exclude):
204204
"--exclude",
205205
multiple=True,
206206
default=[],
207-
help="Exclude multiple files or dir from the build" "Eg: -e tmp -e tmp/a.py",
207+
help="Exclude multiple files or dir from the buildEg: -e tmp -e tmp/a.py",
208208
)
209209
def lint(files, exclude):
210210
"""Lint .py files and notebooks (.ipynb, .md) with flake8"""

0 commit comments

Comments
 (0)