Metadata-Version: 2.4
Name: logleaf
Version: 1.0.0
Summary: Bounded background logging with configurable outputs and loss accounting
Author: Allen-zjx
License-Expression: MIT
Project-URL: Repository, https://github.com/Allen-zjx/logleaf
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: ruff>=0.9; extra == "dev"
Provides-Extra: benchmark
Requires-Dist: loguru==0.7.3; extra == "benchmark"
Requires-Dist: structlog==26.1.0; extra == "benchmark"
Dynamic: license-file

# logleaf

轻量的 Python 结构化日志库。业务线程提交日志，后台线程完成字段合并、格式化和批量写入。

**Python 3.11+ · 无第三方运行时依赖 · MIT 许可证**

- 文件与终端分别开关、设置等级，支持 JSONL 和可读文本。
- 用关键字参数添加业务字段，用 `bind()` 复用公共上下文。
- 支持异常堆栈、文件轮转、毫秒或微秒时间显示。
- 支持线程共享、异步关闭，并提供队列丢弃和输出失败统计。

## 安装

```bash
python -m pip install logleaf
```

## 快速开始

在程序入口初始化一次，退出前关闭：

```python
from logleaf import log

log.init(
    file_path="logs/app.jsonl",
    console_enabled=True,
)
try:
    log.info("程序启动")
    log.info("任务完成", 耗时=120, 次数=3, 策略="策略A")
finally:
    log.close()
```

默认文件名带进程 PID，例如 `logs/app.12345.jsonl`。使用 `log.file_path` 获取实际路径。
文件默认采用 JSONL，每行一条 JSON；终端采用文本，默认输出到 stderr。

文件内容示例：

```json
{"timestamp":"2026-09-24T14:30:25.123456+08:00","timestamp_mode":"call","level":"INFO","event":"任务完成","耗时":120,"次数":3,"策略":"策略A"}
```

终端内容示例：

```text
2026-09-24 14:30:25.123456 | INFO | 任务完成 | 耗时=120 次数=3 策略=策略A
```

时间仅作格式示意。文本中的空格、分隔符和控制字符会按需加引号或转义。

## 多个模块共享配置

所有模块导入同一个 `log`，跟随程序入口的初始化配置。其他模块无需重复初始化：

```python
# worker.py
from logleaf import log


def run():
    log.info("收到任务", task_id=123)
```

```python
# main.py
from logleaf import log
from worker import run

log.init(file_path="logs/app.jsonl", console_enabled=False)
try:
    run()
finally:
    log.close()
```

导入不会创建文件或启动后台线程。记录日志前必须调用 `log.init()`；重复初始化会报错。
成功关闭后可以重新初始化，但之前的 `bind()` 对象仍属于旧实例，需要重新创建。

## 自定义字段

初始化后，可以按业务需要自由增减字段：

```python
log.info("任务完成", duration_us=120, attempts=3)

strategy_log = log.bind(strategy="A", symbol="BTC")
strategy_log.info("收到信号", signal="buy", price=60000)
strategy_log.warning("重试", attempts=2)
```

字段值支持字符串、整数、有限浮点数、布尔值和 `None`。字典、列表和自定义对象不受支持。
`duration_us`、`attempts` 等都是自定义字段，库不会自动计算耗时或推断单位。
单条记录中的同名字段会覆盖绑定值，不影响后续记录。

`timestamp`、`timestamp_mode`、`level`、`event`、`_log_hub` 是保留字段。

## 记录异常

在已初始化的日志上下文中，使用 `exception()` 记录当前异常：

```python
try:
    result = 1 / 0
except ZeroDivisionError:
    log.exception("任务执行失败", task_id=123)
```

`exception()` 使用 ERROR 等级，支持异常原因链和异常组。JSONL 将异常详情放在
`_log_hub` 中，仍保持每条一行；文本输出展开为多行堆栈。
`error()` 只记录消息；在 `except` 外调用 `exception()` 时也不会附加堆栈。

异常记录会在调用端采集位置和说明，后台完成格式化与写入；不采集局部变量，也不自动脱敏。

## 文件与终端配置

通过 `log.init()` 的参数控制输出。例如，文件保留 INFO 及以上，终端只显示 WARNING 及以上：

```python
log.init(
    file_path="logs/app.jsonl",
    file_level="INFO",
    console_enabled=True,
    console_level="WARNING",
    timestamp_mode="call",
    time_precision="us",
    timezone="UTC",
)
```

| 需求 | 配置 |
|---|---|
| 只写文件 | `console_enabled=False`，默认行为 |
| 只显示终端 | `file_enabled=False, console_enabled=True` |
| 文件保存为可读文本 | `file_format="text"` |
| 终端输出 JSONL | `console_format="jsonl"` |
| 显示毫秒 / 微秒 | `time_precision="ms"` / `"us"`，默认微秒 |
| 使用调用时刻 / 后台处理时刻 | `timestamp_mode="call"` / `"worker"`，默认调用时刻 |
| 设置时区 | `timezone="local"`、`"UTC"` 或 IANA 名称 |
| 文件轮转 | `max_bytes=50 * 1024 * 1024, backup_count=5`，默认值 |
| 限制队列容量 | `capacity=8192`，默认值 |

日志始终带时间。`call` 模式在调用时采集，不受后续排队影响；`worker` 模式记录后台处理时刻。
微秒显示精度不代表系统时钟保证微秒准确度。缺少 IANA 时区数据库的系统可安装 `tzdata`。

## 异步程序

在协程中直接调用 `log.info()`，退出时使用 `await log.aclose()`：

```python
import asyncio
from logleaf import log


async def main():
    log.init(file_path="logs/async.jsonl")
    try:
        log.info("开始")
        await asyncio.sleep(0.01)
        log.info("完成")
    finally:
        await log.aclose()


asyncio.run(main())
```

普通日志调用仍有参数处理和入队开销。`aclose()` 将等待后台排空的工作移到线程中，
避免同步等待阻塞事件循环。

## 独立日志实例

需要不同输出或独立生命周期时使用 `Logger`：

```python
from logleaf import Logger

with Logger("logs/orders.jsonl", console_enabled=True) as orders:
    orders.info("订单创建", order_id="A001")
```

独立实例支持 `with` 和 `async with`，不会改变共享 `log` 的配置。
多个线程可以共享一个实例；多进程应在每个子进程内部初始化，并分别写文件。

## 队列、统计与关闭

- 日志方法返回 `True` 表示已入队，尚不代表落盘；返回 `False` 表示被等级过滤、队列满或输出不可用。
- 队列满时丢弃新记录，包括 ERROR/CRITICAL。后台会汇总丢弃数量；可读取 `log.stats.dropped`、`log.stats.pending` 和 `log.stats.outputs` 监控状态。
- 文件或终端发生写入错误时，停用失败的输出，其他健康输出继续工作；关闭时会反馈错误。
- `close()` / `aclose()` 默认最多等待 10 秒。正常关闭会排空队列并刷新缓冲，但不会执行 `fsync`。
- 请显式关闭日志。强制结束进程或断电可能丢失尚未写完的内容。

后台写入减少了调用端的工作，但仍会消耗 CPU，并受锁、GIL、磁盘和终端速度影响。
不保证零开销、固定延迟上限或零丢失；需要逐条持久化保障的审计记录应使用专门的存储机制。

## 文档

本页涵盖常用用法。完整指南和可运行示例随 [PyPI 源码发行包](https://pypi.org/project/logleaf/#files) 提供：

- `docs/usage.md`：完整配置、异常格式、统计与生命周期。
- `docs/versioning.md`：公开接口和版本兼容约定。
- `examples/`：同步、异步、多模块和多进程示例。
- `CHANGELOG.md`：版本更新记录。

## 许可证

logleaf 采用 [MIT 许可证](https://spdx.org/licenses/MIT.html)。完整许可文本随安装包分发。
