instruktai-python-logger 0.1.0__tar.gz → 0.1.0.post1__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.0.post1}/PKG-INFO +13 -2
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/README.md +12 -1
- instruktai_python_logger-0.1.0.post1/instrukt_ai_logging/__init__.py +19 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instrukt_ai_logging/logging.py +118 -19
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/PKG-INFO +13 -2
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/pyproject.toml +1 -1
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/tests/test_configure_logging.py +6 -38
- instruktai_python_logger-0.1.0/instrukt_ai_logging/__init__.py +0 -14
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instrukt_ai_logging/cli.py +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/SOURCES.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/dependency_links.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/entry_points.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/requires.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/instruktai_python_logger.egg-info/top_level.txt +0 -0
- {instruktai_python_logger-0.1.0 → instruktai_python_logger-0.1.0.post1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instruktai-python-logger
|
|
3
|
-
Version: 0.1.0
|
|
3
|
+
Version: 0.1.0.post1
|
|
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,20 @@ 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
|
+
import logging
|
|
44
|
+
from instrukt_ai_logging import configure_logging
|
|
45
|
+
|
|
46
|
+
configure_logging("teleclaude")
|
|
47
|
+
logger = logging.getLogger("teleclaude.core")
|
|
48
|
+
logger.info("job_started", job_id="abc123", user_id=123)
|
|
49
|
+
```
|
|
50
|
+
|
|
40
51
|
## Environment variables (contract)
|
|
41
52
|
|
|
42
53
|
Per-app prefix model (example uses `TELECLAUDE_`):
|
|
@@ -24,9 +24,20 @@ 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
|
+
import logging
|
|
34
|
+
from instrukt_ai_logging import configure_logging
|
|
35
|
+
|
|
36
|
+
configure_logging("teleclaude")
|
|
37
|
+
logger = logging.getLogger("teleclaude.core")
|
|
38
|
+
logger.info("job_started", job_id="abc123", user_id=123)
|
|
39
|
+
```
|
|
40
|
+
|
|
30
41
|
## Environment variables (contract)
|
|
31
42
|
|
|
32
43
|
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)
|
|
@@ -4,7 +4,6 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
import sys
|
|
7
|
-
from collections.abc import Mapping
|
|
8
7
|
from dataclasses import dataclass
|
|
9
8
|
from datetime import datetime, timedelta, timezone
|
|
10
9
|
from logging.handlers import WatchedFileHandler
|
|
@@ -12,6 +11,42 @@ from pathlib import Path
|
|
|
12
11
|
from typing import Any
|
|
13
12
|
|
|
14
13
|
|
|
14
|
+
def _normalize_env_prefix(name: str) -> str:
|
|
15
|
+
# TELECLAUDE, MY_APP, etc.
|
|
16
|
+
raw = name.strip().upper()
|
|
17
|
+
if not raw:
|
|
18
|
+
raise ValueError("name must be non-empty")
|
|
19
|
+
raw = re.sub(r"[^A-Z0-9]+", "_", raw)
|
|
20
|
+
raw = re.sub(r"_+", "_", raw).strip("_")
|
|
21
|
+
if not raw:
|
|
22
|
+
raise ValueError("name did not produce a valid env prefix")
|
|
23
|
+
return raw
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _normalize_app_name(name: str) -> str:
|
|
27
|
+
# teleclaude, my-app, etc.
|
|
28
|
+
raw = name.strip().lower()
|
|
29
|
+
if not raw:
|
|
30
|
+
raise ValueError("name must be non-empty")
|
|
31
|
+
raw = re.sub(r"[^a-z0-9]+", "-", raw)
|
|
32
|
+
raw = re.sub(r"-+", "-", raw).strip("-")
|
|
33
|
+
if not raw:
|
|
34
|
+
raise ValueError("name did not produce a valid app name")
|
|
35
|
+
return raw
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _normalize_logger_prefix(name: str) -> str:
|
|
39
|
+
# teleclaude, crypto_ai, etc. (match Python package style)
|
|
40
|
+
raw = name.strip().lower()
|
|
41
|
+
if not raw:
|
|
42
|
+
raise ValueError("name must be non-empty")
|
|
43
|
+
raw = re.sub(r"[^a-z0-9]+", "_", raw)
|
|
44
|
+
raw = re.sub(r"_+", "_", raw).strip("_")
|
|
45
|
+
if not raw:
|
|
46
|
+
raise ValueError("name did not produce a valid logger prefix")
|
|
47
|
+
return raw
|
|
48
|
+
|
|
49
|
+
|
|
15
50
|
def _level_name_to_int(level_name: str, default: int) -> int:
|
|
16
51
|
name = level_name.strip().upper()
|
|
17
52
|
if not name:
|
|
@@ -143,24 +178,79 @@ class LogfmtFormatter(UtcMillisFormatter):
|
|
|
143
178
|
return " ".join(parts)
|
|
144
179
|
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
"""
|
|
181
|
+
class InstruktLogger(logging.Logger):
|
|
182
|
+
"""Logger that accepts arbitrary `**kv` fields.
|
|
183
|
+
|
|
184
|
+
This lets callers use normal logger methods with named key/value pairs:
|
|
185
|
+
logging.getLogger("...").info("event_name", job_id=..., user_id=...)
|
|
148
186
|
|
|
149
|
-
`
|
|
187
|
+
All `**kv` values are serialized to text by the formatter.
|
|
150
188
|
"""
|
|
151
|
-
if "msg" not in data:
|
|
152
|
-
raise ValueError('log_kv requires a "msg" key')
|
|
153
189
|
|
|
154
|
-
msg
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
kv[
|
|
190
|
+
def _log_with_kv(self, level: int, msg: object, args: tuple[object, ...], **kwargs: Any) -> None:
|
|
191
|
+
exc_info = kwargs.pop("exc_info", None)
|
|
192
|
+
stack_info = kwargs.pop("stack_info", False)
|
|
193
|
+
stacklevel = kwargs.pop("stacklevel", 1)
|
|
194
|
+
extra = kwargs.pop("extra", None)
|
|
195
|
+
|
|
196
|
+
# Remaining kwargs are treated as `kv`.
|
|
197
|
+
kv: dict[str, object] = {k: v for k, v in kwargs.items() if _SAFE_KEY.fullmatch(k)}
|
|
198
|
+
|
|
199
|
+
merged_extra: dict[str, object] = {}
|
|
200
|
+
if isinstance(extra, dict):
|
|
201
|
+
merged_extra.update(extra)
|
|
202
|
+
|
|
203
|
+
existing_kv = merged_extra.get("kv")
|
|
204
|
+
if isinstance(existing_kv, dict):
|
|
205
|
+
merged_extra["kv"] = {**existing_kv, **kv}
|
|
206
|
+
else:
|
|
207
|
+
merged_extra["kv"] = kv
|
|
208
|
+
|
|
209
|
+
super()._log(
|
|
210
|
+
level,
|
|
211
|
+
msg,
|
|
212
|
+
args,
|
|
213
|
+
exc_info=exc_info,
|
|
214
|
+
extra=merged_extra,
|
|
215
|
+
stack_info=stack_info,
|
|
216
|
+
stacklevel=stacklevel,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def debug(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
220
|
+
if self.isEnabledFor(logging.DEBUG):
|
|
221
|
+
self._log_with_kv(logging.DEBUG, msg, args, **kwargs)
|
|
222
|
+
|
|
223
|
+
def info(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
224
|
+
if self.isEnabledFor(logging.INFO):
|
|
225
|
+
self._log_with_kv(logging.INFO, msg, args, **kwargs)
|
|
226
|
+
|
|
227
|
+
def warning(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
228
|
+
if self.isEnabledFor(logging.WARNING):
|
|
229
|
+
self._log_with_kv(logging.WARNING, msg, args, **kwargs)
|
|
230
|
+
|
|
231
|
+
def error(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
232
|
+
if self.isEnabledFor(logging.ERROR):
|
|
233
|
+
self._log_with_kv(logging.ERROR, msg, args, **kwargs)
|
|
162
234
|
|
|
163
|
-
|
|
235
|
+
def critical(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
236
|
+
if self.isEnabledFor(logging.CRITICAL):
|
|
237
|
+
self._log_with_kv(logging.CRITICAL, msg, args, **kwargs)
|
|
238
|
+
|
|
239
|
+
def exception(self, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
240
|
+
kwargs.setdefault("exc_info", True)
|
|
241
|
+
if self.isEnabledFor(logging.ERROR):
|
|
242
|
+
self._log_with_kv(logging.ERROR, msg, args, **kwargs)
|
|
243
|
+
|
|
244
|
+
def log(self, level: int, msg: object, *args: object, **kwargs: Any) -> None: # type: ignore[override]
|
|
245
|
+
if not isinstance(level, int):
|
|
246
|
+
raise TypeError("level must be an int")
|
|
247
|
+
if self.isEnabledFor(level):
|
|
248
|
+
self._log_with_kv(level, msg, args, **kwargs)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def get_logger(name: str) -> logging.Logger:
|
|
252
|
+
"""Compatibility helper (returns a normal logger, but with `**kv` support after configuration)."""
|
|
253
|
+
return logging.getLogger(name)
|
|
164
254
|
|
|
165
255
|
|
|
166
256
|
class _ThirdPartySelectorFilter(logging.Filter):
|
|
@@ -225,10 +315,8 @@ def _ensure_log_dir(log_dir: Path) -> None:
|
|
|
225
315
|
|
|
226
316
|
|
|
227
317
|
def configure_logging(
|
|
318
|
+
name: str,
|
|
228
319
|
*,
|
|
229
|
-
env_prefix: str,
|
|
230
|
-
app_logger_prefix: str,
|
|
231
|
-
app_name: str,
|
|
232
320
|
log_filename: str | None = None,
|
|
233
321
|
max_message_chars: int = 4000,
|
|
234
322
|
) -> Path:
|
|
@@ -236,12 +324,23 @@ def configure_logging(
|
|
|
236
324
|
|
|
237
325
|
Returns the resolved log file path in use.
|
|
238
326
|
"""
|
|
327
|
+
env_prefix = _normalize_env_prefix(name)
|
|
328
|
+
app_logger_prefix = _normalize_logger_prefix(name)
|
|
329
|
+
app_name = app_logger_prefix
|
|
330
|
+
log_filename = log_filename or f"{app_logger_prefix}.log"
|
|
331
|
+
|
|
239
332
|
contract = LoggingContract(
|
|
240
333
|
env_prefix=env_prefix,
|
|
241
334
|
app_logger_prefix=app_logger_prefix,
|
|
242
335
|
app_name=app_name,
|
|
243
336
|
)
|
|
244
337
|
|
|
338
|
+
# Ensure all loggers accept arbitrary `**kv` (no wrapper at call sites).
|
|
339
|
+
logging.setLoggerClass(InstruktLogger)
|
|
340
|
+
for obj in logging.root.manager.loggerDict.values():
|
|
341
|
+
if isinstance(obj, logging.Logger) and not isinstance(obj, InstruktLogger):
|
|
342
|
+
obj.__class__ = InstruktLogger
|
|
343
|
+
|
|
245
344
|
our_level_name = (os.getenv(contract.env_log_level) or "INFO").upper()
|
|
246
345
|
third_party_level_name = (os.getenv(contract.env_third_party_level) or "WARNING").upper()
|
|
247
346
|
spotlight = _parse_csv(os.getenv(contract.env_third_party_loggers, ""))
|
|
@@ -259,7 +358,7 @@ def configure_logging(
|
|
|
259
358
|
log_dir = _fallback_log_root(app_name)
|
|
260
359
|
_ensure_log_dir(log_dir)
|
|
261
360
|
|
|
262
|
-
log_file = log_dir /
|
|
361
|
+
log_file = log_dir / log_filename
|
|
263
362
|
|
|
264
363
|
formatter = LogfmtFormatter(max_message_chars=max_message_chars)
|
|
265
364
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: instruktai-python-logger
|
|
3
|
-
Version: 0.1.0
|
|
3
|
+
Version: 0.1.0.post1
|
|
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,20 @@ 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
|
+
import logging
|
|
44
|
+
from instrukt_ai_logging import configure_logging
|
|
45
|
+
|
|
46
|
+
configure_logging("teleclaude")
|
|
47
|
+
logger = logging.getLogger("teleclaude.core")
|
|
48
|
+
logger.info("job_started", job_id="abc123", user_id=123)
|
|
49
|
+
```
|
|
50
|
+
|
|
40
51
|
## Environment variables (contract)
|
|
41
52
|
|
|
42
53
|
Per-app prefix model (example uses `TELECLAUDE_`):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "instruktai-python-logger"
|
|
7
|
-
version = "0.1.0"
|
|
7
|
+
version = "0.1.0.post1"
|
|
8
8
|
description = "Centralized logging utilities for InstruktAI Python services."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -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
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@pytest.fixture()
|
|
@@ -35,11 +35,7 @@ def test_our_logs_respect_app_level_and_third_party_baseline(isolated_logging, m
|
|
|
35
35
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "WARNING")
|
|
36
36
|
monkeypatch.delenv("TELECLAUDE_THIRD_PARTY_LOGGERS", raising=False)
|
|
37
37
|
|
|
38
|
-
log_path = configure_logging(
|
|
39
|
-
env_prefix="TELECLAUDE",
|
|
40
|
-
app_logger_prefix="teleclaude",
|
|
41
|
-
app_name="teleclaude",
|
|
42
|
-
)
|
|
38
|
+
log_path = configure_logging("teleclaude")
|
|
43
39
|
|
|
44
40
|
logging.getLogger("teleclaude.core").debug("hello from ours")
|
|
45
41
|
logging.getLogger("httpcore.http11").info("hello from third-party")
|
|
@@ -57,11 +53,7 @@ def test_spotlight_allows_selected_third_party_only(isolated_logging, monkeypatc
|
|
|
57
53
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "INFO")
|
|
58
54
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOGGERS", "httpcore")
|
|
59
55
|
|
|
60
|
-
log_path = configure_logging(
|
|
61
|
-
env_prefix="TELECLAUDE",
|
|
62
|
-
app_logger_prefix="teleclaude",
|
|
63
|
-
app_name="teleclaude",
|
|
64
|
-
)
|
|
56
|
+
log_path = configure_logging("teleclaude")
|
|
65
57
|
|
|
66
58
|
# Ensure records are actually created even though root is WARNING in spotlight mode.
|
|
67
59
|
httpcore_logger = logging.getLogger("httpcore")
|
|
@@ -85,39 +77,15 @@ def test_spotlight_allows_selected_third_party_only(isolated_logging, monkeypatc
|
|
|
85
77
|
assert "telegram info" not in content
|
|
86
78
|
|
|
87
79
|
|
|
88
|
-
def
|
|
80
|
+
def test_named_kv_logger_emits_pairs(isolated_logging, monkeypatch):
|
|
89
81
|
with TemporaryDirectory() as tmp:
|
|
90
82
|
monkeypatch.setenv("INSTRUKT_AI_LOG_ROOT", tmp)
|
|
91
83
|
monkeypatch.setenv("TELECLAUDE_LOG_LEVEL", "INFO")
|
|
92
84
|
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "WARNING")
|
|
93
85
|
|
|
94
|
-
configure_logging(
|
|
95
|
-
env_prefix="TELECLAUDE",
|
|
96
|
-
app_logger_prefix="teleclaude",
|
|
97
|
-
app_name="teleclaude",
|
|
98
|
-
)
|
|
86
|
+
log_path = configure_logging("teleclaude")
|
|
99
87
|
|
|
100
|
-
|
|
101
|
-
log_kv(logging.getLogger("teleclaude.core"), logging.INFO, {"session": "abc"})
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def test_log_kv_emits_pairs(isolated_logging, monkeypatch):
|
|
105
|
-
with TemporaryDirectory() as tmp:
|
|
106
|
-
monkeypatch.setenv("INSTRUKT_AI_LOG_ROOT", tmp)
|
|
107
|
-
monkeypatch.setenv("TELECLAUDE_LOG_LEVEL", "INFO")
|
|
108
|
-
monkeypatch.setenv("TELECLAUDE_THIRD_PARTY_LOG_LEVEL", "WARNING")
|
|
109
|
-
|
|
110
|
-
log_path = configure_logging(
|
|
111
|
-
env_prefix="TELECLAUDE",
|
|
112
|
-
app_logger_prefix="teleclaude",
|
|
113
|
-
app_name="teleclaude",
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
log_kv(
|
|
117
|
-
logging.getLogger("teleclaude.core"),
|
|
118
|
-
logging.INFO,
|
|
119
|
-
{"msg": "hello", "session": "abc123", "n": 1},
|
|
120
|
-
)
|
|
88
|
+
logging.getLogger("teleclaude.core").info("hello", session="abc123", n=1)
|
|
121
89
|
|
|
122
90
|
content = _read_text(log_path)
|
|
123
91
|
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.0.post1}/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
|