惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Troy Hunt's Blog
Cloudbric
Cloudbric
Security Archives - TechRepublic
Security Archives - TechRepublic
WordPress大学
WordPress大学
Recent Commits to openclaw:main
Recent Commits to openclaw:main
W
WeLiveSecurity
GbyAI
GbyAI
V2EX - 技术
V2EX - 技术
Latest news
Latest news
Engineering at Meta
Engineering at Meta
TaoSecurity Blog
TaoSecurity Blog
K
Kaspersky official blog
PCI Perspectives
PCI Perspectives
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 叶小钗
I
InfoQ
Y
Y Combinator Blog
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
The Register - Security
The Register - Security
U
Unit 42
F
Full Disclosure
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
D
DataBreaches.Net
博客园 - 聂微东
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
博客园 - Franky
M
MIT News - Artificial intelligence
爱范儿
爱范儿
N
Netflix TechBlog - Medium
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
S
SegmentFault 最新的问题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Threat Research - Cisco Blogs
腾讯CDC
S
Securelist
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
AI
AI
Simon Willison's Weblog
Simon Willison's Weblog
Google Online Security Blog
Google Online Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Cybersecurity and Infrastructure Security Agency CISA
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More

博客园 - papering

QT 主线程 优化 卡顿 主线程上的同步重活 防重入:进行中直接 return,避免连点双开。 把「一次性任务」收到 ThreadPoolExecutor(max_workers=2~3),限制峰值线程数 统一 Activity 浮层 去掉连环成功弹窗 为 with语句上下文提供的工具 Canvas 指纹 魔改chromium源码——CDP(Chrome DevTools Protocol)检测01 whether the browser environment is controlled by a robot. chromium指纹魔改 对拷线 rpa 任务编排 a JSON formatted stream to ``fp`` “幽灵字符”问题 浏览器背后的黑科技 多进程 多线程 callback technique: signals and slots chrome 浏览器 调试 数据采集 Chrome DevTools ProtocolCDP PyInstaller 实现延迟导入 Storage mount options 汽车 OTA 技术体系 使用 grep 的扩展正则表达式功能 Python 中双下划线会触发名称修饰(mangling),通常用于避免子类覆盖, 容器内没有安装curl 验证web服务存在 在容器内执命令 连接数据库 http响应的 headers中拿到文件名 复制python项目文件,包含 包依赖 物理机 虚拟机 云厂商 裸金属服务器 CPU 透传 CPU Passthrough Docker 环境权限与资源限制 can't start new thread Seccomp security profiles Don't let containers reboot the host. Registry Pattern search_after 深度分页 Scrolling is not intended for real time user requests no longer recommend using the scroll API for deep pagination point in time PIT preserve the current index state kafka 大消 小消 依赖于中央数据库来协调网络中的查询 使用泛滥式查询(query flooding)来查询文件,搜索网络中的所有节点 使用分布式散列表来查询网络中的文件 使用 DHT (Distributed Hash Table,分布式哈希表) 替代 Tracker,标志着 P2P 系统从“有中心”进化到了“完全去中心化”。 STUN 负责“问路”(发现 IP),Tracker 负责“找人”(发现节点),而打洞失败时则靠 TURN “代跑”(转发数据) p2p 视频传输方案 UDP 打洞 attached detached shared memory 共享内存 shm ipcs folders of instructions, scripts, and resources that Claude loads dynamically to improve performance on specialized tasks complete specific tasks in a repeatable way 词元 指令 分词器 向量化 计算关系 输出回答 QQ 25 年进化史:从UDP到NT架构,支撑亿级在线的技术之路 如何设计一个IM单聊架构 长链接业务层 短链接业务层 go java python dart 内存模型区别 go java python 面向对象区别 常规价将被永久拉低,难以重建价格锚点 AI对比功能强调的是产品的真实价值 LLM provider configuration luckclaw AI 智能体 Gateway 网关 技术架构的每一次重大更新,都被称作一次“脱壳” 具备“手脚”去在真实操作系统中“执行”任务的数字实体 供应链攻击从“欺骗人类”向“操纵AI逻辑”的演进 字符串相等 地址比较 内容比较 ‌ 零宽不连字, 不可见字符 ch.isprintable() purge会移除配置文件,而remove仅删除程序文件。 c# 异步 同步 请求 生成式引擎优化 Generative Engine Optimization GEO 内容优化策略 python lua 传值 传址 用AI原生的思维重新构建产品
懒加载 IDE发现 import
papering · 2026-07-06 · via 博客园 - papering
_LOGGER_EXPORTS = {

    "db_logger": "db",

"web_logger": "web", } def __getattr__(name: str) -> logging.Logger: logger_name = _LOGGER_EXPORTS.get(name) if logger_name is not None: return get_logger(logger_name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

from src.core.logger import web_logger

懒加载实现

# -*- coding: utf-8 -*-
"""
统一日志模块。

Level 约定(生产默认 INFO):
- DEBUG:HTTP/DB 明细、轮询重试、Handler 内部步骤、参数/响应 dump
- INFO:任务生命周期、派发/完成、FTP 出站成功、Token 换票、批次任务摘要
- WARNING:可恢复异常、跳过、降级路径
- ERROR/EXCEPTION:失败、需介入

维护脚本:scripts/optimize_log_levels.py
"""
from __future__ import annotations

import gzip
import logging
import os
import shutil
import sys
from datetime import datetime
from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from typing import cast

from src.core.config import LoggingFileConfig, get_settings

_DIR_MAINTAIN_INTERVAL_SEC = 60
_SIZE_CHECK_EVERY = 256  # 每 N 条日志检查一次单文件大小,降低 I/O

_LEVEL_SPECS: tuple[tuple[str, int], ...] = (
    ("debug", logging.DEBUG),
    ("info", logging.INFO),
    ("warning", logging.WARNING),
    ("error", logging.ERROR),
    ("critical", logging.CRITICAL),
)

_loggers: dict[str, logging.Logger] = {}
_file_handlers: dict[str, dict[str, SizeLimitedTimedRotatingFileHandler]] = {}
_console_handler: logging.StreamHandler | None = None
_formatter: logging.Formatter | None = None
_last_dir_maintain_at: float = 0.0


class _ExactLevelFilter(logging.Filter):
    """仅放行指定 level 的日志记录。"""

    def __init__(self, level: int) -> None:
        super().__init__()
        self._level = level

    def filter(self, record: logging.LogRecord) -> bool:
        return record.levelno == self._level


class SizeLimitedTimedRotatingFileHandler(TimedRotatingFileHandler):
    """按天轮转,单文件超过 maxBytes 时压缩并截断。"""

    def __init__(self, *args, max_bytes: int, **kwargs):
        super().__init__(*args, **kwargs)
        self.maxBytes = max_bytes
        self._rollover_reason = "time"
        self._records_since_size_check = 0

    def shouldRollover(self, record: logging.LogRecord) -> bool:
        if super().shouldRollover(record):
            self._rollover_reason = "time"
            return True
        if self.maxBytes <= 0:
            return False
        self._records_since_size_check += 1
        if self._records_since_size_check < _SIZE_CHECK_EVERY:
            return False
        self._records_since_size_check = 0
        if self.stream is None:
            self.stream = self._open()
        self.stream.seek(0, 2)
        if self.stream.tell() >= self.maxBytes:
            self._rollover_reason = "size"
            return True
        return False

    def doRollover(self) -> None:
        if self._rollover_reason == "size":
            if self.stream:
                self.stream.close()
                self.stream = None
            _compress_and_truncate(self.baseFilename, self.maxBytes)
            if not self.delay:
                self.stream = self._open()
        else:
            super().doRollover()
        _maintain_log_dir(Path(self.baseFilename).parent, force=True)


def _resolve_file_key(name: str) -> str:
    """
    统一日志文件归类键,减少零散 log 文件数量。

    - handlers.* / src.handlers.* -> handlers
    - src.services.* -> services
    - src.utils.* -> utils
    - src.apps.rpatask.* -> rpatask
    - src.apps.tokenpool.* -> tokenpool
    - src.apps.* -> apps
    - providers.* -> providers
    - 其他模块按 logger name 各自落盘
    """
    if name == "handlers" or name.startswith("handlers.") or name.startswith("src.handlers."):
        return "handlers"

    if name == "utils" or name.startswith("src.utils."):
        return "utils"

    if name.startswith("src.apps."):
        return "apps"

    return name


def _get_formatter() -> logging.Formatter:
    global _formatter
    if _formatter is None:
        _formatter = logging.Formatter(
            fmt="%(asctime)s | %(levelname)-8s | [%(name)s] | PID=%(process)d | %(filename)s:%(lineno)d | %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )
    return _formatter


def _ensure_level_handlers(
    file_key: str,
    log_dir: Path,
    formatter: logging.Formatter,
    min_level: int,
    log_cfg: LoggingFileConfig,
) -> dict[str, SizeLimitedTimedRotatingFileHandler]:
    """为 file_key 创建各级别独立文件 handler(仅创建 >= min_level 的级别)。"""
    level_handlers = _file_handlers.setdefault(file_key, {})
    created = False
    max_bytes = log_cfg.max_file_size_gb * 1024 ** 3

    for level_name, level_no in _LEVEL_SPECS:
        if level_no < min_level or level_name in level_handlers:
            continue
        log_file = log_dir / f"{file_key}.{level_name}.log"
        handler = SizeLimitedTimedRotatingFileHandler(
            filename=log_file,
            when="midnight",
            interval=1,
            backupCount=log_cfg.retention_days,
            encoding="utf-8",
            max_bytes=max_bytes,
        )
        handler.suffix = "%Y-%m-%d.gz"
        handler.setLevel(level_no)
        handler.addFilter(_ExactLevelFilter(level_no))
        handler.setFormatter(formatter)
        handler.rotator = _log_rotator
        level_handlers[level_name] = handler
        created = True

    if created:
        _maintain_log_dir(log_dir)
    return level_handlers


def get_logger(name: str = "rpa") -> logging.Logger:
    """获取 logger,相同 name 返回同一实例。"""
    if name in _loggers:
        return _loggers[name]
    settings = get_settings()
    log_cfg = settings.logging
    level = getattr(logging, log_cfg.level.upper(), logging.INFO)
    logger = logging.getLogger(name)
    logger.setLevel(level)
    logger.propagate = False
    formatter = _get_formatter()
    if log_cfg.file.enabled:
        file_key = _resolve_file_key(name)
        log_dir = Path(log_cfg.file.path)
        log_dir.mkdir(parents=True, exist_ok=True)
        for handler in _ensure_level_handlers(
            file_key, log_dir, formatter, level, log_cfg.file
        ).values():
            if handler not in logger.handlers:
                logger.addHandler(handler)
    if log_cfg.console:
        global _console_handler
        if _console_handler is None:
            _console_handler = logging.StreamHandler(sys.stdout)
            _console_handler.setFormatter(formatter)
        logger.addHandler(_console_handler)
    _loggers[name] = logger
    return logger


def _get_active_log_filenames() -> set[str]:
    names: set[str] = set()
    for level_handlers in _file_handlers.values():
        for handler in level_handlers.values():
            names.add(Path(handler.baseFilename).name)
    return names


def _is_active_log_file(path: Path) -> bool:
    return path.name in _get_active_log_filenames()


def _is_log_backup(path: Path) -> bool:
    if path.name in _get_active_log_filenames():
        return False
    return path.suffix == ".gz" or path.suffix == ".log" and ".log." in path.name


def _log_level_deletion_rank(path: Path) -> int:
    """删除优先级:数值越小越先删(debug 最先)。"""
    lower = path.name.lower()
    if ".debug." in lower or lower.endswith(".debug.log"):
        return 0
    return 1


def _should_run_dir_maintenance(force: bool) -> bool:
    global _last_dir_maintain_at
    now = datetime.now().timestamp()
    if not force and now - _last_dir_maintain_at < _DIR_MAINTAIN_INTERVAL_SEC:
        return False
    _last_dir_maintain_at = now
    return True


def _maintain_log_dir(log_dir: Path, *, force: bool = False) -> None:
    """压缩非活跃日志,并按保留天数与目录总大小上限清理。"""
    if not _should_run_dir_maintenance(force):
        return
    log_cfg = get_settings().logging.file
    _compress_inactive_logs(log_dir)
    _cleanup_expired_logs(log_dir, log_cfg.retention_days)
    _enforce_log_dir_size_limit(log_dir, log_cfg.max_dir_size_gb * 1024 ** 3)


def _compress_inactive_logs(log_dir: Path) -> None:
    """除正在写入的日志外,将目录内未压缩的日志备份 gzip 压缩。"""
    active = _get_active_log_filenames()
    try:
        for path in log_dir.iterdir():
            if not path.is_file() or path.name in active:
                continue
            if path.suffix == ".gz":
                continue
            if path.suffix != ".log" and ".log." not in path.name:
                continue
            dest = path.with_name(f"{path.name}.gz")
            counter = 0
            while dest.exists():
                counter += 1
                dest = path.with_name(f"{path.name}_{counter}.gz")
            if _gzip_to_file(str(path), str(dest)):
                path.unlink(missing_ok=True)
    except Exception:
        pass


def _cleanup_expired_logs(log_dir: Path, retention_days: int) -> None:
    """删除超过 retention_days 的全部日志备份(按 mtime)。"""
    if retention_days <= 0:
        return
    try:
        cutoff = datetime.now().timestamp() - retention_days * 86400
        for path in log_dir.iterdir():
            if not path.is_file() or not _is_log_backup(path):
                continue
            if path.stat().st_mtime < cutoff:
                path.unlink(missing_ok=True)
    except Exception:
        pass


def _enforce_log_dir_size_limit(log_dir: Path, max_bytes: int) -> None:
    """目录总大小超限时删除备份;同条件下优先删 debug,再按时间从旧到新。"""
    if max_bytes <= 0:
        return
    try:
        files = [p for p in log_dir.iterdir() if p.is_file()]
        if not files:
            return

        file_stats = [(p, p.stat()) for p in files]
        total = sum(st.st_size for _, st in file_stats)
        if total <= max_bytes:
            return

        file_stats.sort(key=lambda item: (
            _is_active_log_file(item[0]),
            _log_level_deletion_rank(item[0]),
            item[1].st_mtime,
        ))
        for path, st in file_stats:
            if total <= max_bytes:
                break
            try:
                path.unlink(missing_ok=True)
                total -= st.st_size
            except Exception:
                pass
    except Exception:
        pass


def _gzip_to_file(source: str, dest: str) -> bool:
    if not os.path.exists(source) or os.path.getsize(source) == 0:
        return False
    if os.path.exists(dest):
        return False
    with open(source, "rb") as f_in:
        with gzip.open(dest, "wb", compresslevel=6) as f_out:
            shutil.copyfileobj(f_in, f_out)
    return True


def _truncate_file(source: str) -> None:
    with open(source, "w", encoding="utf-8"):
        pass


def _log_rotator(source: str, dest: str) -> None:
    """按天轮转:gzip 压缩备份并截断原文件(全平台 copy+truncate)。"""
    try:
        _gzip_to_file(source, dest)
        _truncate_file(source)
    except Exception:
        pass


def _compress_and_truncate(source: str, max_bytes: int) -> None:
    """单文件超过大小上限时:gzip 压缩当前内容,再截断原文件。"""
    try:
        if not os.path.exists(source):
            return
        size = os.path.getsize(source)
        if size == 0 or size < max_bytes:
            return

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        dest = f"{source}.{timestamp}.gz"
        counter = 0
        while os.path.exists(dest):
            counter += 1
            dest = f"{source}.{timestamp}_{counter}.gz"

        _gzip_to_file(source, dest)
        _truncate_file(source)
    except Exception:
        pass


# 预定义 logger:模块级显式导出供 IDE 跳转;首次写日志时才 init handler。
class _LazyLogger:
    __slots__ = ("_name", "_logger")

    def __init__(self, name: str) -> None:
        self._name = name
        self._logger: logging.Logger | None = None

    def _resolve(self) -> logging.Logger:
        if self._logger is None:
            self._logger = get_logger(self._name)
        return self._logger

    def __getattr__(self, name: str):
        return getattr(self._resolve(), name)

    def __repr__(self) -> str:
        if self._logger is not None:
            return repr(self._logger)
        return f"<LazyLogger {self._name!r}>"


def _lazy_logger(name: str) -> logging.Logger:
    return cast(logging.Logger, _LazyLogger(name))



db_logger = _lazy_logger("db")

web_logger = _lazy_logger("web")


__all__ = [
    "get_logger",

    "db_logger",

    "web_logger",

]