instruktai-python-logger 0.1.0__tar.gz → 0.1.1__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.
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/PKG-INFO +12 -2
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/README.md +11 -1
- instruktai_python_logger-0.1.1/instrukt_ai_logging/__init__.py +19 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instrukt_ai_logging/logging.py +86 -16
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/PKG-INFO +12 -2
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/pyproject.toml +1 -1
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/tests/test_configure_logging.py +7 -29
- instruktai_python_logger-0.1.0/instrukt_ai_logging/__init__.py +0 -14
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instrukt_ai_logging/cli.py +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/SOURCES.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/dependency_links.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/entry_points.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/requires.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instruktai_python_logger.egg-info/top_level.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instruktai-python-logger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Centralized logging utilities for InstruktAI Python services.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -34,9 +34,19 @@ pip install git+ssh://git@github.com/InstruktAI/python-logger.git
|
|
|
34
34
|
## API
|
|
35
35
|
|
|
36
36
|
- Python entrypoint: `instrukt_ai_logging.configure_logging(...)`
|
|
37
|
-
-
|
|
37
|
+
- Logger helper: `instrukt_ai_logging.get_logger(name)` (named `**kv` logging)
|
|
38
38
|
- CLI entrypoint: `instrukt-ai-logs` (reads recent log lines)
|
|
39
39
|
|
|
40
|
+
Example:
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
from instrukt_ai_logging import configure_logging, get_logger
|
|
44
|
+
|
|
45
|
+
configure_logging(name="teleclaude", app_logger_prefix="teleclaude")
|
|
46
|
+
logger = get_logger("teleclaude.core")
|
|
47
|
+
logger.info("job_started", job_id="abc123")
|
|
48
|
+
```
|
|
49
|
+
|
|
40
50
|
## Environment variables (contract)
|
|
41
51
|
|
|
42
52
|
Per-app prefix model (example uses `TELECLAUDE_`):
|
|
@@ -24,9 +24,19 @@ pip install git+ssh://git@github.com/InstruktAI/python-logger.git
|
|
|
24
24
|
## API
|
|
25
25
|
|
|
26
26
|
- Python entrypoint: `instrukt_ai_logging.configure_logging(...)`
|
|
27
|
-
-
|
|
27
|
+
- Logger helper: `instrukt_ai_logging.get_logger(name)` (named `**kv` logging)
|
|
28
28
|
- CLI entrypoint: `instrukt-ai-logs` (reads recent log lines)
|
|
29
29
|
|
|
30
|
+
Example:
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
from instrukt_ai_logging import configure_logging, get_logger
|
|
34
|
+
|
|
35
|
+
configure_logging(name="teleclaude", app_logger_prefix="teleclaude")
|
|
36
|
+
logger = get_logger("teleclaude.core")
|
|
37
|
+
logger.info("job_started", job_id="abc123")
|
|
38
|
+
```
|
|
39
|
+
|
|
30
40
|
## Environment variables (contract)
|
|
31
41
|
|
|
32
42
|
Per-app prefix model (example uses `TELECLAUDE_`):
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""InstruktAI logging standard library.
|
|
2
|
+
|
|
3
|
+
See `README.md` for usage and `docs/design.md` for design intent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"__version__",
|
|
8
|
+
"configure_logging",
|
|
9
|
+
"get_logger",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from importlib.metadata import version as _pkg_version
|
|
14
|
+
|
|
15
|
+
__version__ = _pkg_version("instruktai-python-logger")
|
|
16
|
+
except Exception: # pragma: no cover
|
|
17
|
+
__version__ = "0.0.0"
|
|
18
|
+
|
|
19
|
+
from instrukt_ai_logging.logging import configure_logging, get_logger # noqa: E402 (intentional re-export)
|
{instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instrukt_ai_logging/logging.py
RENAMED
|
@@ -12,6 +12,30 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def _normalize_env_prefix(name: str) -> str:
|
|
16
|
+
# TELECLAUDE, MY_APP, etc.
|
|
17
|
+
raw = name.strip().upper()
|
|
18
|
+
if not raw:
|
|
19
|
+
raise ValueError("name must be non-empty")
|
|
20
|
+
raw = re.sub(r"[^A-Z0-9]+", "_", raw)
|
|
21
|
+
raw = re.sub(r"_+", "_", raw).strip("_")
|
|
22
|
+
if not raw:
|
|
23
|
+
raise ValueError("name did not produce a valid env prefix")
|
|
24
|
+
return raw
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_app_name(name: str) -> str:
|
|
28
|
+
# teleclaude, my-app, etc.
|
|
29
|
+
raw = name.strip().lower()
|
|
30
|
+
if not raw:
|
|
31
|
+
raise ValueError("name must be non-empty")
|
|
32
|
+
raw = re.sub(r"[^a-z0-9]+", "-", raw)
|
|
33
|
+
raw = re.sub(r"-+", "-", raw).strip("-")
|
|
34
|
+
if not raw:
|
|
35
|
+
raise ValueError("name did not produce a valid app name")
|
|
36
|
+
return raw
|
|
37
|
+
|
|
38
|
+
|
|
15
39
|
def _level_name_to_int(level_name: str, default: int) -> int:
|
|
16
40
|
name = level_name.strip().upper()
|
|
17
41
|
if not name:
|
|
@@ -143,24 +167,60 @@ class LogfmtFormatter(UtcMillisFormatter):
|
|
|
143
167
|
return " ".join(parts)
|
|
144
168
|
|
|
145
169
|
|
|
146
|
-
def
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
raise ValueError('log_kv requires a "msg" key')
|
|
153
|
-
|
|
154
|
-
msg = str(data["msg"])
|
|
155
|
-
kv: dict[str, object] = {}
|
|
156
|
-
for key, value in data.items():
|
|
170
|
+
def _log_kv(logger: logging.Logger, level: int, msg: str, kv: Mapping[str, Any]) -> None:
|
|
171
|
+
"""Internal helper: attach `kv` pairs without forcing callers to use `extra`."""
|
|
172
|
+
safe: dict[str, object] = {}
|
|
173
|
+
for key, value in kv.items():
|
|
174
|
+
if not isinstance(key, str):
|
|
175
|
+
continue
|
|
157
176
|
if key == "msg":
|
|
158
177
|
continue
|
|
159
178
|
if not _SAFE_KEY.fullmatch(key):
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
continue
|
|
180
|
+
safe[key] = value
|
|
181
|
+
|
|
182
|
+
logger.log(level, msg, extra={"kv": safe})
|
|
162
183
|
|
|
163
|
-
|
|
184
|
+
|
|
185
|
+
class KVLogger:
|
|
186
|
+
"""Ergonomic key/value logger.
|
|
187
|
+
|
|
188
|
+
Callers use `KVLogger` like a normal logger:
|
|
189
|
+
logger.info("event_name", job_id=..., user_id=...)
|
|
190
|
+
|
|
191
|
+
This keeps call sites human-friendly while still producing strict logfmt pairs.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(self, logger: logging.Logger) -> None:
|
|
195
|
+
self._logger = logger
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def name(self) -> str:
|
|
199
|
+
return self._logger.name
|
|
200
|
+
|
|
201
|
+
def debug(self, msg: str, **kv: Any) -> None:
|
|
202
|
+
_log_kv(self._logger, logging.DEBUG, msg, kv)
|
|
203
|
+
|
|
204
|
+
def info(self, msg: str, **kv: Any) -> None:
|
|
205
|
+
_log_kv(self._logger, logging.INFO, msg, kv)
|
|
206
|
+
|
|
207
|
+
def warning(self, msg: str, **kv: Any) -> None:
|
|
208
|
+
_log_kv(self._logger, logging.WARNING, msg, kv)
|
|
209
|
+
|
|
210
|
+
def error(self, msg: str, **kv: Any) -> None:
|
|
211
|
+
_log_kv(self._logger, logging.ERROR, msg, kv)
|
|
212
|
+
|
|
213
|
+
def exception(self, msg: str, **kv: Any) -> None:
|
|
214
|
+
# Match logging.exception behavior (includes exc_info=True).
|
|
215
|
+
self._logger.exception(msg, extra={"kv": {k: v for k, v in kv.items() if _SAFE_KEY.fullmatch(k)}})
|
|
216
|
+
|
|
217
|
+
def log(self, level: int, msg: str, **kv: Any) -> None:
|
|
218
|
+
_log_kv(self._logger, level, msg, kv)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get_logger(name: str) -> KVLogger:
|
|
222
|
+
"""Return a `KVLogger` wrapper for the named logger."""
|
|
223
|
+
return KVLogger(logging.getLogger(name))
|
|
164
224
|
|
|
165
225
|
|
|
166
226
|
class _ThirdPartySelectorFilter(logging.Filter):
|
|
@@ -226,9 +286,10 @@ def _ensure_log_dir(log_dir: Path) -> None:
|
|
|
226
286
|
|
|
227
287
|
def configure_logging(
|
|
228
288
|
*,
|
|
229
|
-
env_prefix: str,
|
|
230
289
|
app_logger_prefix: str,
|
|
231
|
-
|
|
290
|
+
name: str | None = None,
|
|
291
|
+
env_prefix: str | None = None,
|
|
292
|
+
app_name: str | None = None,
|
|
232
293
|
log_filename: str | None = None,
|
|
233
294
|
max_message_chars: int = 4000,
|
|
234
295
|
) -> Path:
|
|
@@ -236,6 +297,15 @@ def configure_logging(
|
|
|
236
297
|
|
|
237
298
|
Returns the resolved log file path in use.
|
|
238
299
|
"""
|
|
300
|
+
if name is not None:
|
|
301
|
+
if env_prefix is not None or app_name is not None:
|
|
302
|
+
raise ValueError("Pass either name= OR env_prefix/app_name (not both)")
|
|
303
|
+
env_prefix = _normalize_env_prefix(name)
|
|
304
|
+
app_name = _normalize_app_name(name)
|
|
305
|
+
|
|
306
|
+
if env_prefix is None or app_name is None:
|
|
307
|
+
raise ValueError("Missing required config: name= OR both env_prefix= and app_name=")
|
|
308
|
+
|
|
239
309
|
contract = LoggingContract(
|
|
240
310
|
env_prefix=env_prefix,
|
|
241
311
|
app_logger_prefix=app_logger_prefix,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instruktai-python-logger
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Centralized logging utilities for InstruktAI Python services.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -34,9 +34,19 @@ pip install git+ssh://git@github.com/InstruktAI/python-logger.git
|
|
|
34
34
|
## API
|
|
35
35
|
|
|
36
36
|
- Python entrypoint: `instrukt_ai_logging.configure_logging(...)`
|
|
37
|
-
-
|
|
37
|
+
- Logger helper: `instrukt_ai_logging.get_logger(name)` (named `**kv` logging)
|
|
38
38
|
- CLI entrypoint: `instrukt-ai-logs` (reads recent log lines)
|
|
39
39
|
|
|
40
|
+
Example:
|
|
41
|
+
|
|
42
|
+
```py
|
|
43
|
+
from instrukt_ai_logging import configure_logging, get_logger
|
|
44
|
+
|
|
45
|
+
configure_logging(name="teleclaude", app_logger_prefix="teleclaude")
|
|
46
|
+
logger = get_logger("teleclaude.core")
|
|
47
|
+
logger.info("job_started", job_id="abc123")
|
|
48
|
+
```
|
|
49
|
+
|
|
40
50
|
## Environment variables (contract)
|
|
41
51
|
|
|
42
52
|
Per-app prefix model (example uses `TELECLAUDE_`):
|
{instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/tests/test_configure_logging.py
RENAMED
|
@@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from instrukt_ai_logging import configure_logging,
|
|
7
|
+
from instrukt_ai_logging import configure_logging, get_logger
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@pytest.fixture()
|
|
@@ -36,9 +36,8 @@ def test_our_logs_respect_app_level_and_third_party_baseline(isolated_logging, m
|
|
|
36
36
|
monkeypatch.delenv("TELECLAUDE_THIRD_PARTY_LOGGERS", raising=False)
|
|
37
37
|
|
|
38
38
|
log_path = configure_logging(
|
|
39
|
-
env_prefix="TELECLAUDE",
|
|
40
39
|
app_logger_prefix="teleclaude",
|
|
41
|
-
|
|
40
|
+
name="teleclaude",
|
|
42
41
|
)
|
|
43
42
|
|
|
44
43
|
logging.getLogger("teleclaude.core").debug("hello from ours")
|
|
@@ -58,9 +57,8 @@ def test_spotlight_allows_selected_third_party_only(isolated_logging, monkeypatc
|
|
|
58
57
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOGGERS", "httpcore")
|
|
59
58
|
|
|
60
59
|
log_path = configure_logging(
|
|
61
|
-
env_prefix="TELECLAUDE",
|
|
62
60
|
app_logger_prefix="teleclaude",
|
|
63
|
-
|
|
61
|
+
name="teleclaude",
|
|
64
62
|
)
|
|
65
63
|
|
|
66
64
|
# Ensure records are actually created even though root is WARNING in spotlight mode.
|
|
@@ -85,39 +83,19 @@ def test_spotlight_allows_selected_third_party_only(isolated_logging, monkeypatc
|
|
|
85
83
|
assert "telegram info" not in content
|
|
86
84
|
|
|
87
85
|
|
|
88
|
-
def
|
|
89
|
-
with TemporaryDirectory() as tmp:
|
|
90
|
-
monkeypatch.setenv("INSTRUKT_AI_LOG_ROOT", tmp)
|
|
91
|
-
monkeypatch.setenv("TELECLAUDE_LOG_LEVEL", "INFO")
|
|
92
|
-
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "WARNING")
|
|
93
|
-
|
|
94
|
-
configure_logging(
|
|
95
|
-
env_prefix="TELECLAUDE",
|
|
96
|
-
app_logger_prefix="teleclaude",
|
|
97
|
-
app_name="teleclaude",
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
with pytest.raises(ValueError):
|
|
101
|
-
log_kv(logging.getLogger("teleclaude.core"), logging.INFO, {"session": "abc"})
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def test_log_kv_emits_pairs(isolated_logging, monkeypatch):
|
|
86
|
+
def test_named_kv_logger_emits_pairs(isolated_logging, monkeypatch):
|
|
105
87
|
with TemporaryDirectory() as tmp:
|
|
106
88
|
monkeypatch.setenv("INSTRUKT_AI_LOG_ROOT", tmp)
|
|
107
89
|
monkeypatch.setenv("TELECLAUDE_LOG_LEVEL", "INFO")
|
|
108
90
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "WARNING")
|
|
109
91
|
|
|
110
92
|
log_path = configure_logging(
|
|
111
|
-
env_prefix="TELECLAUDE",
|
|
112
93
|
app_logger_prefix="teleclaude",
|
|
113
|
-
|
|
94
|
+
name="teleclaude",
|
|
114
95
|
)
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
logging.INFO,
|
|
119
|
-
{"msg": "hello", "session": "abc123", "n": 1},
|
|
120
|
-
)
|
|
97
|
+
logger = get_logger("teleclaude.core")
|
|
98
|
+
logger.info("hello", session="abc123", n=1)
|
|
121
99
|
|
|
122
100
|
content = _read_text(log_path)
|
|
123
101
|
assert 'msg="hello"' in content
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"""InstruktAI logging standard library.
|
|
2
|
-
|
|
3
|
-
See `README.md` for usage and `docs/design.md` for design intent.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
__all__ = [
|
|
7
|
-
"__version__",
|
|
8
|
-
"configure_logging",
|
|
9
|
-
"log_kv",
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
__version__ = "0.0.0"
|
|
13
|
-
|
|
14
|
-
from instrukt_ai_logging.logging import configure_logging, log_kv # noqa: E402 (intentional re-export)
|
{instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.1}/instrukt_ai_logging/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|