$context */ public static function log(string $stage, string $message, array $context = []): void { if (!self::isEnabled()) { return; } $context = self::sanitize($context); $ctxJson = $context !== [] ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : ''; $line = sprintf( "[%s] %s [%s] %s%s\n", date('Y-m-d H:i:s'), self::$ticketTag ?? '[global]', $stage, $message, $ctxJson ); $runtime = defined('RUNTIME_PATH') ? RUNTIME_PATH : (ROOT_PATH . 'runtime/'); $dir = $runtime . 'log/'; if (!is_dir($dir)) { @mkdir($dir, 0755, true); } @file_put_contents($dir . self::LOG_FILE, $line, FILE_APPEND | LOCK_EX); } /** * @param array $context * @return array */ private static function sanitize(array $context): array { $out = []; foreach ($context as $key => $value) { $lower = strtolower((string) $key); if (in_array($lower, ['password', 'passwd', 'pwd', 'token', 'secret'], true)) { continue; } if ($key === 'authActions' && is_array($value)) { $out[$key] = self::sanitizeAuthActions($value); continue; } if (is_array($value)) { $out[$key] = self::sanitize($value); continue; } if (is_string($value) && mb_strlen($value) > 800) { $out[$key] = mb_substr($value, 0, 800, 'UTF-8') . '...(truncated)'; continue; } $out[$key] = $value; } return $out; } /** * @param array $actions * @return array */ private static function sanitizeAuthActions(array $actions): array { $sanitized = []; foreach ($actions as $action) { if (!is_array($action)) { $sanitized[] = $action; continue; } if (array_key_exists('value', $action)) { $action['value'] = '***'; } $sanitized[] = $action; } return $sanitized; } }