err_log.py 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. # -*- coding: utf-8 -*-
  2. """
  3. 异常日志 — 按设备分文件记录采集过程异常,JSONL 格式(每行一个JSON对象)。
  4. 文件位置: logs/errors/{设备号}/{设备号}_YYYYMMDD.jsonl
  5. 字段: ts=本地时间, device=设备号, source=发生点标识, type=异常类名或event,
  6. message=错误消息, traceback=堆栈(有异常对象时), extra=附加上下文dict
  7. 用法:
  8. from commons import err_log
  9. err_log.log_error(ex.device_id, "task_exception", exc=e, extra={"task_id": tid})
  10. 设计: 记录失败静默吞掉,绝不影响采集主流程;进程内加锁,多进程各写各的设备文件互不冲突。
  11. """
  12. import json
  13. import threading
  14. import traceback
  15. from datetime import datetime
  16. from pathlib import Path
  17. _BASE = Path(__file__).resolve().parent.parent / "logs" / "errors"
  18. _LOCK = threading.Lock()
  19. def log_error(device_id: str, source: str, exc=None, message: str = "", extra: dict = None):
  20. """记录一条异常/错误事件。传 exc=异常对象(自动带类型+堆栈),或仅 message 描述事件。"""
  21. try:
  22. rec = {
  23. "ts": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  24. "device": str(device_id or "unknown"),
  25. "source": source,
  26. "type": type(exc).__name__ if exc else "event",
  27. "message": str(exc) if exc else str(message),
  28. }
  29. if exc is not None:
  30. tb = traceback.format_exc(limit=8)
  31. if tb and "NoneType: None" not in tb:
  32. rec["traceback"] = tb
  33. if extra:
  34. rec["extra"] = extra
  35. d = _BASE / rec["device"]
  36. d.mkdir(parents=True, exist_ok=True)
  37. path = d / f"{rec['device']}_{datetime.now().strftime('%Y%m%d')}.jsonl"
  38. with _LOCK:
  39. with open(path, "a", encoding="utf-8") as f:
  40. f.write(json.dumps(rec, ensure_ascii=False) + "\n")
  41. except Exception:
  42. pass # 日志失败不影响主流程
  43. def log_pressure(device_id: str, data: dict):
  44. """账号压力日志: 每任务结束追加一条累计统计(crawled/tasks/captcha/runtime),
  45. 与 account_kicked 事件对照 → 分析风控封号的压力阈值。
  46. 文件: logs/pressure/{设备}_{日期}.jsonl"""
  47. try:
  48. rec = {"ts": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  49. "device": str(device_id or "unknown")}
  50. rec.update(data or {})
  51. d = _BASE.parent / "pressure" / rec["device"]
  52. d.mkdir(parents=True, exist_ok=True)
  53. path = d / f"{rec['device']}_{datetime.now().strftime('%Y%m%d')}.jsonl"
  54. with _LOCK:
  55. with open(path, "a", encoding="utf-8") as f:
  56. f.write(json.dumps(rec, ensure_ascii=False) + "\n")
  57. except Exception:
  58. pass