Skip to content

Commit 3a644aa

Browse files
committed
[core] added logging options to root command
1 parent 8e4e247 commit 3a644aa

File tree

2 files changed

+192
-5
lines changed

2 files changed

+192
-5
lines changed

src/instrumentman/__init__.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
from pathlib import Path
2+
from logging import getLogger
3+
14
from click_extra import extra_group, version_option
25

36
try:
47
from ._version import __version__ as __version__
58
except Exception:
69
__version__ = "0.0.0" # Placeholder value for source installs
710

11+
from .utils import (
12+
logging_option_group,
13+
logging_levels_constraint,
14+
logging_output_constraint,
15+
logging_target_constraint,
16+
logging_rotation_constraint,
17+
configure_logging
18+
)
819
from . import morse
920
from . import terminal
1021
from . import setup
@@ -20,10 +31,37 @@
2031

2132
@extra_group("iman", params=None) # type: ignore[misc]
2233
@version_option()
23-
def cli() -> None:
34+
@logging_option_group()
35+
@logging_levels_constraint()
36+
@logging_output_constraint()
37+
@logging_target_constraint()
38+
@logging_rotation_constraint()
39+
def cli(
40+
debug: bool = False,
41+
info: bool = False,
42+
warning: bool = False,
43+
error: bool = False,
44+
file: Path | None = None,
45+
stdout: bool = False,
46+
stderr: bool = False,
47+
format: str = "{message}",
48+
dateformat: str = "%Y-%m-%d %H:%M:%S",
49+
rotate: tuple[int, int] | None = None
50+
) -> None:
2451
"""Automated measurement programs and related utilities for surveying
2552
instruments."""
26-
pass
53+
configure_logging(
54+
debug,
55+
info,
56+
warning,
57+
error,
58+
file,
59+
stderr,
60+
stdout,
61+
format,
62+
dateformat,
63+
rotate
64+
)
2765

2866

2967
@cli.group("measure") # type: ignore[misc]

src/instrumentman/utils.py

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
from logging import DEBUG, ERROR, INFO, WARNING, Logger
1+
from logging import (
2+
DEBUG,
3+
ERROR,
4+
INFO,
5+
WARNING,
6+
NOTSET,
7+
Logger,
8+
StreamHandler,
9+
basicConfig,
10+
Handler
11+
)
12+
from sys import stdout, stderr
13+
from logging.handlers import RotatingFileHandler
214
import os
315
from typing import Any, Callable, cast, TypeVar
416
from re import compile
17+
from pathlib import Path
518

619
from click_extra import (
720
Color,
@@ -12,11 +25,20 @@
1225
argument,
1326
Choice,
1427
IntRange,
28+
file_path,
1529
ParamType,
1630
Context,
1731
Parameter
1832
)
19-
from cloup.constraints import mutually_exclusive
33+
from cloup.constraints import (
34+
ErrorFmt,
35+
constraint,
36+
mutually_exclusive,
37+
require_one,
38+
require_all,
39+
If,
40+
AnySet
41+
)
2042

2143

2244
F = TypeVar('F', bound=Callable[..., Any])
@@ -126,7 +148,78 @@ def logging_option_group() -> Callable[[F], F]:
126148
"--error",
127149
is_flag=True
128150
),
129-
constraint=mutually_exclusive
151+
option(
152+
"--file",
153+
help="log to file",
154+
type=file_path(readable=False)
155+
),
156+
option(
157+
"--stdout",
158+
help="log to standard output",
159+
is_flag=True
160+
),
161+
option(
162+
"--stderr",
163+
help="log to standard error",
164+
is_flag=True
165+
),
166+
option(
167+
"--format",
168+
help=(
169+
"logging format string (as accepted by the `logging` package "
170+
"in '{' style)"
171+
),
172+
type=str,
173+
default="{asctime} <{name}> [{levelname}] {message}"
174+
),
175+
option(
176+
"--dateformat",
177+
help="date-time format spec (as accepted by `strftime`)",
178+
type=str,
179+
default="%Y-%m-%d %H:%M:%S"
180+
),
181+
option(
182+
"--rotate",
183+
help=(
184+
"number of backup log files to rotate, and maximum size "
185+
"(in bytes) of a log file before rotation"
186+
),
187+
type=(IntRange(1), IntRange(1))
188+
)
189+
)
190+
191+
192+
def logging_levels_constraint() -> Callable[[F], F]:
193+
return constraint(
194+
mutually_exclusive,
195+
["debug", "info", "warning", "error"]
196+
)
197+
198+
199+
def logging_output_constraint() -> Callable[[F], F]:
200+
return constraint(
201+
If(AnySet("file", "stdout", "stderr"), require_one),
202+
["debug", "info", "warning", "error"]
203+
)
204+
205+
206+
def logging_target_constraint() -> Callable[[F], F]:
207+
return constraint(
208+
If(AnySet("debug", "info", "warning", "error"), require_one),
209+
["file", "stdout", "stderr"]
210+
)
211+
212+
213+
def logging_rotation_constraint() -> Callable[[F], F]:
214+
return constraint(
215+
If("rotate", require_all).rephrased(
216+
help="required if --rotate is set",
217+
error=(
218+
"when --rotate is set, the following parameter must also be "
219+
f"set:\n{ErrorFmt.param_list}"
220+
)
221+
),
222+
["file"]
130223
)
131224

132225

@@ -200,6 +293,62 @@ def make_directory(filepath: str) -> None:
200293
os.makedirs(dirname, exist_ok=True)
201294

202295

296+
def configure_logging(
297+
debug: bool = False,
298+
info: bool = False,
299+
warning: bool = False,
300+
error: bool = False,
301+
to_path: Path | None = None,
302+
to_stdout: bool = False,
303+
to_stderr: bool = False,
304+
format: str = "{message}",
305+
dateformat: str = "%Y-%m-%d %H:%M:%S",
306+
rotate: tuple[int, int] | None = None
307+
) -> None:
308+
if not any((debug, info, warning, error)):
309+
return
310+
311+
level = NOTSET
312+
if debug:
313+
level = DEBUG
314+
elif info:
315+
level = INFO
316+
elif warning:
317+
level = WARNING
318+
elif error:
319+
level = ERROR
320+
321+
handlers: list[Handler] = []
322+
if to_path is not None:
323+
max_size = 0
324+
backups = 0
325+
if rotate is not None:
326+
backups, max_size = rotate
327+
328+
handlers.append(
329+
RotatingFileHandler(
330+
to_path,
331+
encoding="utf8",
332+
maxBytes=max_size,
333+
backupCount=backups
334+
)
335+
)
336+
337+
if to_stdout:
338+
handlers.append(StreamHandler(stdout))
339+
340+
if to_stderr:
341+
handlers.append(StreamHandler(stderr))
342+
343+
basicConfig(
344+
format=format,
345+
datefmt=dateformat,
346+
style="{",
347+
level=level,
348+
handlers=handlers
349+
)
350+
351+
203352
def make_logger(
204353
name: str,
205354
debug: bool = False,

0 commit comments

Comments
 (0)