log.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <?php
  2. declare(strict_types=1);
  3. // JSONL logging with size-based rotation.
  4. function manageErrorLogFile(): string
  5. {
  6. return manageLogsDir() . "error.log";
  7. }
  8. function manageAccessLogFile(): string
  9. {
  10. return manageLogsDir() . "access.log";
  11. }
  12. function manageRotateLogFileIfNeeded(string $file): void
  13. {
  14. $maxBytes = (int) MANAGE_LOG_MAX_BYTES;
  15. if ($maxBytes < 1 || !is_file($file)) {
  16. return;
  17. }
  18. $size = filesize($file);
  19. if ($size === false || $size < $maxBytes) {
  20. manageDeleteExpiredLogFiles($file);
  21. return;
  22. }
  23. $keepFiles = max(1, (int) MANAGE_LOG_KEEP_FILES);
  24. for ($i = $keepFiles; $i >= 1; $i--) {
  25. $source = $i === 1 ? $file : $file . "." . ($i - 1);
  26. $target = $file . "." . $i;
  27. if (!is_file($source)) {
  28. continue;
  29. }
  30. if ($i === $keepFiles && is_file($target)) {
  31. @unlink($target);
  32. }
  33. @rename($source, $target);
  34. }
  35. manageDeleteExpiredLogFiles($file);
  36. }
  37. function manageDeleteExpiredLogFiles(string $file): void
  38. {
  39. $maxAge = (int) MANAGE_LOG_MAX_AGE_SECONDS;
  40. if ($maxAge < 1) {
  41. return;
  42. }
  43. foreach (glob($file . ".*") ?: [] as $rotated) {
  44. $mtime = @filemtime($rotated);
  45. if ($mtime !== false && time() - $mtime > $maxAge) {
  46. @unlink($rotated);
  47. }
  48. }
  49. }
  50. function manageWriteLogEntry(string $file, array $entry): void
  51. {
  52. try {
  53. manageEnsureDirectory(dirname($file));
  54. } catch (Throwable $exception) {
  55. return;
  56. }
  57. manageRotateLogFileIfNeeded($file);
  58. $line = json_encode($entry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  59. if ($line === false) {
  60. return;
  61. }
  62. @file_put_contents($file, $line . PHP_EOL, FILE_APPEND | LOCK_EX);
  63. }
  64. function manageLogError(string $message, array $context = [], string $level = "ERROR"): void
  65. {
  66. manageWriteLogEntry(manageErrorLogFile(), [
  67. "timestamp" => date("Y-m-d H:i:s.u"),
  68. "level" => $level,
  69. "message" => $message,
  70. "context" => $context,
  71. "ip" => $_SERVER["REMOTE_ADDR"] ?? "unknown",
  72. "user_agent" => substr((string) ($_SERVER["HTTP_USER_AGENT"] ?? "unknown"), 0, 300),
  73. "request_uri" => $_SERVER["REQUEST_URI"] ?? "unknown",
  74. ]);
  75. }
  76. function manageLogAccess(string $message, array $context = []): void
  77. {
  78. manageWriteLogEntry(manageAccessLogFile(), [
  79. "timestamp" => date("Y-m-d H:i:s.u"),
  80. "message" => $message,
  81. "context" => $context,
  82. "ip" => $_SERVER["REMOTE_ADDR"] ?? "unknown",
  83. "request_method" => $_SERVER["REQUEST_METHOD"] ?? "unknown",
  84. "request_uri" => $_SERVER["REQUEST_URI"] ?? "unknown",
  85. ]);
  86. }
  87. // Plain-text append-only log for S3 diagnostics. Kept separate from the JSONL
  88. // logs so the raw provider error stays readable in the management UI.
  89. function manageLogS3(string $message, array $context = []): void
  90. {
  91. try {
  92. manageEnsureDirectory(dirname(manageS3LogFile()));
  93. } catch (Throwable $exception) {
  94. return;
  95. }
  96. $line = date(DATE_ATOM) . " " . $message;
  97. $encoded = @json_encode($context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
  98. if (is_string($encoded) && $encoded !== "[]") {
  99. $line .= " " . $encoded;
  100. }
  101. @file_put_contents(manageS3LogFile(), $line . PHP_EOL, FILE_APPEND | LOCK_EX);
  102. }
  103. function manageLogS3Tail(int $lines): array
  104. {
  105. $file = manageS3LogFile();
  106. if (!is_file($file) || $lines < 1) {
  107. return [];
  108. }
  109. $content = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  110. if (!is_array($content)) {
  111. return [];
  112. }
  113. return array_slice($content, -$lines);
  114. }