buildRequestLog($request); $responseLog = null; if ($response instanceof Response) { $responseLog = $this->buildResponseLog($response); } Log::info('api_access', [ 'request' => $requestLog, 'response' => $responseLog, 'duration_ms' => $durationMs, 'exception' => $thrown ? [ 'class' => $thrown::class, 'message' => $thrown->getMessage(), ] : null, ]); } } /** * @return array */ private function buildRequestLog(Request $request): array { $headers = $request->header(); if (!is_array($headers)) { $headers = []; } $headersOut = []; foreach ($headers as $name => $value) { $nameStr = strtolower((string) $name); if ($this->isSensitiveHeaderName($nameStr)) { $headersOut[$nameStr] = $this->maskToken(is_string($value) ? $value : (string) $value); } else { $headersOut[$nameStr] = $value; } } $get = $request->get(); $post = $request->post(); $params = []; if (is_array($get)) { $params = array_merge($params, $get); } if (is_array($post)) { $params = array_merge($params, $post); } $params = $this->sanitizeForLog($params); $paramsJson = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); if (!is_string($paramsJson)) { $paramsJson = ''; } if (strlen($paramsJson) > self::REQUEST_PARAM_MAX_LEN) { $paramsJson = substr($paramsJson, 0, self::REQUEST_PARAM_MAX_LEN) . '...(truncated)'; } return [ 'method' => $request->method(), 'path' => $request->path(), 'uri' => $request->uri(), 'ip' => method_exists($request, 'getRealIp') ? $request->getRealIp() : ($request->getRemoteIp() ?? ''), 'content_type' => $request->header('content-type', ''), 'headers' => $headersOut, 'params' => $paramsJson, 'agent_id' => $request->agent_id ?? null, 'player_id' => $request->player_id ?? null, ]; } /** * @return array */ private function buildResponseLog(Response $response): array { $raw = method_exists($response, 'rawBody') ? $response->rawBody() : ''; if (!is_string($raw)) { $raw = ''; } $bodyForLog = $raw; $decoded = json_decode($raw, true); if (is_array($decoded)) { $sanitized = $this->sanitizeForLog($decoded); $encoded = json_encode($sanitized, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); if (is_string($encoded)) { $bodyForLog = $encoded; } } if (strlen($bodyForLog) > self::RESPONSE_BODY_MAX_LEN) { $bodyForLog = substr($bodyForLog, 0, self::RESPONSE_BODY_MAX_LEN) . '...(truncated)'; } return [ 'status' => $response->getStatusCode(), 'body' => $bodyForLog, ]; } private function isSensitiveHeaderName(string $name): bool { return in_array($name, self::SENSITIVE_HEADER_NAMES, true); } private function isSensitiveParamKey(string $keyLower): bool { foreach (self::SENSITIVE_PARAM_KEYS as $s) { if ($keyLower === $s) { return true; } } return false; } private function maskToken(string $value): string { $t = trim($value); if ($t === '') { return ''; } if (strlen($t) <= 12) { return '***'; } return substr($t, 0, 8) . '***' . substr($t, -4); } /** * @param mixed $value * @return mixed */ private function sanitizeForLog($value) { if (!is_array($value)) { return $value; } $out = []; foreach ($value as $k => $v) { $keyLower = is_string($k) ? strtolower($k) : ''; if ($keyLower !== '' && $this->isSensitiveParamKey($keyLower)) { $out[$k] = '***'; continue; } if (is_array($v)) { $out[$k] = $this->sanitizeForLog($v); continue; } $out[$k] = $v; } return $out; } }