Logger.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. <?php
  2. /**
  3. * 重建工程专用日志器。
  4. *
  5. * 迭代流程是「脚本跑 → 日志和报告交给 LLM 评估 → 改规则 → 再跑」,所以日志的读者
  6. * 是模型不是人:宁可啰嗦,但必须结构化。unresolved() 记录的是脚本自己判定
  7. * 「我处理不了」的点,它们是下一轮规则迭代的输入,单独成文件。
  8. */
  9. class RpLogger
  10. {
  11. private $handle;
  12. private array $counters = [];
  13. /** @var array<int, array<string, mixed>> */
  14. private array $unresolved = [];
  15. private float $startedAt;
  16. public function __construct(
  17. private string $name,
  18. private string $reportDir,
  19. private bool $echoToStdout = true,
  20. ) {
  21. $this->startedAt = microtime(true);
  22. $this->handle = fopen($this->reportDir."/{$name}.log", 'w');
  23. $this->line('INFO', "==== $name 开始 ".date('Y-m-d H:i:s').' ====');
  24. }
  25. public function info(string $message): void
  26. {
  27. $this->line('INFO', $message);
  28. }
  29. public function warn(string $message): void
  30. {
  31. $this->line('WARN', $message);
  32. }
  33. public function error(string $message): void
  34. {
  35. $this->line('ERROR', $message);
  36. }
  37. /** 计数器:高频事件不逐条打印,只累计,收尾时汇总。 */
  38. public function count(string $key, int $n = 1): void
  39. {
  40. $this->counters[$key] = ($this->counters[$key] ?? 0) + $n;
  41. }
  42. public function counters(): array
  43. {
  44. return $this->counters;
  45. }
  46. /**
  47. * 脚本无法判定的点。type 是规则名,供下一轮按类型聚合决定要不要加规则。
  48. */
  49. public function unresolved(string $type, array $context): void
  50. {
  51. $this->unresolved[] = ['type' => $type] + $context;
  52. $this->count("unresolved.$type");
  53. }
  54. /** 收尾:写计数汇总与 unresolved 清单,返回汇总数组。 */
  55. public function finish(): array
  56. {
  57. $elapsed = round(microtime(true) - $this->startedAt, 1);
  58. ksort($this->counters);
  59. $this->line('INFO', '---- 计数汇总 ----');
  60. foreach ($this->counters as $key => $value) {
  61. $this->line('INFO', sprintf(' %-46s %10d', $key, $value));
  62. }
  63. $byType = [];
  64. foreach ($this->unresolved as $item) {
  65. $byType[$item['type']] = ($byType[$item['type']] ?? 0) + 1;
  66. }
  67. if ($byType !== []) {
  68. $this->line('WARN', '---- 未决问题(按类型) ----');
  69. foreach ($byType as $type => $n) {
  70. $this->line('WARN', sprintf(' %-46s %10d', $type, $n));
  71. }
  72. }
  73. $path = $this->reportDir."/{$this->name}.unresolved.json";
  74. rp_json_write($path, [
  75. 'generated_at' => date('c'),
  76. 'total' => count($this->unresolved),
  77. 'by_type' => $byType,
  78. 'items' => $this->unresolved,
  79. ]);
  80. $this->line('INFO', "未决清单:$path");
  81. $this->line('INFO', "==== {$this->name} 结束,耗时 {$elapsed}s ====");
  82. fclose($this->handle);
  83. return [
  84. 'counters' => $this->counters,
  85. 'unresolved_total' => count($this->unresolved),
  86. 'unresolved_by_type' => $byType,
  87. 'elapsed_seconds' => $elapsed,
  88. ];
  89. }
  90. private function line(string $level, string $message): void
  91. {
  92. $row = sprintf('[%s] %-5s %s', date('H:i:s'), $level, $message);
  93. fwrite($this->handle, $row."\n");
  94. if ($this->echoToStdout) {
  95. fwrite(STDOUT, $row."\n");
  96. }
  97. }
  98. }