Metadata-Version: 2.4
Name: kitty_logger
Version: 0.2.0.dev0
Summary: Cross-process logging via a dedicated log server process and SocketHandler.
Author: Kitty
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# kitty_logger

单机跨进程的 Python 日志库。主进程启动一个独立的"日志服务"子进程，所有
通过 `spawn` 创建的子进程把 `LogRecord` 经 `logging.handlers.SocketHandler`
发到这里，由它统一落盘 / 输出到 `stderr`，**不会出现多进程并发写文件的
错行或丢失问题**。

**适用范围。** 公司内部研发协作环境：单台主机、本机所有进程都互相信任、
追求"用起来简单"而不是"对抗外部威胁"。**不适用于**生产部署或多租户机器
等不能信任本机其他进程的场景。

## 安装

```bash
pip install kitty-logger
```

## 用法

```python
# main.py
import multiprocessing as mp
import kitty_logger

def worker(i):
    log = kitty_logger.getLogger(f"worker.{i}")
    log.info("hello from worker %d", i)

if __name__ == "__main__":
    kitty_logger.setup_logging(log_file="app.log")
    log = kitty_logger.getLogger("main")
    log.info("starting")

    with mp.get_context("spawn").Pool(4) as pool:
        pool.map(worker, range(4))
```

`setup_logging` 会写入环境变量
`KITTY_LOGGER_HOST` / `KITTY_LOGGER_PORT` / `KITTY_LOGGER_LEVEL`；任意层级
`spawn` 出来的后代进程会自动继承，从而能在零配置的情况下连上日志服务。

## API

- `setup_logging(log_file=None, level=logging.INFO, host="127.0.0.1", port=0, stream=True, console_fmt=..., file_fmt=..., datefmt=None, attach_main_logger=True) -> (host, port)`
  启动日志服务子进程（始终使用 `spawn`）。幂等。已通过 `atexit` 注册清理。
  `port=0` 让操作系统挑选空闲端口；返回真实绑定到的 `(host, port)`。
  **`host` 必须是 loopback**——绑定非 loopback 地址会直接 `ValueError`。
- `getLogger(name=None) -> logging.Logger`
  返回一个挂好 `SocketHandler`、指向日志服务的 logger。
- `shutdown_logging()` — 显式停止日志服务子进程。

## 为什么只支持 spawn

`fork` 出来的子进程会继承父进程已经建立的 `SocketHandler` 及其底层 TCP
连接。父子进程会在同一个 socket 上交叉写 pickle 字节流，导致服务端反
序列化必然失败。此外 `fork` 在多线程父进程中不安全（其他线程持有的锁
会原样留在子进程里，引发死锁），且在 Windows 上不被支持。

请使用 `multiprocessing.get_context("spawn")`，或在顶层用
`if __name__ == "__main__":` 守卫配合 Python 默认行为。

## 注意事项

- **不要**在 `setup_logging(attach_main_logger=True)` 之前调用
  `logging.basicConfig()`（或自行给 root logger 挂 `StreamHandler`）——
  否则主进程会把每条记录输出两次：一次走本地 root handler，一次走日志
  服务。要么让 kitty_logger 做唯一的配置入口，要么传
  `attach_main_logger=False` 自行管理主进程的 handler。
- `shutdown_logging()` 会卸载本进程内 kitty_logger 自己挂的
  `SocketHandler`，并清理 `KITTY_LOGGER_*` 环境变量，确保进程状态与
  `setup_logging` 对称。如果你额外挂了别的 handler，仍由你自己负责清理。

## 安全性

服务端用 `pickle.loads` 反序列化收到的字节流——这等价于在本机上对攻击者
可控的输入做"任意代码执行"。为了让这个能力**永远不会跨出本机**，
`setup_logging` 在打开 socket 之前就会**直接拒绝任何非 loopback 绑定**
（`ValueError`）。监听端口仅本机可达；请把它视为受信任的内部接口，**不要**
在不能信任本机其他进程的多租户机器上运行。
