dd-logging 0.1.0__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.
- dd_logging-0.1.0/LICENSE +21 -0
- dd_logging-0.1.0/PKG-INFO +123 -0
- dd_logging-0.1.0/README.md +101 -0
- dd_logging-0.1.0/dd_logging/__init__.py +18 -0
- dd_logging-0.1.0/dd_logging/core.py +118 -0
- dd_logging-0.1.0/dd_logging.egg-info/PKG-INFO +123 -0
- dd_logging-0.1.0/dd_logging.egg-info/SOURCES.txt +9 -0
- dd_logging-0.1.0/dd_logging.egg-info/dependency_links.txt +1 -0
- dd_logging-0.1.0/dd_logging.egg-info/top_level.txt +1 -0
- dd_logging-0.1.0/pyproject.toml +33 -0
- dd_logging-0.1.0/setup.cfg +4 -0
dd_logging-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 digital-duck
|
|
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
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do 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.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dd-logging
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared logging helpers for Digital Duck projects (spl-llm, spl-flow, …)
|
|
5
|
+
Author-email: Wen Gong <wen.gong.research@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/digital-duck/dd-logging
|
|
8
|
+
Project-URL: Repository, https://github.com/digital-duck/dd-logging
|
|
9
|
+
Project-URL: Issues, https://github.com/digital-duck/dd-logging/issues
|
|
10
|
+
Keywords: logging,digital-duck,spl
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: System :: Logging
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# dd-logging
|
|
24
|
+
|
|
25
|
+
Shared logging helpers for [Digital Duck](https://github.com/digital-duck) projects.
|
|
26
|
+
|
|
27
|
+
Provides a consistent, timestamped-file logging pattern for CLI tools and Streamlit apps.
|
|
28
|
+
Used by **spl-llm** and **spl-flow**; designed to be reused by any project in the ecosystem.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# from PyPI (once published)
|
|
34
|
+
pip install dd-logging
|
|
35
|
+
|
|
36
|
+
# local editable install (for development)
|
|
37
|
+
pip install -e /path/to/dd-logging
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from dd_logging import setup_logging, get_logger, disable_logging
|
|
44
|
+
|
|
45
|
+
# 1. Call once per process (CLI entry point or app startup)
|
|
46
|
+
log_path = setup_logging(
|
|
47
|
+
"run",
|
|
48
|
+
root_name="my_app", # top-level logger namespace
|
|
49
|
+
adapter="openrouter", # appended to filename (optional)
|
|
50
|
+
log_level="info", # debug | info | warning | error
|
|
51
|
+
)
|
|
52
|
+
# → logs/run-openrouter-20260215-143022.log
|
|
53
|
+
|
|
54
|
+
# 2. In each module
|
|
55
|
+
_log = get_logger("nodes.text2spl", root_name="my_app")
|
|
56
|
+
_log.info("translating query len=%d", len(query))
|
|
57
|
+
|
|
58
|
+
# 3. Silence all logging (e.g. --no-log CLI flag)
|
|
59
|
+
disable_logging("my_app")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Thin wrapper pattern (recommended)
|
|
63
|
+
|
|
64
|
+
Each project wraps `dd_logging` so call-sites never pass `root_name`:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# myapp/logging_config.py
|
|
68
|
+
from pathlib import Path
|
|
69
|
+
from dd_logging import setup_logging as _setup, get_logger as _get, disable_logging as _disable
|
|
70
|
+
|
|
71
|
+
_ROOT = "my_app"
|
|
72
|
+
LOG_DIR = Path(__file__).resolve().parent.parent / "logs"
|
|
73
|
+
|
|
74
|
+
def get_logger(name: str):
|
|
75
|
+
return _get(name, _ROOT)
|
|
76
|
+
|
|
77
|
+
def setup_logging(run_name: str, **kw):
|
|
78
|
+
kw.setdefault("log_dir", LOG_DIR)
|
|
79
|
+
return _setup(run_name, root_name=_ROOT, **kw)
|
|
80
|
+
|
|
81
|
+
def disable_logging():
|
|
82
|
+
return _disable(_ROOT)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Log file naming
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
<log_dir>/<run_name>[-<adapter>]-<YYYYMMDD-HHMMSS>.log
|
|
89
|
+
|
|
90
|
+
logs/run-openrouter-20260215-143022.log
|
|
91
|
+
logs/benchmark-claude_cli-20260215-144500.log
|
|
92
|
+
logs/generate-20260215-145001.log
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Logger hierarchy
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
my_app ← root (FileHandler attached here)
|
|
99
|
+
├── my_app.api ← get_logger("api", "my_app")
|
|
100
|
+
├── my_app.nodes.text2spl ← get_logger("nodes.text2spl", "my_app")
|
|
101
|
+
└── my_app.flows ← get_logger("flows", "my_app")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
All child loggers inherit the root's handler — no per-module handler setup needed.
|
|
105
|
+
|
|
106
|
+
## Design notes
|
|
107
|
+
|
|
108
|
+
- **`propagate=False`** — prevents duplicate output when a root Python logger
|
|
109
|
+
handler is already configured (e.g. Streamlit, pytest, Jupyter).
|
|
110
|
+
- **Stale-handler removal** — calling `setup_logging()` multiple times in one
|
|
111
|
+
process (e.g. test suites) is safe; old `FileHandler`s are replaced.
|
|
112
|
+
- **No third-party dependencies** — stdlib `logging` only.
|
|
113
|
+
|
|
114
|
+
## Projects using dd-logging
|
|
115
|
+
|
|
116
|
+
| Project | Root name | Log dir |
|
|
117
|
+
|---------|-----------|---------|
|
|
118
|
+
| [spl-llm](https://github.com/digital-duck/SPL) | `spl` | `SPL/logs/` |
|
|
119
|
+
| [spl-flow](https://github.com/digital-duck/SPL-Flow) | `spl_flow` | `SPL-Flow/logs/` |
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# dd-logging
|
|
2
|
+
|
|
3
|
+
Shared logging helpers for [Digital Duck](https://github.com/digital-duck) projects.
|
|
4
|
+
|
|
5
|
+
Provides a consistent, timestamped-file logging pattern for CLI tools and Streamlit apps.
|
|
6
|
+
Used by **spl-llm** and **spl-flow**; designed to be reused by any project in the ecosystem.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# from PyPI (once published)
|
|
12
|
+
pip install dd-logging
|
|
13
|
+
|
|
14
|
+
# local editable install (for development)
|
|
15
|
+
pip install -e /path/to/dd-logging
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from dd_logging import setup_logging, get_logger, disable_logging
|
|
22
|
+
|
|
23
|
+
# 1. Call once per process (CLI entry point or app startup)
|
|
24
|
+
log_path = setup_logging(
|
|
25
|
+
"run",
|
|
26
|
+
root_name="my_app", # top-level logger namespace
|
|
27
|
+
adapter="openrouter", # appended to filename (optional)
|
|
28
|
+
log_level="info", # debug | info | warning | error
|
|
29
|
+
)
|
|
30
|
+
# → logs/run-openrouter-20260215-143022.log
|
|
31
|
+
|
|
32
|
+
# 2. In each module
|
|
33
|
+
_log = get_logger("nodes.text2spl", root_name="my_app")
|
|
34
|
+
_log.info("translating query len=%d", len(query))
|
|
35
|
+
|
|
36
|
+
# 3. Silence all logging (e.g. --no-log CLI flag)
|
|
37
|
+
disable_logging("my_app")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Thin wrapper pattern (recommended)
|
|
41
|
+
|
|
42
|
+
Each project wraps `dd_logging` so call-sites never pass `root_name`:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# myapp/logging_config.py
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from dd_logging import setup_logging as _setup, get_logger as _get, disable_logging as _disable
|
|
48
|
+
|
|
49
|
+
_ROOT = "my_app"
|
|
50
|
+
LOG_DIR = Path(__file__).resolve().parent.parent / "logs"
|
|
51
|
+
|
|
52
|
+
def get_logger(name: str):
|
|
53
|
+
return _get(name, _ROOT)
|
|
54
|
+
|
|
55
|
+
def setup_logging(run_name: str, **kw):
|
|
56
|
+
kw.setdefault("log_dir", LOG_DIR)
|
|
57
|
+
return _setup(run_name, root_name=_ROOT, **kw)
|
|
58
|
+
|
|
59
|
+
def disable_logging():
|
|
60
|
+
return _disable(_ROOT)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Log file naming
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
<log_dir>/<run_name>[-<adapter>]-<YYYYMMDD-HHMMSS>.log
|
|
67
|
+
|
|
68
|
+
logs/run-openrouter-20260215-143022.log
|
|
69
|
+
logs/benchmark-claude_cli-20260215-144500.log
|
|
70
|
+
logs/generate-20260215-145001.log
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Logger hierarchy
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
my_app ← root (FileHandler attached here)
|
|
77
|
+
├── my_app.api ← get_logger("api", "my_app")
|
|
78
|
+
├── my_app.nodes.text2spl ← get_logger("nodes.text2spl", "my_app")
|
|
79
|
+
└── my_app.flows ← get_logger("flows", "my_app")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
All child loggers inherit the root's handler — no per-module handler setup needed.
|
|
83
|
+
|
|
84
|
+
## Design notes
|
|
85
|
+
|
|
86
|
+
- **`propagate=False`** — prevents duplicate output when a root Python logger
|
|
87
|
+
handler is already configured (e.g. Streamlit, pytest, Jupyter).
|
|
88
|
+
- **Stale-handler removal** — calling `setup_logging()` multiple times in one
|
|
89
|
+
process (e.g. test suites) is safe; old `FileHandler`s are replaced.
|
|
90
|
+
- **No third-party dependencies** — stdlib `logging` only.
|
|
91
|
+
|
|
92
|
+
## Projects using dd-logging
|
|
93
|
+
|
|
94
|
+
| Project | Root name | Log dir |
|
|
95
|
+
|---------|-----------|---------|
|
|
96
|
+
| [spl-llm](https://github.com/digital-duck/SPL) | `spl` | `SPL/logs/` |
|
|
97
|
+
| [spl-flow](https://github.com/digital-duck/SPL-Flow) | `spl_flow` | `SPL-Flow/logs/` |
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""dd-logging — shared logging helpers for Digital Duck projects."""
|
|
2
|
+
from dd_logging.core import (
|
|
3
|
+
FORMATTER,
|
|
4
|
+
LOG_LEVELS,
|
|
5
|
+
disable_logging,
|
|
6
|
+
get_logger,
|
|
7
|
+
setup_logging,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"setup_logging",
|
|
14
|
+
"get_logger",
|
|
15
|
+
"disable_logging",
|
|
16
|
+
"LOG_LEVELS",
|
|
17
|
+
"FORMATTER",
|
|
18
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Core logging helpers shared across Digital Duck projects.
|
|
2
|
+
|
|
3
|
+
Provides a consistent, timestamped-file logging pattern for CLI tools and
|
|
4
|
+
Streamlit apps. Each project supplies its own *root_name* (the top-level
|
|
5
|
+
logger namespace) so hierarchies stay isolated:
|
|
6
|
+
|
|
7
|
+
spl → spl / spl.executor (spl-llm package)
|
|
8
|
+
spl_flow → spl_flow / spl_flow.nodes.* (spl-flow package)
|
|
9
|
+
|
|
10
|
+
Log file naming convention
|
|
11
|
+
--------------------------
|
|
12
|
+
<log_dir>/<run_name>[-<adapter>]-<YYYYMMDD-HHMMSS>.log
|
|
13
|
+
e.g. logs/run-openrouter-20260215-143022.log
|
|
14
|
+
logs/benchmark-claude_cli-20260215-144500.log
|
|
15
|
+
logs/generate-20260215-145001.log
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# ── Constants ──────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
LOG_LEVELS: dict[str, int] = {
|
|
26
|
+
"debug": logging.DEBUG,
|
|
27
|
+
"info": logging.INFO,
|
|
28
|
+
"warning": logging.WARNING,
|
|
29
|
+
"error": logging.ERROR,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
FORMATTER = logging.Formatter(
|
|
33
|
+
"%(asctime)s %(levelname)-7s %(name)s %(message)s",
|
|
34
|
+
datefmt="%H:%M:%S",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# ── Public API ─────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
def setup_logging(
|
|
40
|
+
run_name: str,
|
|
41
|
+
*,
|
|
42
|
+
root_name: str,
|
|
43
|
+
adapter: str = "",
|
|
44
|
+
log_level: str = "info",
|
|
45
|
+
log_dir: Path | str | None = None,
|
|
46
|
+
console: bool = False,
|
|
47
|
+
) -> Path:
|
|
48
|
+
"""Attach a timestamped FileHandler to *root_name* logger.
|
|
49
|
+
|
|
50
|
+
Safe to call multiple times in one process — stale FileHandlers from
|
|
51
|
+
a previous call are removed before the new one is attached.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
run_name : short label for the filename, e.g. ``"run"``, ``"benchmark"``
|
|
56
|
+
root_name : root logger namespace, e.g. ``"spl"`` or ``"spl_flow"``
|
|
57
|
+
adapter : LLM adapter name appended to the filename (omitted if empty)
|
|
58
|
+
log_level : ``"debug"`` | ``"info"`` | ``"warning"`` | ``"error"``
|
|
59
|
+
log_dir : directory for log files; defaults to ``./logs`` relative to CWD
|
|
60
|
+
console : also attach a StreamHandler (useful in CLI --verbose mode)
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
Path absolute path of the log file created
|
|
65
|
+
"""
|
|
66
|
+
target_dir = Path(log_dir) if log_dir else Path.cwd() / "logs"
|
|
67
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
|
|
69
|
+
dt_str = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
70
|
+
suffix = f"-{adapter}" if adapter else ""
|
|
71
|
+
log_path = target_dir / f"{run_name}{suffix}-{dt_str}.log"
|
|
72
|
+
|
|
73
|
+
level = LOG_LEVELS.get(log_level.lower(), logging.INFO)
|
|
74
|
+
|
|
75
|
+
root = logging.getLogger(root_name)
|
|
76
|
+
root.setLevel(logging.DEBUG) # capture everything; handlers filter
|
|
77
|
+
|
|
78
|
+
# Remove stale FileHandlers from a previous setup_logging() call
|
|
79
|
+
root.handlers = [h for h in root.handlers if not isinstance(h, logging.FileHandler)]
|
|
80
|
+
|
|
81
|
+
fh = logging.FileHandler(log_path, encoding="utf-8")
|
|
82
|
+
fh.setLevel(level)
|
|
83
|
+
fh.setFormatter(FORMATTER)
|
|
84
|
+
root.addHandler(fh)
|
|
85
|
+
|
|
86
|
+
if console:
|
|
87
|
+
ch = logging.StreamHandler()
|
|
88
|
+
ch.setLevel(level)
|
|
89
|
+
ch.setFormatter(FORMATTER)
|
|
90
|
+
root.addHandler(ch)
|
|
91
|
+
|
|
92
|
+
# Prevent propagation to the Python root logger — avoids duplicate output
|
|
93
|
+
# in environments that configure their own root handler (e.g. Streamlit).
|
|
94
|
+
root.propagate = False
|
|
95
|
+
|
|
96
|
+
return log_path
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_logger(name: str, root_name: str) -> logging.Logger:
|
|
100
|
+
"""Return a child logger under *root_name*.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
name : dotted sub-path, e.g. ``"nodes.text2spl"`` or ``"executor"``
|
|
105
|
+
root_name : root logger namespace, e.g. ``"spl"`` or ``"spl_flow"``
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
logging.Logger named ``<root_name>.<name>``
|
|
110
|
+
"""
|
|
111
|
+
return logging.getLogger(f"{root_name}.{name}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def disable_logging(root_name: str) -> None:
|
|
115
|
+
"""Remove all handlers from *root_name* logger (no-op log mode)."""
|
|
116
|
+
root = logging.getLogger(root_name)
|
|
117
|
+
root.handlers.clear()
|
|
118
|
+
root.propagate = False
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dd-logging
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared logging helpers for Digital Duck projects (spl-llm, spl-flow, …)
|
|
5
|
+
Author-email: Wen Gong <wen.gong.research@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/digital-duck/dd-logging
|
|
8
|
+
Project-URL: Repository, https://github.com/digital-duck/dd-logging
|
|
9
|
+
Project-URL: Issues, https://github.com/digital-duck/dd-logging/issues
|
|
10
|
+
Keywords: logging,digital-duck,spl
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: System :: Logging
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# dd-logging
|
|
24
|
+
|
|
25
|
+
Shared logging helpers for [Digital Duck](https://github.com/digital-duck) projects.
|
|
26
|
+
|
|
27
|
+
Provides a consistent, timestamped-file logging pattern for CLI tools and Streamlit apps.
|
|
28
|
+
Used by **spl-llm** and **spl-flow**; designed to be reused by any project in the ecosystem.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# from PyPI (once published)
|
|
34
|
+
pip install dd-logging
|
|
35
|
+
|
|
36
|
+
# local editable install (for development)
|
|
37
|
+
pip install -e /path/to/dd-logging
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from dd_logging import setup_logging, get_logger, disable_logging
|
|
44
|
+
|
|
45
|
+
# 1. Call once per process (CLI entry point or app startup)
|
|
46
|
+
log_path = setup_logging(
|
|
47
|
+
"run",
|
|
48
|
+
root_name="my_app", # top-level logger namespace
|
|
49
|
+
adapter="openrouter", # appended to filename (optional)
|
|
50
|
+
log_level="info", # debug | info | warning | error
|
|
51
|
+
)
|
|
52
|
+
# → logs/run-openrouter-20260215-143022.log
|
|
53
|
+
|
|
54
|
+
# 2. In each module
|
|
55
|
+
_log = get_logger("nodes.text2spl", root_name="my_app")
|
|
56
|
+
_log.info("translating query len=%d", len(query))
|
|
57
|
+
|
|
58
|
+
# 3. Silence all logging (e.g. --no-log CLI flag)
|
|
59
|
+
disable_logging("my_app")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Thin wrapper pattern (recommended)
|
|
63
|
+
|
|
64
|
+
Each project wraps `dd_logging` so call-sites never pass `root_name`:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# myapp/logging_config.py
|
|
68
|
+
from pathlib import Path
|
|
69
|
+
from dd_logging import setup_logging as _setup, get_logger as _get, disable_logging as _disable
|
|
70
|
+
|
|
71
|
+
_ROOT = "my_app"
|
|
72
|
+
LOG_DIR = Path(__file__).resolve().parent.parent / "logs"
|
|
73
|
+
|
|
74
|
+
def get_logger(name: str):
|
|
75
|
+
return _get(name, _ROOT)
|
|
76
|
+
|
|
77
|
+
def setup_logging(run_name: str, **kw):
|
|
78
|
+
kw.setdefault("log_dir", LOG_DIR)
|
|
79
|
+
return _setup(run_name, root_name=_ROOT, **kw)
|
|
80
|
+
|
|
81
|
+
def disable_logging():
|
|
82
|
+
return _disable(_ROOT)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Log file naming
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
<log_dir>/<run_name>[-<adapter>]-<YYYYMMDD-HHMMSS>.log
|
|
89
|
+
|
|
90
|
+
logs/run-openrouter-20260215-143022.log
|
|
91
|
+
logs/benchmark-claude_cli-20260215-144500.log
|
|
92
|
+
logs/generate-20260215-145001.log
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Logger hierarchy
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
my_app ← root (FileHandler attached here)
|
|
99
|
+
├── my_app.api ← get_logger("api", "my_app")
|
|
100
|
+
├── my_app.nodes.text2spl ← get_logger("nodes.text2spl", "my_app")
|
|
101
|
+
└── my_app.flows ← get_logger("flows", "my_app")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
All child loggers inherit the root's handler — no per-module handler setup needed.
|
|
105
|
+
|
|
106
|
+
## Design notes
|
|
107
|
+
|
|
108
|
+
- **`propagate=False`** — prevents duplicate output when a root Python logger
|
|
109
|
+
handler is already configured (e.g. Streamlit, pytest, Jupyter).
|
|
110
|
+
- **Stale-handler removal** — calling `setup_logging()` multiple times in one
|
|
111
|
+
process (e.g. test suites) is safe; old `FileHandler`s are replaced.
|
|
112
|
+
- **No third-party dependencies** — stdlib `logging` only.
|
|
113
|
+
|
|
114
|
+
## Projects using dd-logging
|
|
115
|
+
|
|
116
|
+
| Project | Root name | Log dir |
|
|
117
|
+
|---------|-----------|---------|
|
|
118
|
+
| [spl-llm](https://github.com/digital-duck/SPL) | `spl` | `SPL/logs/` |
|
|
119
|
+
| [spl-flow](https://github.com/digital-duck/SPL-Flow) | `spl_flow` | `SPL-Flow/logs/` |
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dd_logging
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dd-logging"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared logging helpers for Digital Duck projects (spl-llm, spl-flow, …)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Wen Gong", email = "wen.gong.research@gmail.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["logging", "digital-duck", "spl"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: System :: Logging",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/digital-duck/dd-logging"
|
|
28
|
+
Repository = "https://github.com/digital-duck/dd-logging"
|
|
29
|
+
Issues = "https://github.com/digital-duck/dd-logging/issues"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["."]
|
|
33
|
+
include = ["dd_logging*"]
|