logleaf 1.0.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.
Files changed (74) hide show
  1. logleaf-1.0.0/CHANGELOG.md +26 -0
  2. logleaf-1.0.0/LICENSE +21 -0
  3. logleaf-1.0.0/MANIFEST.in +6 -0
  4. logleaf-1.0.0/PKG-INFO +226 -0
  5. logleaf-1.0.0/PROTOTYPE.md +103 -0
  6. logleaf-1.0.0/README.md +208 -0
  7. logleaf-1.0.0/benchmarks/baselines/v0.1.0.zip +0 -0
  8. logleaf-1.0.0/benchmarks/business_load.py +422 -0
  9. logleaf-1.0.0/benchmarks/compare.py +458 -0
  10. logleaf-1.0.0/benchmarks/diagnose.py +139 -0
  11. logleaf-1.0.0/benchmarks/exception_cost.py +116 -0
  12. logleaf-1.0.0/benchmarks/optimization_compare.py +90 -0
  13. logleaf-1.0.0/benchmarks/process_experiment.py +186 -0
  14. logleaf-1.0.0/benchmarks/shared_entry.py +148 -0
  15. logleaf-1.0.0/docs/development-history.md +63 -0
  16. logleaf-1.0.0/docs/development.md +102 -0
  17. logleaf-1.0.0/docs/publishing.md +39 -0
  18. logleaf-1.0.0/docs/usage.md +206 -0
  19. logleaf-1.0.0/docs/versioning.md +35 -0
  20. logleaf-1.0.0/examples/async_usage.py +25 -0
  21. logleaf-1.0.0/examples/basic.py +19 -0
  22. logleaf-1.0.0/examples/exception_usage.py +29 -0
  23. logleaf-1.0.0/examples/multiprocess.py +31 -0
  24. logleaf-1.0.0/examples/shared_async.py +22 -0
  25. logleaf-1.0.0/examples/shared_usage.py +23 -0
  26. logleaf-1.0.0/examples/strategy.py +9 -0
  27. logleaf-1.0.0/pyproject.toml +34 -0
  28. logleaf-1.0.0/reports/README.md +18 -0
  29. logleaf-1.0.0/reports/benchmark.json +850 -0
  30. logleaf-1.0.0/reports/benchmark.md +30 -0
  31. logleaf-1.0.0/reports/business-load.json +3918 -0
  32. logleaf-1.0.0/reports/business-load.md +53 -0
  33. logleaf-1.0.0/reports/conclusion.md +47 -0
  34. logleaf-1.0.0/reports/diagnosis.json +615 -0
  35. logleaf-1.0.0/reports/exception-cost.json +177 -0
  36. logleaf-1.0.0/reports/exceptions.md +51 -0
  37. logleaf-1.0.0/reports/optimization-comparison.json +2626 -0
  38. logleaf-1.0.0/reports/optimization-final.json +1732 -0
  39. logleaf-1.0.0/reports/optimization-validation.json +1422 -0
  40. logleaf-1.0.0/reports/optimization-validation.md +53 -0
  41. logleaf-1.0.0/reports/optimization.md +85 -0
  42. logleaf-1.0.0/reports/refactoring.md +20 -0
  43. logleaf-1.0.0/reports/renaming.md +35 -0
  44. logleaf-1.0.0/reports/shared-entry.json +1776 -0
  45. logleaf-1.0.0/reports/shared-entry.md +52 -0
  46. logleaf-1.0.0/reports/sustained-load.json +138 -0
  47. logleaf-1.0.0/reports/tail-recheck.json +204 -0
  48. logleaf-1.0.0/reports/v0.1-summary.md +65 -0
  49. logleaf-1.0.0/reports/wake-experiment.json +842 -0
  50. logleaf-1.0.0/requirements-bench.txt +4 -0
  51. logleaf-1.0.0/requirements-dev.txt +7 -0
  52. logleaf-1.0.0/setup.cfg +4 -0
  53. logleaf-1.0.0/src/logleaf/__init__.py +24 -0
  54. logleaf-1.0.0/src/logleaf/_core.py +344 -0
  55. logleaf-1.0.0/src/logleaf/_models.py +64 -0
  56. logleaf-1.0.0/src/logleaf/exceptions.py +134 -0
  57. logleaf-1.0.0/src/logleaf/experimental/__init__.py +5 -0
  58. logleaf-1.0.0/src/logleaf/experimental/prototype.py +232 -0
  59. logleaf-1.0.0/src/logleaf/formatting.py +92 -0
  60. logleaf-1.0.0/src/logleaf/logger.py +226 -0
  61. logleaf-1.0.0/src/logleaf/prototype.py +8 -0
  62. logleaf-1.0.0/src/logleaf/shared.py +113 -0
  63. logleaf-1.0.0/src/logleaf/sinks.py +103 -0
  64. logleaf-1.0.0/src/logleaf.egg-info/PKG-INFO +226 -0
  65. logleaf-1.0.0/src/logleaf.egg-info/SOURCES.txt +72 -0
  66. logleaf-1.0.0/src/logleaf.egg-info/dependency_links.txt +1 -0
  67. logleaf-1.0.0/src/logleaf.egg-info/requires.txt +8 -0
  68. logleaf-1.0.0/src/logleaf.egg-info/top_level.txt +1 -0
  69. logleaf-1.0.0/tests/experimental/test_prototype.py +179 -0
  70. logleaf-1.0.0/tests/test_api_compatibility.py +58 -0
  71. logleaf-1.0.0/tests/test_exceptions.py +344 -0
  72. logleaf-1.0.0/tests/test_logger.py +583 -0
  73. logleaf-1.0.0/tests/test_optimization.py +205 -0
  74. logleaf-1.0.0/tests/test_shared.py +305 -0
@@ -0,0 +1,26 @@
1
+ # 更新记录
2
+
3
+ ## 1.0.0 — 2026-09-24
4
+
5
+ 首次正式发行。
6
+
7
+ ### 功能
8
+
9
+ - 提供共享入口 `log`、独立实例 `Logger` 和可绑定业务上下文的 `BoundLogger`。
10
+ - 后台批量格式化和写入,文件与终端分别配置格式、等级和开关。
11
+ - 支持 JSONL、可读文本、自由标量字段及异常堆栈。
12
+ - 支持调用时刻或处理时刻采集、毫秒或微秒显示及可配置时区。
13
+ - 提供有界队列、按等级统计的丢弃汇总和各输出的失败统计。
14
+ - 支持文件大小轮转、进程独立文件、同步与异步关闭。
15
+
16
+ ### 兼容性与许可
17
+
18
+ - 要求 Python 3.11+,运行时无第三方依赖。
19
+ - 公开接口遵循 1.x 兼容约定;实验接口不在稳定范围内。
20
+ - 采用 MIT 许可证。
21
+
22
+ ### 验证
23
+
24
+ - macOS 与 Ubuntu 上的 Python 3.11、3.13 功能测试通过。
25
+ - 发布流程检查源码包、wheel 元数据和安装后行为。
26
+ - 性能测试采用合成负载;实际业务吞吐与延迟取决于运行环境和输出配置。
logleaf-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Allen-zjx
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,6 @@
1
+ include CHANGELOG.md PROTOTYPE.md requirements-dev.txt requirements-bench.txt
2
+ recursive-include docs *.md
3
+ recursive-include examples *.py
4
+ recursive-include tests *.py
5
+ recursive-include benchmarks *.py *.zip
6
+ recursive-include reports *.md *.json
logleaf-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: logleaf
3
+ Version: 1.0.0
4
+ Summary: Bounded background logging with configurable outputs and loss accounting
5
+ Author: Allen-zjx
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/Allen-zjx/logleaf
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8; extra == "dev"
13
+ Requires-Dist: ruff>=0.9; extra == "dev"
14
+ Provides-Extra: benchmark
15
+ Requires-Dist: loguru==0.7.3; extra == "benchmark"
16
+ Requires-Dist: structlog==26.1.0; extra == "benchmark"
17
+ Dynamic: license-file
18
+
19
+ # logleaf
20
+
21
+ 轻量的 Python 结构化日志库。业务线程提交日志,后台线程完成字段合并、格式化和批量写入。
22
+
23
+ **Python 3.11+ · 无第三方运行时依赖 · MIT 许可证**
24
+
25
+ - 文件与终端分别开关、设置等级,支持 JSONL 和可读文本。
26
+ - 用关键字参数添加业务字段,用 `bind()` 复用公共上下文。
27
+ - 支持异常堆栈、文件轮转、毫秒或微秒时间显示。
28
+ - 支持线程共享、异步关闭,并提供队列丢弃和输出失败统计。
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ python -m pip install logleaf
34
+ ```
35
+
36
+ ## 快速开始
37
+
38
+ 在程序入口初始化一次,退出前关闭:
39
+
40
+ ```python
41
+ from logleaf import log
42
+
43
+ log.init(
44
+ file_path="logs/app.jsonl",
45
+ console_enabled=True,
46
+ )
47
+ try:
48
+ log.info("程序启动")
49
+ log.info("任务完成", 耗时=120, 次数=3, 策略="策略A")
50
+ finally:
51
+ log.close()
52
+ ```
53
+
54
+ 默认文件名带进程 PID,例如 `logs/app.12345.jsonl`。使用 `log.file_path` 获取实际路径。
55
+ 文件默认采用 JSONL,每行一条 JSON;终端采用文本,默认输出到 stderr。
56
+
57
+ 文件内容示例:
58
+
59
+ ```json
60
+ {"timestamp":"2026-09-24T14:30:25.123456+08:00","timestamp_mode":"call","level":"INFO","event":"任务完成","耗时":120,"次数":3,"策略":"策略A"}
61
+ ```
62
+
63
+ 终端内容示例:
64
+
65
+ ```text
66
+ 2026-09-24 14:30:25.123456 | INFO | 任务完成 | 耗时=120 次数=3 策略=策略A
67
+ ```
68
+
69
+ 时间仅作格式示意。文本中的空格、分隔符和控制字符会按需加引号或转义。
70
+
71
+ ## 多个模块共享配置
72
+
73
+ 所有模块导入同一个 `log`,跟随程序入口的初始化配置。其他模块无需重复初始化:
74
+
75
+ ```python
76
+ # worker.py
77
+ from logleaf import log
78
+
79
+
80
+ def run():
81
+ log.info("收到任务", task_id=123)
82
+ ```
83
+
84
+ ```python
85
+ # main.py
86
+ from logleaf import log
87
+ from worker import run
88
+
89
+ log.init(file_path="logs/app.jsonl", console_enabled=False)
90
+ try:
91
+ run()
92
+ finally:
93
+ log.close()
94
+ ```
95
+
96
+ 导入不会创建文件或启动后台线程。记录日志前必须调用 `log.init()`;重复初始化会报错。
97
+ 成功关闭后可以重新初始化,但之前的 `bind()` 对象仍属于旧实例,需要重新创建。
98
+
99
+ ## 自定义字段
100
+
101
+ 初始化后,可以按业务需要自由增减字段:
102
+
103
+ ```python
104
+ log.info("任务完成", duration_us=120, attempts=3)
105
+
106
+ strategy_log = log.bind(strategy="A", symbol="BTC")
107
+ strategy_log.info("收到信号", signal="buy", price=60000)
108
+ strategy_log.warning("重试", attempts=2)
109
+ ```
110
+
111
+ 字段值支持字符串、整数、有限浮点数、布尔值和 `None`。字典、列表和自定义对象不受支持。
112
+ `duration_us`、`attempts` 等都是自定义字段,库不会自动计算耗时或推断单位。
113
+ 单条记录中的同名字段会覆盖绑定值,不影响后续记录。
114
+
115
+ `timestamp`、`timestamp_mode`、`level`、`event`、`_log_hub` 是保留字段。
116
+
117
+ ## 记录异常
118
+
119
+ 在已初始化的日志上下文中,使用 `exception()` 记录当前异常:
120
+
121
+ ```python
122
+ try:
123
+ result = 1 / 0
124
+ except ZeroDivisionError:
125
+ log.exception("任务执行失败", task_id=123)
126
+ ```
127
+
128
+ `exception()` 使用 ERROR 等级,支持异常原因链和异常组。JSONL 将异常详情放在
129
+ `_log_hub` 中,仍保持每条一行;文本输出展开为多行堆栈。
130
+ `error()` 只记录消息;在 `except` 外调用 `exception()` 时也不会附加堆栈。
131
+
132
+ 异常记录会在调用端采集位置和说明,后台完成格式化与写入;不采集局部变量,也不自动脱敏。
133
+
134
+ ## 文件与终端配置
135
+
136
+ 通过 `log.init()` 的参数控制输出。例如,文件保留 INFO 及以上,终端只显示 WARNING 及以上:
137
+
138
+ ```python
139
+ log.init(
140
+ file_path="logs/app.jsonl",
141
+ file_level="INFO",
142
+ console_enabled=True,
143
+ console_level="WARNING",
144
+ timestamp_mode="call",
145
+ time_precision="us",
146
+ timezone="UTC",
147
+ )
148
+ ```
149
+
150
+ | 需求 | 配置 |
151
+ |---|---|
152
+ | 只写文件 | `console_enabled=False`,默认行为 |
153
+ | 只显示终端 | `file_enabled=False, console_enabled=True` |
154
+ | 文件保存为可读文本 | `file_format="text"` |
155
+ | 终端输出 JSONL | `console_format="jsonl"` |
156
+ | 显示毫秒 / 微秒 | `time_precision="ms"` / `"us"`,默认微秒 |
157
+ | 使用调用时刻 / 后台处理时刻 | `timestamp_mode="call"` / `"worker"`,默认调用时刻 |
158
+ | 设置时区 | `timezone="local"`、`"UTC"` 或 IANA 名称 |
159
+ | 文件轮转 | `max_bytes=50 * 1024 * 1024, backup_count=5`,默认值 |
160
+ | 限制队列容量 | `capacity=8192`,默认值 |
161
+
162
+ 日志始终带时间。`call` 模式在调用时采集,不受后续排队影响;`worker` 模式记录后台处理时刻。
163
+ 微秒显示精度不代表系统时钟保证微秒准确度。缺少 IANA 时区数据库的系统可安装 `tzdata`。
164
+
165
+ ## 异步程序
166
+
167
+ 在协程中直接调用 `log.info()`,退出时使用 `await log.aclose()`:
168
+
169
+ ```python
170
+ import asyncio
171
+ from logleaf import log
172
+
173
+
174
+ async def main():
175
+ log.init(file_path="logs/async.jsonl")
176
+ try:
177
+ log.info("开始")
178
+ await asyncio.sleep(0.01)
179
+ log.info("完成")
180
+ finally:
181
+ await log.aclose()
182
+
183
+
184
+ asyncio.run(main())
185
+ ```
186
+
187
+ 普通日志调用仍有参数处理和入队开销。`aclose()` 将等待后台排空的工作移到线程中,
188
+ 避免同步等待阻塞事件循环。
189
+
190
+ ## 独立日志实例
191
+
192
+ 需要不同输出或独立生命周期时使用 `Logger`:
193
+
194
+ ```python
195
+ from logleaf import Logger
196
+
197
+ with Logger("logs/orders.jsonl", console_enabled=True) as orders:
198
+ orders.info("订单创建", order_id="A001")
199
+ ```
200
+
201
+ 独立实例支持 `with` 和 `async with`,不会改变共享 `log` 的配置。
202
+ 多个线程可以共享一个实例;多进程应在每个子进程内部初始化,并分别写文件。
203
+
204
+ ## 队列、统计与关闭
205
+
206
+ - 日志方法返回 `True` 表示已入队,尚不代表落盘;返回 `False` 表示被等级过滤、队列满或输出不可用。
207
+ - 队列满时丢弃新记录,包括 ERROR/CRITICAL。后台会汇总丢弃数量;可读取 `log.stats.dropped`、`log.stats.pending` 和 `log.stats.outputs` 监控状态。
208
+ - 文件或终端发生写入错误时,停用失败的输出,其他健康输出继续工作;关闭时会反馈错误。
209
+ - `close()` / `aclose()` 默认最多等待 10 秒。正常关闭会排空队列并刷新缓冲,但不会执行 `fsync`。
210
+ - 请显式关闭日志。强制结束进程或断电可能丢失尚未写完的内容。
211
+
212
+ 后台写入减少了调用端的工作,但仍会消耗 CPU,并受锁、GIL、磁盘和终端速度影响。
213
+ 不保证零开销、固定延迟上限或零丢失;需要逐条持久化保障的审计记录应使用专门的存储机制。
214
+
215
+ ## 文档
216
+
217
+ 本页涵盖常用用法。完整指南和可运行示例随 [PyPI 源码发行包](https://pypi.org/project/logleaf/#files) 提供:
218
+
219
+ - `docs/usage.md`:完整配置、异常格式、统计与生命周期。
220
+ - `docs/versioning.md`:公开接口和版本兼容约定。
221
+ - `examples/`:同步、异步、多模块和多进程示例。
222
+ - `CHANGELOG.md`:版本更新记录。
223
+
224
+ ## 许可证
225
+
226
+ logleaf 采用 [MIT 许可证](https://spdx.org/licenses/MIT.html)。完整许可文本随安装包分发。
@@ -0,0 +1,103 @@
1
+ # logleaf:日志性能实验
2
+
3
+ 这是早期性能原型的历史说明,不代表当前 `Logger` 的能力或推荐用法。
4
+ 原型源码位于 `src/logleaf/experimental/prototype.py`,仅用于复现实验;
5
+ 当前使用方式见 [README](README.md),运行时仍只依赖标准库。
6
+
7
+ 本阶段要回答两个问题:调用日志能否更快返回;计入后台全部写完的时间后,整体是否更快。
8
+ 还要检查后台工作对其他 asyncio 任务的影响,以及队列带来的内存成本。
9
+
10
+ 本次结果:[性能表格与测试口径](reports/benchmark.md)、[结论和开发建议](reports/conclusion.md)。
11
+ 现有证据支持优先复用 structlog 前端、按需配置批量后端;尚不足以支持重写完整日志框架。
12
+
13
+ ## 运行实验
14
+
15
+ 需要 Python 3.11+,本次实测使用 CPython 3.13.5。依赖版本记录在
16
+ `requirements-bench.txt` 中。以下命令在本仓库目录运行:
17
+
18
+ ```bash
19
+ uv venv --python 3.13
20
+ uv pip install --python .venv/bin/python -r requirements-bench.txt
21
+ .venv/bin/pytest -q
22
+ .venv/bin/ruff check src tests benchmarks
23
+ TZ=UTC .venv/bin/python benchmarks/compare.py --count 30000 --rounds 3
24
+ ```
25
+
26
+ 测试自动创建、删除临时日志,最终生成 `reports/benchmark.json`(原始数据)和
27
+ `reports/benchmark.md`(表格与方法说明)。`--execution-context` 可记录运行环境;
28
+ 正式测试使用 `--execution-context unsandboxed`,这个参数仅添加说明,不改变系统权限。
29
+ 本机初测发现沙箱会显著放大部分系统时间调用的成本,比较时应统一执行环境。
30
+
31
+ ## 比较对象
32
+
33
+ | 名称 | 处理方式 |
34
+ |---|---|
35
+ | `stdlib_sync` | logging + 自定义缓冲 JSON 输出 Handler |
36
+ | `stdlib_queue` | logging QueueHandler / QueueListener + 相同输出 Handler |
37
+ | `loguru_sync` | Loguru + 自定义缓冲 JSON sink |
38
+ | `loguru_queue` | 上一配置启用 `enqueue=True` |
39
+ | `structlog_sync` | 原生过滤、缓存绑定 logger + 自定义缓冲 JSON sink |
40
+ | `structlog_batch` | structlog 生成事件,接入本原型的批量写入后端 |
41
+ | `prototype` | 本项目最小接口 + 相同批量写入后端 |
42
+
43
+ 这些都是针对本次输出需求配置的方案,不是各库的默认配置。
44
+ 相同数据字段和序列化器不意味着功能完全相同:例如 Loguru 仍收集额外元信息、
45
+ 其队列支持多进程;原型没有这些能力。不能把结果宣传为对成熟库的全面性能领先。
46
+
47
+ 吞吐量计算包含 drain、flush、close,并逐行检查字段、数量、顺序;不包含 fsync。
48
+ 正式性能组增大队列容量以避免丢弃,默认 8192 条容量的原型在持续超载时会丢弃新记录。
49
+ 队列满、慢写入、退出、写入失败等行为另有测试;不会把丢弃吞吐算作写入性能。
50
+ 标准库和第三方同步适配器使用 64 KiB 文件缓冲并在结束时 flush;原型另外周期 flush。
51
+ 本次未比较“每条日志立即可见”或“每条日志立即持久化”的场景。
52
+
53
+ ## 尝试原型
54
+
55
+ 可通过 `uv pip install --python .venv/bin/python -e .` 安装,或在源码目录使用
56
+ `PYTHONPATH=src .venv/bin/python`。示例:
57
+
58
+ ```python
59
+ from logleaf.experimental import BatchLogger
60
+
61
+ with BatchLogger("app.jsonl", capacity=8192, batch_size=256) as log:
62
+ accepted = log.info("任务完成", task_id="task-123", elapsed_ms=12.5)
63
+ log.debug("调试信息") # 默认 INFO 级别,此条被过滤
64
+ if not accepted:
65
+ # INFO 已开启时,False 表示队列满。由业务选择是否处理。
66
+ pass
67
+
68
+ print(log.stats) # 正常离开上下文时已排空并 flush
69
+ ```
70
+
71
+ 原型目前提供 `info()`、`debug()`、`log(level, event, **fields)`;等级数值为
72
+ 10/20/30/40/50。接受字符串、整数、有限浮点数、布尔值、None;拒绝列表、字典等
73
+ 可变对象,避免后台记录到对象后续修改后的值。`event`、`level`、`timestamp` 为保留字段。
74
+
75
+ ## 行为边界
76
+
77
+ - 单进程、可多线程调用;不支持在 fork 后共享实例,也不协调多个进程写同一个文件。
78
+ - 容量包含队列中和正在处理的记录。限制的是记录数量,不是总字节数。
79
+ - 入队成功只代表被接受,不代表已经写入。队列满返回 False 并增加 `dropped`;
80
+ 等级过滤也返回 False,但不增加 `dropped`。
81
+ - 文件追加写入;默认最大批量 256,目标 flush 间隔 100 ms。调度、磁盘和大批次耗时
82
+ 都可能延长这个间隔,不能视作严格的延迟上限。后台仍消耗 CPU,并可能竞争 GIL。
83
+ - `stats.written` 是已交给文件流的条数,仍可能处于用户态缓冲;`close()` 成功才能确认
84
+ 所有已接受日志完成 flush。`failed` 是写入器失败时未确认提交给文件流的条数;
85
+ 若底层发生部分写入,文件可能含部分数据,不能据此实现精确重试。
86
+ - `close()` 默认最多等待 10 秒,超时会抛异常,可以再次调用等待。超时不是强制终止
87
+ 文件写入。关闭后的新日志、已发现的后台写入失败会抛异常,调用方可以明确发现故障。
88
+ - 在 asyncio 中结束时使用 `await asyncio.to_thread(log.close)`,避免同步排空阻塞事件循环。
89
+ - 需要显式关闭或使用上下文管理器;没有退出钩子。异常终止、断电可能丢失缓冲日志。
90
+ 即使 flush 完成,也没有 fsync 持久化保证。
91
+ - 尚未实现异常堆栈、上下文绑定、脱敏、轮转、通知桥接。启用这些功能后的性能需重新测量。
92
+
93
+ ## 如何决定下一步
94
+
95
+ 先比较 `prototype` 与 `structlog_batch`:如果接近,说明主要价值在写入后端,
96
+ 应优先复用成熟的日志前端;如果原型有稳定优势,再考虑保留轻量接口。
97
+ 同步 structlog 则用于检查“后台线程是否真的提高总吞吐”,防止只看入队速度。
98
+
99
+ 官方设计参考:
100
+
101
+ - [structlog 性能指南](https://www.structlog.org/en/stable/performance.html)
102
+ - [Loguru logger API](https://loguru.readthedocs.io/en/stable/api/logger.html)
103
+ - [Python QueueHandler / QueueListener](https://docs.python.org/3/library/logging.handlers.html#queuehandler)
@@ -0,0 +1,208 @@
1
+ # logleaf
2
+
3
+ 轻量的 Python 结构化日志库。业务线程提交日志,后台线程完成字段合并、格式化和批量写入。
4
+
5
+ **Python 3.11+ · 无第三方运行时依赖 · MIT 许可证**
6
+
7
+ - 文件与终端分别开关、设置等级,支持 JSONL 和可读文本。
8
+ - 用关键字参数添加业务字段,用 `bind()` 复用公共上下文。
9
+ - 支持异常堆栈、文件轮转、毫秒或微秒时间显示。
10
+ - 支持线程共享、异步关闭,并提供队列丢弃和输出失败统计。
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ python -m pip install logleaf
16
+ ```
17
+
18
+ ## 快速开始
19
+
20
+ 在程序入口初始化一次,退出前关闭:
21
+
22
+ ```python
23
+ from logleaf import log
24
+
25
+ log.init(
26
+ file_path="logs/app.jsonl",
27
+ console_enabled=True,
28
+ )
29
+ try:
30
+ log.info("程序启动")
31
+ log.info("任务完成", 耗时=120, 次数=3, 策略="策略A")
32
+ finally:
33
+ log.close()
34
+ ```
35
+
36
+ 默认文件名带进程 PID,例如 `logs/app.12345.jsonl`。使用 `log.file_path` 获取实际路径。
37
+ 文件默认采用 JSONL,每行一条 JSON;终端采用文本,默认输出到 stderr。
38
+
39
+ 文件内容示例:
40
+
41
+ ```json
42
+ {"timestamp":"2026-09-24T14:30:25.123456+08:00","timestamp_mode":"call","level":"INFO","event":"任务完成","耗时":120,"次数":3,"策略":"策略A"}
43
+ ```
44
+
45
+ 终端内容示例:
46
+
47
+ ```text
48
+ 2026-09-24 14:30:25.123456 | INFO | 任务完成 | 耗时=120 次数=3 策略=策略A
49
+ ```
50
+
51
+ 时间仅作格式示意。文本中的空格、分隔符和控制字符会按需加引号或转义。
52
+
53
+ ## 多个模块共享配置
54
+
55
+ 所有模块导入同一个 `log`,跟随程序入口的初始化配置。其他模块无需重复初始化:
56
+
57
+ ```python
58
+ # worker.py
59
+ from logleaf import log
60
+
61
+
62
+ def run():
63
+ log.info("收到任务", task_id=123)
64
+ ```
65
+
66
+ ```python
67
+ # main.py
68
+ from logleaf import log
69
+ from worker import run
70
+
71
+ log.init(file_path="logs/app.jsonl", console_enabled=False)
72
+ try:
73
+ run()
74
+ finally:
75
+ log.close()
76
+ ```
77
+
78
+ 导入不会创建文件或启动后台线程。记录日志前必须调用 `log.init()`;重复初始化会报错。
79
+ 成功关闭后可以重新初始化,但之前的 `bind()` 对象仍属于旧实例,需要重新创建。
80
+
81
+ ## 自定义字段
82
+
83
+ 初始化后,可以按业务需要自由增减字段:
84
+
85
+ ```python
86
+ log.info("任务完成", duration_us=120, attempts=3)
87
+
88
+ strategy_log = log.bind(strategy="A", symbol="BTC")
89
+ strategy_log.info("收到信号", signal="buy", price=60000)
90
+ strategy_log.warning("重试", attempts=2)
91
+ ```
92
+
93
+ 字段值支持字符串、整数、有限浮点数、布尔值和 `None`。字典、列表和自定义对象不受支持。
94
+ `duration_us`、`attempts` 等都是自定义字段,库不会自动计算耗时或推断单位。
95
+ 单条记录中的同名字段会覆盖绑定值,不影响后续记录。
96
+
97
+ `timestamp`、`timestamp_mode`、`level`、`event`、`_log_hub` 是保留字段。
98
+
99
+ ## 记录异常
100
+
101
+ 在已初始化的日志上下文中,使用 `exception()` 记录当前异常:
102
+
103
+ ```python
104
+ try:
105
+ result = 1 / 0
106
+ except ZeroDivisionError:
107
+ log.exception("任务执行失败", task_id=123)
108
+ ```
109
+
110
+ `exception()` 使用 ERROR 等级,支持异常原因链和异常组。JSONL 将异常详情放在
111
+ `_log_hub` 中,仍保持每条一行;文本输出展开为多行堆栈。
112
+ `error()` 只记录消息;在 `except` 外调用 `exception()` 时也不会附加堆栈。
113
+
114
+ 异常记录会在调用端采集位置和说明,后台完成格式化与写入;不采集局部变量,也不自动脱敏。
115
+
116
+ ## 文件与终端配置
117
+
118
+ 通过 `log.init()` 的参数控制输出。例如,文件保留 INFO 及以上,终端只显示 WARNING 及以上:
119
+
120
+ ```python
121
+ log.init(
122
+ file_path="logs/app.jsonl",
123
+ file_level="INFO",
124
+ console_enabled=True,
125
+ console_level="WARNING",
126
+ timestamp_mode="call",
127
+ time_precision="us",
128
+ timezone="UTC",
129
+ )
130
+ ```
131
+
132
+ | 需求 | 配置 |
133
+ |---|---|
134
+ | 只写文件 | `console_enabled=False`,默认行为 |
135
+ | 只显示终端 | `file_enabled=False, console_enabled=True` |
136
+ | 文件保存为可读文本 | `file_format="text"` |
137
+ | 终端输出 JSONL | `console_format="jsonl"` |
138
+ | 显示毫秒 / 微秒 | `time_precision="ms"` / `"us"`,默认微秒 |
139
+ | 使用调用时刻 / 后台处理时刻 | `timestamp_mode="call"` / `"worker"`,默认调用时刻 |
140
+ | 设置时区 | `timezone="local"`、`"UTC"` 或 IANA 名称 |
141
+ | 文件轮转 | `max_bytes=50 * 1024 * 1024, backup_count=5`,默认值 |
142
+ | 限制队列容量 | `capacity=8192`,默认值 |
143
+
144
+ 日志始终带时间。`call` 模式在调用时采集,不受后续排队影响;`worker` 模式记录后台处理时刻。
145
+ 微秒显示精度不代表系统时钟保证微秒准确度。缺少 IANA 时区数据库的系统可安装 `tzdata`。
146
+
147
+ ## 异步程序
148
+
149
+ 在协程中直接调用 `log.info()`,退出时使用 `await log.aclose()`:
150
+
151
+ ```python
152
+ import asyncio
153
+ from logleaf import log
154
+
155
+
156
+ async def main():
157
+ log.init(file_path="logs/async.jsonl")
158
+ try:
159
+ log.info("开始")
160
+ await asyncio.sleep(0.01)
161
+ log.info("完成")
162
+ finally:
163
+ await log.aclose()
164
+
165
+
166
+ asyncio.run(main())
167
+ ```
168
+
169
+ 普通日志调用仍有参数处理和入队开销。`aclose()` 将等待后台排空的工作移到线程中,
170
+ 避免同步等待阻塞事件循环。
171
+
172
+ ## 独立日志实例
173
+
174
+ 需要不同输出或独立生命周期时使用 `Logger`:
175
+
176
+ ```python
177
+ from logleaf import Logger
178
+
179
+ with Logger("logs/orders.jsonl", console_enabled=True) as orders:
180
+ orders.info("订单创建", order_id="A001")
181
+ ```
182
+
183
+ 独立实例支持 `with` 和 `async with`,不会改变共享 `log` 的配置。
184
+ 多个线程可以共享一个实例;多进程应在每个子进程内部初始化,并分别写文件。
185
+
186
+ ## 队列、统计与关闭
187
+
188
+ - 日志方法返回 `True` 表示已入队,尚不代表落盘;返回 `False` 表示被等级过滤、队列满或输出不可用。
189
+ - 队列满时丢弃新记录,包括 ERROR/CRITICAL。后台会汇总丢弃数量;可读取 `log.stats.dropped`、`log.stats.pending` 和 `log.stats.outputs` 监控状态。
190
+ - 文件或终端发生写入错误时,停用失败的输出,其他健康输出继续工作;关闭时会反馈错误。
191
+ - `close()` / `aclose()` 默认最多等待 10 秒。正常关闭会排空队列并刷新缓冲,但不会执行 `fsync`。
192
+ - 请显式关闭日志。强制结束进程或断电可能丢失尚未写完的内容。
193
+
194
+ 后台写入减少了调用端的工作,但仍会消耗 CPU,并受锁、GIL、磁盘和终端速度影响。
195
+ 不保证零开销、固定延迟上限或零丢失;需要逐条持久化保障的审计记录应使用专门的存储机制。
196
+
197
+ ## 文档
198
+
199
+ 本页涵盖常用用法。完整指南和可运行示例随 [PyPI 源码发行包](https://pypi.org/project/logleaf/#files) 提供:
200
+
201
+ - `docs/usage.md`:完整配置、异常格式、统计与生命周期。
202
+ - `docs/versioning.md`:公开接口和版本兼容约定。
203
+ - `examples/`:同步、异步、多模块和多进程示例。
204
+ - `CHANGELOG.md`:版本更新记录。
205
+
206
+ ## 许可证
207
+
208
+ logleaf 采用 [MIT 许可证](https://spdx.org/licenses/MIT.html)。完整许可文本随安装包分发。