logdruid 0.1.0.dev2__tar.gz → 0.1.0.dev3__tar.gz
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.
- {logdruid-0.1.0.dev2 → logdruid-0.1.0.dev3}/LICENSE +1 -1
- {logdruid-0.1.0.dev2 → logdruid-0.1.0.dev3}/PKG-INFO +13 -12
- {logdruid-0.1.0.dev2 → logdruid-0.1.0.dev3}/README.md +5 -7
- {logdruid-0.1.0.dev2 → logdruid-0.1.0.dev3}/pyproject.toml +16 -2
- logdruid-0.1.0.dev3/src/logdruid/__init__.py +22 -0
- {logdruid-0.1.0.dev2 → logdruid-0.1.0.dev3}/src/logdruid/logdruid.py +61 -25
- logdruid-0.1.0.dev2/src/logdruid/__init__.py +0 -86
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: logdruid
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev3
|
|
4
4
|
Summary: A drop-in logging setup layer for multi-process applications, built on loguru.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
|
-
Author:
|
|
8
|
-
Author-email: f.herbrand@hzdr.de
|
|
7
|
+
Author: github.com/fherb2
|
|
9
8
|
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
11
|
Classifier: Programming Language :: Python :: 3
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.15
|
|
16
15
|
Requires-Dist: loguru (>=0.7.3)
|
|
16
|
+
Project-URL: Changelog, https://fherb2.github.io/logdruid/changelog/
|
|
17
|
+
Project-URL: Documentation, https://fherb2.github.io/logdruid/
|
|
18
|
+
Project-URL: Issues, https://github.com/fherb2/logdruid/issues
|
|
19
|
+
Project-URL: Repository, https://github.com/fherb2/logdruid
|
|
17
20
|
Description-Content-Type: text/markdown
|
|
18
21
|
|
|
19
22
|
# logdruid
|
|
@@ -24,8 +27,7 @@ Description-Content-Type: text/markdown
|
|
|
24
27
|
A drop-in logging setup layer for multi-process applications, built on
|
|
25
28
|
[loguru](https://loguru.readthedocs.io/).
|
|
26
29
|
|
|
27
|
-
**
|
|
28
|
-
<https://fherb2.github.io/logdruid/>**
|
|
30
|
+
**[Documentation](https://fherb2.github.io/logdruid/)** – installation, usage, configuration and API.
|
|
29
31
|
|
|
30
32
|
## What it is for
|
|
31
33
|
|
|
@@ -65,13 +67,12 @@ through `logging`.
|
|
|
65
67
|
| Configuration from a file, precedence defaults < application < file < command line | no, only environment variables | yes |
|
|
66
68
|
| Mistakes in the configuration do not stop the application | no | yes, they only produce a warning |
|
|
67
69
|
|
|
68
|
-
##
|
|
70
|
+
## Documentation
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
(in German).
|
|
72
|
+
Installation, usage, configuration, the API reference and the changelog:
|
|
73
|
+
<https://fherb2.github.io/logdruid/>
|
|
73
74
|
|
|
74
75
|
## License
|
|
75
76
|
|
|
76
|
-
MIT, see [
|
|
77
|
+
MIT, see [LICENSE](https://github.com/fherb2/logdruid/blob/main/LICENSE).
|
|
77
78
|
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
A drop-in logging setup layer for multi-process applications, built on
|
|
7
7
|
[loguru](https://loguru.readthedocs.io/).
|
|
8
8
|
|
|
9
|
-
**
|
|
10
|
-
<https://fherb2.github.io/logdruid/>**
|
|
9
|
+
**[Documentation](https://fherb2.github.io/logdruid/)** – installation, usage, configuration and API.
|
|
11
10
|
|
|
12
11
|
## What it is for
|
|
13
12
|
|
|
@@ -47,12 +46,11 @@ through `logging`.
|
|
|
47
46
|
| Configuration from a file, precedence defaults < application < file < command line | no, only environment variables | yes |
|
|
48
47
|
| Mistakes in the configuration do not stop the application | no | yes, they only produce a warning |
|
|
49
48
|
|
|
50
|
-
##
|
|
49
|
+
## Documentation
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
(in German).
|
|
51
|
+
Installation, usage, configuration, the API reference and the changelog:
|
|
52
|
+
<https://fherb2.github.io/logdruid/>
|
|
55
53
|
|
|
56
54
|
## License
|
|
57
55
|
|
|
58
|
-
MIT, see [
|
|
56
|
+
MIT, see [LICENSE](https://github.com/fherb2/logdruid/blob/main/LICENSE).
|
|
@@ -4,19 +4,33 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "logdruid"
|
|
7
|
-
version = "0.1.0.
|
|
7
|
+
version = "0.1.0.dev3"
|
|
8
8
|
description = "A drop-in logging setup layer for multi-process applications, built on loguru."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<4.0"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [
|
|
14
|
-
{ name = "
|
|
14
|
+
{ name = "github.com/fherb2" },
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
15
23
|
]
|
|
16
24
|
dependencies = [
|
|
17
25
|
"loguru>=0.7.3",
|
|
18
26
|
]
|
|
19
27
|
|
|
28
|
+
[project.urls]
|
|
29
|
+
Documentation = "https://fherb2.github.io/logdruid/"
|
|
30
|
+
Repository = "https://github.com/fherb2/logdruid"
|
|
31
|
+
Issues = "https://github.com/fherb2/logdruid/issues"
|
|
32
|
+
Changelog = "https://fherb2.github.io/logdruid/changelog/"
|
|
33
|
+
|
|
20
34
|
[tool.poetry]
|
|
21
35
|
packages = [{ include = "logdruid", from = "src" }]
|
|
22
36
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""A logging setup layer for multi-process applications, built on loguru.
|
|
2
|
+
|
|
3
|
+
Reporting uses loguru's own logger unchanged. logdruid sets it up and adds
|
|
4
|
+
one time-ordered output for all processes, the takeover of the standard
|
|
5
|
+
``logging`` module, and a configuration file of its own.
|
|
6
|
+
|
|
7
|
+
Documentation: https://fherb2.github.io/logdruid/
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .logdruid import (
|
|
11
|
+
CONFIG_FILE_NAME,
|
|
12
|
+
LogLink,
|
|
13
|
+
attach,
|
|
14
|
+
check_config,
|
|
15
|
+
link,
|
|
16
|
+
logger,
|
|
17
|
+
setup,
|
|
18
|
+
shutdown,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = ["logger", "setup", "check_config", "link", "attach", "shutdown",
|
|
22
|
+
"LogLink", "CONFIG_FILE_NAME"]
|
|
@@ -24,8 +24,12 @@ from loguru import logger
|
|
|
24
24
|
|
|
25
25
|
# --- Module parameters ------------------------------------------------
|
|
26
26
|
|
|
27
|
-
# Name of the configuration file looked for next to the entry point.
|
|
28
27
|
CONFIG_FILE_NAME = "logdruid.toml"
|
|
28
|
+
"""Name of the configuration file setup() looks for next to the entry point."""
|
|
29
|
+
|
|
30
|
+
# Name under which logdruid writes its own warnings. They bypass the
|
|
31
|
+
# minimum levels: a level set too high must not hide a configuration error.
|
|
32
|
+
OWN_NAME = "logdruid"
|
|
29
33
|
|
|
30
34
|
# Lowest layer: what applies when neither API, file nor command line
|
|
31
35
|
# say anything. No translation, one level for everything.
|
|
@@ -112,16 +116,8 @@ class LogLink:
|
|
|
112
116
|
"""What a child process needs to log into the main process.
|
|
113
117
|
|
|
114
118
|
Created by link() in the main process and handed to the child as an
|
|
115
|
-
argument; the child passes it to attach().
|
|
116
|
-
|
|
117
|
-
Attributes
|
|
118
|
-
----------
|
|
119
|
-
process_name : str
|
|
120
|
-
Name the child process carries in every message.
|
|
121
|
-
conn : multiprocessing.connection.Connection
|
|
122
|
-
Write end of the child's own pipe.
|
|
123
|
-
settings : Settings
|
|
124
|
-
Resolved configuration of the main process.
|
|
119
|
+
argument; the child passes it to attach(). Its fields are internal;
|
|
120
|
+
only release() is part of the public interface.
|
|
125
121
|
"""
|
|
126
122
|
|
|
127
123
|
process_name: str
|
|
@@ -171,15 +167,8 @@ def setup(config_path: Optional[Path] = None, *,
|
|
|
171
167
|
|
|
172
168
|
mp.current_process().name = process_name
|
|
173
169
|
warnings: List[str] = []
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if cli_level is not None:
|
|
177
|
-
cli_layer["std_level"] = cli_level
|
|
178
|
-
if cli_log_file is not None:
|
|
179
|
-
cli_layer["output"] = {"file": {"path": cli_log_file}}
|
|
180
|
-
|
|
181
|
-
raw_layers = [cli_layer, file_layer, defaults or {}, MODULE_DEFAULTS]
|
|
182
|
-
_settings = _resolve(raw_layers, warnings)
|
|
170
|
+
_settings = _load_settings(config_path, defaults, cli_level, cli_log_file,
|
|
171
|
+
warnings)
|
|
183
172
|
_threshold_cache.clear()
|
|
184
173
|
|
|
185
174
|
logger.remove()
|
|
@@ -195,7 +184,33 @@ def setup(config_path: Optional[Path] = None, *,
|
|
|
195
184
|
atexit.register(shutdown)
|
|
196
185
|
|
|
197
186
|
for text in warnings:
|
|
198
|
-
|
|
187
|
+
_output.patch(_own_name).warning(text)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def check_config(config_path: Optional[Path] = None, *,
|
|
191
|
+
defaults: Optional[Dict[str, Any]] = None,
|
|
192
|
+
cli_level: Optional[str] = None,
|
|
193
|
+
cli_log_file: Optional[str] = None) -> List[str]:
|
|
194
|
+
"""Check a configuration without setting anything up.
|
|
195
|
+
|
|
196
|
+
Resolves the same layers as setup() and returns the warnings setup()
|
|
197
|
+
would report about them. Whether an output can actually be created,
|
|
198
|
+
for example whether the path of the log file is writable, only setup()
|
|
199
|
+
finds out.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
config_path, defaults, cli_level, cli_log_file
|
|
204
|
+
As for setup().
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
list of str
|
|
209
|
+
The warnings; empty if the configuration is sound.
|
|
210
|
+
"""
|
|
211
|
+
warnings: List[str] = []
|
|
212
|
+
_load_settings(config_path, defaults, cli_level, cli_log_file, warnings)
|
|
213
|
+
return warnings
|
|
199
214
|
|
|
200
215
|
|
|
201
216
|
def link(process_name: str) -> LogLink:
|
|
@@ -258,6 +273,22 @@ def shutdown() -> None:
|
|
|
258
273
|
|
|
259
274
|
# --- Configuration ----------------------------------------------------
|
|
260
275
|
|
|
276
|
+
def _load_settings(config_path: Optional[Path],
|
|
277
|
+
defaults: Optional[Dict[str, Any]],
|
|
278
|
+
cli_level: Optional[str],
|
|
279
|
+
cli_log_file: Optional[str],
|
|
280
|
+
warnings: List[str]) -> Settings:
|
|
281
|
+
"""Resolve the four layers into Settings, collecting the warnings."""
|
|
282
|
+
file_layer = _read_config_file(config_path, warnings)
|
|
283
|
+
cli_layer: Dict[str, Any] = {}
|
|
284
|
+
if cli_level is not None:
|
|
285
|
+
cli_layer["std_level"] = cli_level
|
|
286
|
+
if cli_log_file is not None:
|
|
287
|
+
cli_layer["output"] = {"file": {"path": cli_log_file}}
|
|
288
|
+
raw_layers = [cli_layer, file_layer, defaults or {}, MODULE_DEFAULTS]
|
|
289
|
+
return _resolve(raw_layers, warnings)
|
|
290
|
+
|
|
291
|
+
|
|
261
292
|
def _read_config_file(config_path: Optional[Path],
|
|
262
293
|
warnings: List[str]) -> Dict[str, Any]:
|
|
263
294
|
"""Return the file layer, or an empty one with a warning."""
|
|
@@ -528,6 +559,11 @@ def _take_over_std_logging(process: str) -> None:
|
|
|
528
559
|
|
|
529
560
|
# --- Transport between the processes ------------------------------------
|
|
530
561
|
|
|
562
|
+
def _own_name(record: Dict[str, Any]) -> None:
|
|
563
|
+
"""loguru patcher: mark a message as logdruid's own warning."""
|
|
564
|
+
record.update(name=OWN_NAME)
|
|
565
|
+
|
|
566
|
+
|
|
531
567
|
def _to_item(message) -> Dict[str, Any]:
|
|
532
568
|
"""What travels for one message: its data, not loguru's objects."""
|
|
533
569
|
record = message.record
|
|
@@ -595,7 +631,7 @@ class _Collector(threading.Thread):
|
|
|
595
631
|
self._stopping = True
|
|
596
632
|
self.join(timeout_s)
|
|
597
633
|
if self.is_alive():
|
|
598
|
-
self._output.warning(
|
|
634
|
+
self._output.patch(_own_name).warning(
|
|
599
635
|
f"Logging pipes of {sorted(self._pipes.values())} did not "
|
|
600
636
|
f"end within {timeout_s} s; their remaining messages are lost")
|
|
601
637
|
with self._lock:
|
|
@@ -648,9 +684,9 @@ class _Collector(threading.Thread):
|
|
|
648
684
|
name = self._pipes.pop(conn, "?")
|
|
649
685
|
conn.close()
|
|
650
686
|
if exc is not None:
|
|
651
|
-
self._output.warning(
|
|
652
|
-
|
|
653
|
-
|
|
687
|
+
self._output.patch(_own_name).warning(
|
|
688
|
+
f"Logging pipe of {name} broke off ({type(exc).__name__}); "
|
|
689
|
+
f"later messages of that process are lost")
|
|
654
690
|
|
|
655
691
|
def _push(self, item: Dict[str, Any]) -> None:
|
|
656
692
|
"""Queue a message by its time stamp."""
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Logging setup for multi-process applications: a setup layer around loguru.
|
|
2
|
-
|
|
3
|
-
Reporting uses loguru unchanged: `logger` is loguru's global logger.
|
|
4
|
-
This module only sets it up, and adds what loguru does not offer:
|
|
5
|
-
|
|
6
|
-
- One output in the main process. Console and file are created there
|
|
7
|
-
only; every child process sends its messages through a pipe of its
|
|
8
|
-
own. Unlike loguru's ``enqueue=True`` no lock is shared between
|
|
9
|
-
processes, so a child that is killed hard blocks nobody but itself.
|
|
10
|
-
- Ordering by time. The main process holds messages back for a short
|
|
11
|
-
window and writes them sorted by their time stamp, its own included.
|
|
12
|
-
- Taking over the standard ``logging`` module, with the logger name as
|
|
13
|
-
the name of the message and with levels translated per logger.
|
|
14
|
-
- Levels per process and per name, configured in ``logdruid.toml`` in
|
|
15
|
-
four layers: module defaults < API < file < command line.
|
|
16
|
-
|
|
17
|
-
Usage
|
|
18
|
-
-----
|
|
19
|
-
Main process, once, before any child process is created:
|
|
20
|
-
|
|
21
|
-
>>> import logdruid
|
|
22
|
-
>>> from logdruid import logger
|
|
23
|
-
>>> logdruid.setup(config_path, cli_level=args.log_level)
|
|
24
|
-
>>> logger.success("Logging active")
|
|
25
|
-
|
|
26
|
-
Per child process, in the main process:
|
|
27
|
-
|
|
28
|
-
>>> link = logdruid.link("raw_writer")
|
|
29
|
-
>>> process = ctx.Process(target=raw_writer_prc, args=(link, ...),
|
|
30
|
-
... name="raw_writer")
|
|
31
|
-
>>> process.start()
|
|
32
|
-
>>> link.release()
|
|
33
|
-
|
|
34
|
-
First thing in the child process (after ignoring SIGINT):
|
|
35
|
-
|
|
36
|
-
>>> logdruid.attach(link)
|
|
37
|
-
|
|
38
|
-
At the end of the main process, after the children have ended:
|
|
39
|
-
|
|
40
|
-
>>> logdruid.shutdown()
|
|
41
|
-
|
|
42
|
-
Configuration file (all keys optional)::
|
|
43
|
-
|
|
44
|
-
std_level = "INFO" # level of everything without its own
|
|
45
|
-
sort_window_ms = 100 # 0 switches the ordering off
|
|
46
|
-
|
|
47
|
-
[output.console] # parameters of loguru's add(),
|
|
48
|
-
enabled = true # passed through; 'enabled' and
|
|
49
|
-
format = "..." # 'path' are logdruid's own
|
|
50
|
-
diagnose = false
|
|
51
|
-
|
|
52
|
-
[output.file]
|
|
53
|
-
enabled = false
|
|
54
|
-
path = "app.log"
|
|
55
|
-
rotation = "10 MB"
|
|
56
|
-
retention = 5
|
|
57
|
-
|
|
58
|
-
[processes.raw_writer]
|
|
59
|
-
level = "DEBUG"
|
|
60
|
-
|
|
61
|
-
[loggers.ognbligg] # applies to ognbligg.* as well
|
|
62
|
-
level = "INFO"
|
|
63
|
-
translate = { DEBUG = "TRACE", INFO = false } # false drops
|
|
64
|
-
|
|
65
|
-
Notes on further development
|
|
66
|
-
----------------------------
|
|
67
|
-
- An application started through a packaged entry point has no
|
|
68
|
-
``logdruid.toml`` next to ``__main__``; it passes the path instead.
|
|
69
|
-
- Exceptions travel through the pipe as text formatted in the child;
|
|
70
|
-
``backtrace`` and ``diagnose`` therefore act where a message arises.
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
from .logdruid import (
|
|
75
|
-
CONFIG_FILE_NAME,
|
|
76
|
-
LogLink,
|
|
77
|
-
Settings,
|
|
78
|
-
attach,
|
|
79
|
-
link,
|
|
80
|
-
logger,
|
|
81
|
-
setup,
|
|
82
|
-
shutdown,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
__all__ = ["logger", "setup", "link", "attach", "shutdown", "LogLink",
|
|
86
|
-
"Settings", "CONFIG_FILE_NAME"]
|