fix(logging): stamp origin server_id into panel error logs (LB vs MAIN)

panel_logs entries could not be attributed to their origin node: FileLogger wrote
each record WITHOUT server_id, and ErrorsCronJob::parseLog always tagged rows with
the SERVER_ID of whichever node parsed the file — so a log written on an LB but
collected/parsed as MAIN showed up as MAIN (server_id=1), making 'is this error
from the LB or MAIN?' unanswerable.

- FileLogger::log() now stamps server_id into the record at write time (guarded by
  defined('SERVER_ID') for very-early failures), so each log line is self-describing.
- ErrorsCronJob::parseLog() reads that server_id and inserts it, falling back to the
  local SERVER_ID for legacy files written before the field existed.
- server_id is now part of the dedup hash / `unique` key, so the same error from
  LB and MAIN stays two distinct rows instead of INSERT IGNORE collapsing them into
  one and losing the attribution.

The panel_logs table already has the server_id column; nothing schema-side to add.
This commit is contained in:
Divarion_D
2026-08-19 17:53:15 +03:00
parent 665c791f9e
commit 4eac1d8ca0
2 changed files with 16 additions and 1 deletions
+11 -1
View File
@@ -88,6 +88,12 @@ class ErrorsCronJob implements CommandInterface {
$rLogTime = (int) ($row['time'] ?? time());
$rLogFile = (string) ($row['file'] ?? '');
$rLogEnv = (string) ($row['env'] ?? php_sapi_name());
// Prefer the origin server stamped into the record (FileLogger); fall
// back to this node's SERVER_ID for legacy files written before the
// field existed. This is what lets a panel_logs row show LB vs MAIN.
$rLogServerID = (isset($row['server_id']) && is_numeric($row['server_id']))
? (int) $row['server_id']
: SERVER_ID;
if (
stripos($rLogMessage, 'server has gone away') !== false ||
@@ -97,7 +103,11 @@ class ErrorsCronJob implements CommandInterface {
continue;
}
// server_id is part of the key so the SAME error from LB and MAIN keeps
// two distinct panel_logs rows — INSERT IGNORE on `unique` would
// otherwise collapse them into one and lose the per-server attribution.
$hash = md5(
$rLogServerID .
$rLogType .
$rLogMessage .
$rLogExtra .
@@ -112,7 +122,7 @@ class ErrorsCronJob implements CommandInterface {
$query .= sprintf(
"(%d,%s,%s,%s,%s,%s,%s,%s,%s),",
SERVER_ID,
$rLogServerID,
$this->sqlValue($rLogType),
$this->sqlValue($rLogMessage),
$this->sqlValue($rLogExtra),
+5
View File
@@ -96,6 +96,11 @@ class FileLogger implements LoggerInterface {
'line' => $line,
'time' => time(),
'env' => php_sapi_name(),
// Origin node, stamped at write time so a log is self-describing (LB vs
// MAIN) regardless of which node later parses it. Guarded because a very
// early failure can log before SERVER_ID is defined; the collector then
// falls back to its own SERVER_ID.
'server_id' => defined('SERVER_ID') ? SERVER_ID : null,
];
$logLine = base64_encode(json_encode($rData, JSON_UNESCAPED_UNICODE)) . "\n";