click_logging_config 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- click_logging_config/VERSION +1 -0
- click_logging_config/__init__.py +5 -0
- click_logging_config/_click.py +143 -0
- click_logging_config/_default_values.py +26 -0
- click_logging_config/_logging.py +207 -0
- click_logging_config/_version.py +38 -0
- click_logging_config/parameters.py +13 -0
- click_logging_config/py.typed +0 -0
- click_logging_config-2.0.2.dist-info/METADATA +236 -0
- click_logging_config-2.0.2.dist-info/RECORD +12 -0
- click_logging_config-2.0.2.dist-info/WHEEL +4 -0
- click_logging_config-2.0.2.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.0.2
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2022 Russell Smiley
|
|
3
|
+
#
|
|
4
|
+
# This file is part of click_logging_config.
|
|
5
|
+
#
|
|
6
|
+
# You should have received a copy of the MIT License along with build_harness.
|
|
7
|
+
# If not, see <https://opensource.org/licenses/MIT>.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
import copy
|
|
11
|
+
import functools
|
|
12
|
+
import logging
|
|
13
|
+
import pathlib
|
|
14
|
+
import typing
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
from click.decorators import FC
|
|
18
|
+
|
|
19
|
+
from ._default_values import VALID_LOG_LEVELS
|
|
20
|
+
from ._logging import LoggingConfiguration, LoggingState
|
|
21
|
+
from ._version import __version__
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
LOG_STATE_KEY = "logging_state"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def logging_parameters(
|
|
29
|
+
default_configuration: typing.Optional[LoggingConfiguration] = None,
|
|
30
|
+
) -> typing.Union[typing.Callable[..., typing.Any], click.Command]:
|
|
31
|
+
"""Define a set of logging configuration options to a ``click`` command.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
default_configuration: User defined logging default configuration.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The decorator function object.
|
|
38
|
+
"""
|
|
39
|
+
resolved_configuration: LoggingConfiguration
|
|
40
|
+
if not default_configuration:
|
|
41
|
+
resolved_configuration = LoggingConfiguration()
|
|
42
|
+
else:
|
|
43
|
+
resolved_configuration = typing.cast(
|
|
44
|
+
LoggingConfiguration, default_configuration
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def decorator(f: FC) -> FC:
|
|
48
|
+
@click.option(
|
|
49
|
+
"--log-console-enable/--log-console-disable",
|
|
50
|
+
"enable_console_log",
|
|
51
|
+
default=resolved_configuration.enable_console_logging,
|
|
52
|
+
help="Enable or disable console logging.",
|
|
53
|
+
is_flag=True,
|
|
54
|
+
show_default=True,
|
|
55
|
+
)
|
|
56
|
+
@click.option(
|
|
57
|
+
"--log-console-json-enable/--log-console-json-disable",
|
|
58
|
+
"enable_console_json",
|
|
59
|
+
default=resolved_configuration.console_logging.json_enabled,
|
|
60
|
+
help="Enable or disable console JSON logging.",
|
|
61
|
+
is_flag=True,
|
|
62
|
+
show_default=True,
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--log-file-enable/--log-file-disable",
|
|
66
|
+
"enable_file_log",
|
|
67
|
+
default=resolved_configuration.enable_file_logging,
|
|
68
|
+
help="Enable or disable file logging.",
|
|
69
|
+
is_flag=True,
|
|
70
|
+
show_default=True,
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"--log-file-json-enable/--log-file-json-disable",
|
|
74
|
+
"enable_file_json",
|
|
75
|
+
default=resolved_configuration.file_logging.json_enabled,
|
|
76
|
+
help="Enable or disable file JSON logging.",
|
|
77
|
+
is_flag=True,
|
|
78
|
+
show_default=True,
|
|
79
|
+
)
|
|
80
|
+
@click.option(
|
|
81
|
+
"--log-file",
|
|
82
|
+
"log_file",
|
|
83
|
+
default=resolved_configuration.file_logging.log_file_path,
|
|
84
|
+
help="The log file to write to.",
|
|
85
|
+
is_eager=True,
|
|
86
|
+
show_default=True,
|
|
87
|
+
type=click.Path(
|
|
88
|
+
dir_okay=False,
|
|
89
|
+
exists=False,
|
|
90
|
+
file_okay=True,
|
|
91
|
+
path_type=pathlib.Path,
|
|
92
|
+
writable=True,
|
|
93
|
+
readable=True,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"--log-level",
|
|
98
|
+
"log_level",
|
|
99
|
+
default=resolved_configuration.log_level,
|
|
100
|
+
help="Select logging level to apply to all enabled log sinks.",
|
|
101
|
+
show_default=True,
|
|
102
|
+
type=click.Choice(VALID_LOG_LEVELS, case_sensitive=False),
|
|
103
|
+
)
|
|
104
|
+
def wrapper(
|
|
105
|
+
*args: typing.Any,
|
|
106
|
+
enable_console_log: bool,
|
|
107
|
+
enable_console_json: bool,
|
|
108
|
+
enable_file_log: bool,
|
|
109
|
+
enable_file_json: bool,
|
|
110
|
+
log_file: pathlib.Path,
|
|
111
|
+
log_level: str,
|
|
112
|
+
**kwargs: typing.Any,
|
|
113
|
+
) -> typing.Any:
|
|
114
|
+
ctx = click.get_current_context()
|
|
115
|
+
this_object = ctx.ensure_object(dict)
|
|
116
|
+
if LOG_STATE_KEY not in this_object:
|
|
117
|
+
this_configuration = copy.deepcopy(resolved_configuration)
|
|
118
|
+
this_configuration.enable_console_logging = enable_console_log
|
|
119
|
+
this_configuration.console_logging.json_enabled = (
|
|
120
|
+
enable_console_json
|
|
121
|
+
)
|
|
122
|
+
this_configuration.enable_file_logging = enable_file_log
|
|
123
|
+
this_configuration.file_logging.json_enabled = enable_file_json
|
|
124
|
+
this_configuration.file_logging.log_file_path = log_file
|
|
125
|
+
this_configuration.log_level = log_level
|
|
126
|
+
|
|
127
|
+
this_object[LOG_STATE_KEY] = LoggingState(this_configuration)
|
|
128
|
+
log.info(f"Click logging config version, {__version__}")
|
|
129
|
+
elif not isinstance(this_object, dict):
|
|
130
|
+
raise RuntimeError(
|
|
131
|
+
"Unable to define logging state since click context.obj is "
|
|
132
|
+
"not a dictionary"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return ctx.invoke(
|
|
136
|
+
f,
|
|
137
|
+
*args,
|
|
138
|
+
**kwargs,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return typing.cast(FC, functools.update_wrapper(wrapper, f))
|
|
142
|
+
|
|
143
|
+
return decorator
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright (c) 2020 Russell Smiley
|
|
2
|
+
#
|
|
3
|
+
# This file is part of click_logging_config.
|
|
4
|
+
#
|
|
5
|
+
# You should have received a copy of the MIT License along with build_harness.
|
|
6
|
+
# If not, see <https://opensource.org/licenses/MIT>.
|
|
7
|
+
|
|
8
|
+
"""Default values."""
|
|
9
|
+
|
|
10
|
+
import pathlib
|
|
11
|
+
|
|
12
|
+
DEFAULT_RELEASE_ID = "0.0.0"
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONSOLE_JSON_ENABLED = False
|
|
15
|
+
DEFAULT_CONSOLE_LOGGING_ENABLED = False
|
|
16
|
+
|
|
17
|
+
DEFAULT_FILE_JSON_ENABLED = True
|
|
18
|
+
DEFAULT_FILE_LOGGING_ENABLED = True
|
|
19
|
+
DEFAULT_FILE_ROTATION_BACKUPS = 10
|
|
20
|
+
DEFAULT_FILE_ROTATION_SIZE_MB = 1
|
|
21
|
+
|
|
22
|
+
DEFAULT_LOG_FILE = pathlib.Path("this.log")
|
|
23
|
+
DEFAULT_LOG_FORMAT = "%(asctime)s::%(levelname)s::%(name)s::%(message)s"
|
|
24
|
+
DEFAULT_LOG_LEVEL = "warning"
|
|
25
|
+
|
|
26
|
+
VALID_LOG_LEVELS = ["critical", "error", "warning", "info", "debug", "notset"]
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Copyright (c) 2022 Russell Smiley
|
|
2
|
+
#
|
|
3
|
+
# This file is part of click_logging_config.
|
|
4
|
+
#
|
|
5
|
+
# You should have received a copy of the MIT License along with build_harness.
|
|
6
|
+
# If not, see <https://opensource.org/licenses/MIT>.
|
|
7
|
+
|
|
8
|
+
"""Logging configuration and state."""
|
|
9
|
+
|
|
10
|
+
import logging.handlers
|
|
11
|
+
import pathlib
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import json_log_formatter # type: ignore
|
|
15
|
+
import pendulum
|
|
16
|
+
import pydantic
|
|
17
|
+
import pytz
|
|
18
|
+
|
|
19
|
+
from ._default_values import (
|
|
20
|
+
DEFAULT_CONSOLE_JSON_ENABLED,
|
|
21
|
+
DEFAULT_CONSOLE_LOGGING_ENABLED,
|
|
22
|
+
DEFAULT_FILE_JSON_ENABLED,
|
|
23
|
+
DEFAULT_FILE_LOGGING_ENABLED,
|
|
24
|
+
DEFAULT_FILE_ROTATION_BACKUPS,
|
|
25
|
+
DEFAULT_FILE_ROTATION_SIZE_MB,
|
|
26
|
+
DEFAULT_LOG_FILE,
|
|
27
|
+
DEFAULT_LOG_FORMAT,
|
|
28
|
+
DEFAULT_LOG_LEVEL,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConsoleLogging(pydantic.BaseModel):
|
|
33
|
+
"""Console log configuration parameters."""
|
|
34
|
+
|
|
35
|
+
json_enabled: bool = DEFAULT_CONSOLE_JSON_ENABLED
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FileLogging(pydantic.BaseModel):
|
|
39
|
+
"""Log file configuration parameters.
|
|
40
|
+
|
|
41
|
+
In this context file logs are *always* rotated - the rotation just might be
|
|
42
|
+
at a relatively large file size. As a best practice, not rotating log files
|
|
43
|
+
is considered not particularly useful.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
json_enabled: bool = DEFAULT_FILE_JSON_ENABLED
|
|
47
|
+
log_file_path: pathlib.Path = DEFAULT_LOG_FILE
|
|
48
|
+
file_rotation_size_megabytes: int = DEFAULT_FILE_ROTATION_SIZE_MB
|
|
49
|
+
max_rotation_backup_files: int = DEFAULT_FILE_ROTATION_BACKUPS
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LoggingConfiguration(pydantic.BaseModel):
|
|
53
|
+
"""Logging configuration data."""
|
|
54
|
+
|
|
55
|
+
log_level: str = DEFAULT_LOG_LEVEL
|
|
56
|
+
|
|
57
|
+
enable_console_logging: bool = DEFAULT_CONSOLE_LOGGING_ENABLED
|
|
58
|
+
console_logging: ConsoleLogging = ConsoleLogging()
|
|
59
|
+
|
|
60
|
+
enable_file_logging: bool = DEFAULT_FILE_LOGGING_ENABLED
|
|
61
|
+
file_logging: FileLogging = FileLogging()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
U = typing.TypeVar("U", bound="Iso8601Formatter")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Iso8601Formatter(logging.Formatter):
|
|
68
|
+
"""Custom formatter with ISO-8601 timestamps."""
|
|
69
|
+
|
|
70
|
+
converter: typing.Callable[..., pendulum.DateTime] = pendulum.from_timestamp # type: ignore [assignment]
|
|
71
|
+
|
|
72
|
+
def formatTime(
|
|
73
|
+
self: U,
|
|
74
|
+
record: logging.LogRecord,
|
|
75
|
+
datefmt: typing.Optional[str] = None,
|
|
76
|
+
timezone: typing.Optional[str] = None,
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Generate formatted time."""
|
|
79
|
+
if timezone:
|
|
80
|
+
v = Iso8601Formatter.converter(
|
|
81
|
+
record.created,
|
|
82
|
+
tz=pytz.timezone(timezone),
|
|
83
|
+
).isoformat()
|
|
84
|
+
else:
|
|
85
|
+
v = Iso8601Formatter.converter(
|
|
86
|
+
record.created,
|
|
87
|
+
).isoformat()
|
|
88
|
+
|
|
89
|
+
return v
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
T = typing.TypeVar("T", bound="LoggingState")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class LoggingState:
|
|
96
|
+
"""Logging configuration parameters."""
|
|
97
|
+
|
|
98
|
+
configuration: LoggingConfiguration
|
|
99
|
+
_console_handler: typing.Optional[logging.StreamHandler]
|
|
100
|
+
_rotation_handler: typing.Optional[logging.handlers.RotatingFileHandler]
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self: T,
|
|
104
|
+
logging_configuration: LoggingConfiguration,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Construct ``LoggingState`` object.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
logging_configuration: Logging configuration to be applied.
|
|
110
|
+
"""
|
|
111
|
+
self.configuration = logging_configuration
|
|
112
|
+
self._console_handler = None
|
|
113
|
+
self._rotation_handler = None
|
|
114
|
+
|
|
115
|
+
self.set_logging_state()
|
|
116
|
+
|
|
117
|
+
def set_logging_state(self: T) -> None:
|
|
118
|
+
"""Apply the logging state from configuration."""
|
|
119
|
+
root_logger = logging.getLogger()
|
|
120
|
+
self.__set_log_level(root_logger)
|
|
121
|
+
self.__set_file_logging(root_logger)
|
|
122
|
+
self.__set_console_logging(root_logger)
|
|
123
|
+
|
|
124
|
+
def __level_value(self: T) -> int:
|
|
125
|
+
"""Convert log level text to a ``logging`` framework integer."""
|
|
126
|
+
return getattr(logging, self.configuration.log_level.upper())
|
|
127
|
+
|
|
128
|
+
def __set_file_logging(self: T, root_logger: logging.Logger) -> None:
|
|
129
|
+
"""Enable or disable file logging.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
root_logger: Root logger to modify.
|
|
133
|
+
"""
|
|
134
|
+
if self.configuration.enable_file_logging:
|
|
135
|
+
# No change if a rotation handler already exists.
|
|
136
|
+
if not self._rotation_handler:
|
|
137
|
+
this_handler = logging.handlers.RotatingFileHandler(
|
|
138
|
+
str(self.configuration.file_logging.log_file_path),
|
|
139
|
+
backupCount=(
|
|
140
|
+
self.configuration.file_logging.max_rotation_backup_files # noqa: E501
|
|
141
|
+
),
|
|
142
|
+
maxBytes=( # noqa: E501
|
|
143
|
+
self.configuration.file_logging.file_rotation_size_megabytes # noqa: E501
|
|
144
|
+
* (1024**2)
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
this_handler.setLevel(self.__level_value())
|
|
148
|
+
if self.configuration.file_logging.json_enabled:
|
|
149
|
+
this_handler.setFormatter(
|
|
150
|
+
json_log_formatter.VerboseJSONFormatter()
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
this_handler.setFormatter(
|
|
154
|
+
Iso8601Formatter(fmt=DEFAULT_LOG_FORMAT)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self._rotation_handler = this_handler
|
|
158
|
+
|
|
159
|
+
root_logger.addHandler(self._rotation_handler)
|
|
160
|
+
elif self._rotation_handler:
|
|
161
|
+
self._rotation_handler.flush()
|
|
162
|
+
self._rotation_handler.close()
|
|
163
|
+
root_logger.removeHandler(self._rotation_handler)
|
|
164
|
+
self._rotation_handler = None
|
|
165
|
+
# else self._rotation_handler is None and not self.enable_file_logging
|
|
166
|
+
# so do nothing
|
|
167
|
+
|
|
168
|
+
def __set_console_logging(self: T, root_logger: logging.Logger) -> None:
|
|
169
|
+
"""Enable or disable console logging.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
root_logger: Root logger to modify.
|
|
173
|
+
"""
|
|
174
|
+
if self.configuration.enable_console_logging:
|
|
175
|
+
# No change if a console handler already exists.
|
|
176
|
+
if not self._console_handler:
|
|
177
|
+
self._console_handler = logging.StreamHandler()
|
|
178
|
+
self._console_handler.setLevel(self.__level_value())
|
|
179
|
+
if self.configuration.console_logging.json_enabled:
|
|
180
|
+
self._console_handler.setFormatter(
|
|
181
|
+
json_log_formatter.VerboseJSONFormatter()
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
self._console_handler.setFormatter(
|
|
185
|
+
Iso8601Formatter(fmt=DEFAULT_LOG_FORMAT)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
root_logger.addHandler(self._console_handler)
|
|
189
|
+
elif self._console_handler:
|
|
190
|
+
self._console_handler.flush()
|
|
191
|
+
self._console_handler.close()
|
|
192
|
+
root_logger.removeHandler(self._console_handler)
|
|
193
|
+
self._console_handler = None
|
|
194
|
+
# else self._console_handler is None and not
|
|
195
|
+
# self.enable_console_logging so do nothing
|
|
196
|
+
|
|
197
|
+
def __set_log_level(self: T, root_logger: logging.Logger) -> None:
|
|
198
|
+
"""Set log level on any existing handlers.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
root_logger: Root logger to modify.
|
|
202
|
+
"""
|
|
203
|
+
for this_handler in root_logger.handlers:
|
|
204
|
+
this_handler.setLevel(self.__level_value())
|
|
205
|
+
# Ensure that the logging level propagates to any subsequently created
|
|
206
|
+
# handlers by setting the root logger level as well.
|
|
207
|
+
root_logger.setLevel(self.__level_value())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright (c) 2020 Russell Smiley
|
|
2
|
+
#
|
|
3
|
+
# This file is part of build_harness.
|
|
4
|
+
#
|
|
5
|
+
# You should have received a copy of the MIT License along with build_harness.
|
|
6
|
+
# If not, see <https://opensource.org/licenses/MIT>.
|
|
7
|
+
|
|
8
|
+
"""Package version management."""
|
|
9
|
+
|
|
10
|
+
import pathlib
|
|
11
|
+
|
|
12
|
+
from click_logging_config._default_values import DEFAULT_RELEASE_ID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def acquire_version() -> str:
|
|
16
|
+
"""Acquire PEP-440 compliant version from VERSION file.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Acquired version text.
|
|
20
|
+
Raises:
|
|
21
|
+
RuntimeError: If version is not valid.
|
|
22
|
+
"""
|
|
23
|
+
here = pathlib.Path(__file__).parent
|
|
24
|
+
version_file_path = (here / "VERSION").absolute()
|
|
25
|
+
|
|
26
|
+
if version_file_path.is_file():
|
|
27
|
+
with version_file_path.open(mode="r") as version_file:
|
|
28
|
+
version = version_file.read().strip()
|
|
29
|
+
else:
|
|
30
|
+
version = DEFAULT_RELEASE_ID
|
|
31
|
+
|
|
32
|
+
if not version:
|
|
33
|
+
raise RuntimeError("Unable to acquire version")
|
|
34
|
+
|
|
35
|
+
return version
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__version__ = acquire_version()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2024 Russell Smiley
|
|
2
|
+
#
|
|
3
|
+
# This file is part of click_logging_config.
|
|
4
|
+
#
|
|
5
|
+
# You should have received a copy of the MIT License along with
|
|
6
|
+
# click_logging_config.
|
|
7
|
+
# If not, see <https://opensource.org/licenses/MIT>.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Convenience imports of core functionality."""
|
|
11
|
+
|
|
12
|
+
from ._click import logging_parameters # noqa: F401
|
|
13
|
+
from ._logging import LoggingConfiguration, LoggingState # noqa: F401
|
|
File without changes
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: click_logging_config
|
|
3
|
+
Version: 2.0.2
|
|
4
|
+
Dynamic: Summary
|
|
5
|
+
Project-URL: source, https://gitlab.com/ci-cd-devops/click_logging_config
|
|
6
|
+
Author-email: Russell Smiley <russell@bytingchipmunk.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE.txt
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: click>=8.3
|
|
14
|
+
Requires-Dist: json-log-formatter>=1.0
|
|
15
|
+
Requires-Dist: pendulum>=3.1
|
|
16
|
+
Requires-Dist: pydantic<3,>=2
|
|
17
|
+
Requires-Dist: pytz>=2025.2
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: invoke>=2.2.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: mypy>=1.18.2; extra == 'dev'
|
|
21
|
+
Requires-Dist: pre-commit>=4.3; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.14.4; extra == 'dev'
|
|
23
|
+
Requires-Dist: types-pytz>=2025.2; extra == 'dev'
|
|
24
|
+
Requires-Dist: uv>=0.9.8; extra == 'dev'
|
|
25
|
+
Provides-Extra: doc
|
|
26
|
+
Requires-Dist: sphinx-rtd-theme>=2.0; extra == 'doc'
|
|
27
|
+
Requires-Dist: sphinx>=7.4.7; extra == 'doc'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest-cov>=7.0; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-mock>=3.15.1; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest>=8.4.2; extra == 'test'
|
|
32
|
+
Description-Content-Type: text/x-rst
|
|
33
|
+
|
|
34
|
+
click-logging-config
|
|
35
|
+
====================
|
|
36
|
+
|
|
37
|
+
Quick and easy CLI logging options for `click <https://palletsprojects.com/p/click/>`_
|
|
38
|
+
commands using a Python decorator.
|
|
39
|
+
|
|
40
|
+
I found myself implementing logging preferences repeatedly for utilities. Logging
|
|
41
|
+
configuration is pretty simple, but for each new implementation I would
|
|
42
|
+
find myself spending time researching the same options to refresh my memory and
|
|
43
|
+
then implementing something slightly different than the last time. 🙄
|
|
44
|
+
|
|
45
|
+
``click-logging-config`` is my attempt to stop the circle of re-implementation
|
|
46
|
+
with settings that are useful enough, with configurability to change it if you
|
|
47
|
+
don't like the out-of-box behaviour. It's proving to be pretty useful and I'm
|
|
48
|
+
already using it across several of my other projects. 😄
|
|
49
|
+
|
|
50
|
+
It is released under the MIT license so you are free to use it in lots of
|
|
51
|
+
different ways. As simple as it looks, a tool like this still represents
|
|
52
|
+
research time and implementation effort, so please use the link below to help
|
|
53
|
+
support development.
|
|
54
|
+
|
|
55
|
+
`Support click-logging-config <https://byting-chipmunk.ck.page/products/click-logging-config>`_
|
|
56
|
+
|
|
57
|
+
`Byting Chipmunk <https://bytingchipmunk.com>`_ 🐿
|
|
58
|
+
|
|
59
|
+
*Take a byte off.*
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
.. contents::
|
|
63
|
+
|
|
64
|
+
.. section-numbering::
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Installation
|
|
68
|
+
------------
|
|
69
|
+
|
|
70
|
+
The ``click-logging-config`` package is available from PyPI. Installing
|
|
71
|
+
into a virtual environment is recommended.
|
|
72
|
+
|
|
73
|
+
.. code-block::
|
|
74
|
+
|
|
75
|
+
python3 -m venv .venv; .venv/bin/pip install click-logging-config
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Getting Started
|
|
79
|
+
---------------
|
|
80
|
+
|
|
81
|
+
Using ``click-logging-config`` is intended to be very simple. A single
|
|
82
|
+
decorator applied to your click command or group adds some click options
|
|
83
|
+
specifically for managing logging context.
|
|
84
|
+
|
|
85
|
+
.. code-block::
|
|
86
|
+
|
|
87
|
+
import click
|
|
88
|
+
import logging
|
|
89
|
+
from click_logging import logging_parameters
|
|
90
|
+
|
|
91
|
+
log = logging.getLogger(__name__)
|
|
92
|
+
|
|
93
|
+
def do_something()
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@click.command()
|
|
97
|
+
@click.option("--my-option", type=str)
|
|
98
|
+
# NOTE: Empty braces are required for hard-coded click-logging-config defaults.
|
|
99
|
+
@logging_parameters()
|
|
100
|
+
def my_command(my_option: str) -> None:
|
|
101
|
+
log.info("doing something")
|
|
102
|
+
try:
|
|
103
|
+
do_something(my_option)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
log.critical(f"something bad happened, {str(e)}")
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
Application of the ``@logging_parameters`` decorator must be applied immediately
|
|
110
|
+
*above* your click command function and *below* any other click decorators such
|
|
111
|
+
as arguments and options.
|
|
112
|
+
|
|
113
|
+
Having applied the decorator, your command now has the following options
|
|
114
|
+
available to it.
|
|
115
|
+
|
|
116
|
+
.. code-block::
|
|
117
|
+
|
|
118
|
+
--log-console-enable / --log-console-disable
|
|
119
|
+
Enable or disable console logging.
|
|
120
|
+
[default: log-console-disable]
|
|
121
|
+
--log-console-json-enable / --log-console-json-disable
|
|
122
|
+
Enable or disable console JSON logging.
|
|
123
|
+
[default: log-console-json-disable]
|
|
124
|
+
--log-file-enable / --log-file-disable
|
|
125
|
+
Enable or disable file logging.
|
|
126
|
+
[default: log-file-enable]
|
|
127
|
+
--log-file-json-enable / --log-file-json-disable
|
|
128
|
+
Enable or disable file JSON logging.
|
|
129
|
+
[default: log-file-json-enable]
|
|
130
|
+
--log-file FILE The log file to write to. [default: this.log]
|
|
131
|
+
--log-level [critical|error|warning|info|debug|notset]
|
|
132
|
+
Select logging level to apply to all enabled
|
|
133
|
+
log sinks. [default: warning]
|
|
134
|
+
|
|
135
|
+
Note that the single log level configuration parameter applies to both console
|
|
136
|
+
and file logging.
|
|
137
|
+
|
|
138
|
+
The internal defaults are configured for an interactive utility (run by a
|
|
139
|
+
human in a terminal rather than via automation, or in a container). In summary,
|
|
140
|
+
|
|
141
|
+
* disabled console logging (allows your application to use console output, if needed)
|
|
142
|
+
* enabled file logging (1MB rotation size, with 10 rotation backups)
|
|
143
|
+
* "warning" log level
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
Custom defaults
|
|
147
|
+
---------------
|
|
148
|
+
|
|
149
|
+
If you don't like the ``click-logging-config`` internal defaults for the options
|
|
150
|
+
you can define your own. The ``LoggingConfiguration`` class is derived from
|
|
151
|
+
``pydantic.BaseModel``, so one easy way to define your defaults is using a
|
|
152
|
+
dictionary. You only need to define values you want to change - any other value
|
|
153
|
+
will continue using the internal defaults.
|
|
154
|
+
|
|
155
|
+
.. code-block::
|
|
156
|
+
|
|
157
|
+
import pathlib
|
|
158
|
+
|
|
159
|
+
import click
|
|
160
|
+
import logging
|
|
161
|
+
from click_logging import logging_parameters, LoggingConfiguration
|
|
162
|
+
|
|
163
|
+
log = logging.getLogger(__name__)
|
|
164
|
+
|
|
165
|
+
MY_LOGGING_DEFAULTS = LoggingConfiguration.parse_obj(
|
|
166
|
+
{
|
|
167
|
+
"file_logging": {
|
|
168
|
+
# NOTE: file path must be specified using pathlib.Path
|
|
169
|
+
"log_file_path": pathlib.Path("some_other.log"),
|
|
170
|
+
},
|
|
171
|
+
"log_level": "info",
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def do_something()
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
@click.command()
|
|
179
|
+
@click.option("--my-option", type=str)
|
|
180
|
+
@logging_parameters(MY_LOGGING_DEFAULTS)
|
|
181
|
+
def my_command(my_option: str) -> None:
|
|
182
|
+
log.info("doing something")
|
|
183
|
+
try:
|
|
184
|
+
do_something(my_option)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
log.critical(f"something bad happened, {str(e)}")
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
The table below summarizes the available settings for defaults. Otherwise
|
|
191
|
+
review the ``LoggingConfiguration`` `class definition <https://gitlab.com/ci-cd-devops/click_logging_config/-/blob/main/click_logging_config/_logging.py#L52>`_ .
|
|
192
|
+
|
|
193
|
+
.. csv-table:: Available top-level settings for logging defaults.
|
|
194
|
+
:header: "Setting", "Type", "Hard default", "Description"
|
|
195
|
+
|
|
196
|
+
"log_level", "str", "warning", "Define log level"
|
|
197
|
+
"enable_console_logging", "boolean", "False", "Enable console logging"
|
|
198
|
+
"console_logging", "dict", "", "Console logging specific settings. See table below."
|
|
199
|
+
"enable_file_logging", "bool", "True", "Enable file logging"
|
|
200
|
+
"file_logging", "dict", "", "File logging specific settings. See table below."
|
|
201
|
+
|
|
202
|
+
.. csv-table:: Available console logging defaults.
|
|
203
|
+
:header: "Setting", "Type", "Hard default", "Description"
|
|
204
|
+
|
|
205
|
+
"json_enabled", "bool", "False", "Output JSON logs using ``json_log_formatter``"
|
|
206
|
+
|
|
207
|
+
.. csv-table:: Available file logging defaults.
|
|
208
|
+
:header: "Setting", "Type", "Hard default", "Description"
|
|
209
|
+
|
|
210
|
+
"json_enabled", "bool", "True", "Output JSON logs using ``json_log_formatter``"
|
|
211
|
+
"log_file_path", "pathlib.Path", "./this.log", "Path and name of log file."
|
|
212
|
+
"file_rotation_size_megabytes", "int", "1", "Maximum size of "
|
|
213
|
+
"max_rotation_backup_files", "int", "10", "Maximum number of rotation backup files"
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
Console logging
|
|
217
|
+
---------------
|
|
218
|
+
|
|
219
|
+
Console logging can be enabled or disabled, and there is an additional option
|
|
220
|
+
to output line-by-line text based timestamped log entries, or JSON logging via
|
|
221
|
+
the ``json_log_formatter`` framework. The format of text based log entries
|
|
222
|
+
cannot be configured at this time and console logging is always emitted to
|
|
223
|
+
stderr at this time.
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
File logging
|
|
227
|
+
------------
|
|
228
|
+
|
|
229
|
+
File rotation on the file log is implemented as a "sensible default" - it cannot
|
|
230
|
+
be disabled at this time, although you might be able to specify a maximum
|
|
231
|
+
rotation of ``1`` to achieve the same end (not tested). The maximum rotation
|
|
232
|
+
size can be specified as a configuration default. File logging itself can be
|
|
233
|
+
enabled or disabled via defaults or the CLI options described above.
|
|
234
|
+
|
|
235
|
+
Similar to console logging the format can be as either text-based or JSON
|
|
236
|
+
logging.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
click_logging_config/VERSION,sha256=tLFvrqUtwEzP4y97NCCUhVkj6_WZo8rBlARwotbXwmE,6
|
|
2
|
+
click_logging_config/__init__.py,sha256=ApTiqgsbFqWTVWw7C_i6dFHWEgVCDRnQ7OlZ6D9fOAw,146
|
|
3
|
+
click_logging_config/_click.py,sha256=F3Rajiqzg6k--17Il3n0YNY8GK7728bu6HRpHuMw2q0,4896
|
|
4
|
+
click_logging_config/_default_values.py,sha256=bvH2zWLZc-LtdoznV6xuPNhXGAmMlySqsaDEzSncR90,735
|
|
5
|
+
click_logging_config/_logging.py,sha256=t1jz6sb9VmM7pF81tTiy_nk-aPEICIYWs43jvLa8TPk,7178
|
|
6
|
+
click_logging_config/_version.py,sha256=bGmsJRW4pXXOBvC6FMYbVDMBgCE0gEsjhXfP1cjv_Dg,961
|
|
7
|
+
click_logging_config/parameters.py,sha256=IRfUamM2yQbjp5c_TWVxQFu9xVvMmRQPgPjjVx0Ibfs,408
|
|
8
|
+
click_logging_config/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
click_logging_config-2.0.2.dist-info/METADATA,sha256=tTT9ZnvwkHIjIWVjA0X1WRvHYMa2JF3qJoI-BjxlIJA,8613
|
|
10
|
+
click_logging_config-2.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
click_logging_config-2.0.2.dist-info/licenses/LICENSE.txt,sha256=YtQuRK_6LysSNNjqInIn-ALBw_A3vqKc8M7K-2u06Dk,1103
|
|
12
|
+
click_logging_config-2.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright 2022 Russell Smiley
|
|
2
|
+
|
|
3
|
+
MIT license https://opensource.org/licenses/MIT
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
9
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
10
|
+
so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|