recordLog($request, null, $startAt, 500); throw $exception; } $this->recordLog($request, $response, $startAt, $response->getStatusCode()); return $response; } private function shouldLog(Request $request): bool { $methods = config('toolbox.operation_log.methods', []); if (!in_array($request->method(), $methods, true)) { return false; } return $request->is('api/*'); } private function recordLog(Request $request, ?Response $response, float $startAt, int $statusCode): void { if (!$this->shouldLog($request)) { return; } try { $ipAddress = $request->ip() ?? ''; $mapping = $ipAddress !== '' ? IpUserMapping::query()->where('ip_address', $ipAddress)->first() : null; OperationLog::query()->create([ 'ip_address' => $ipAddress, 'user_label' => $mapping?->user_name, 'method' => $request->method(), 'path' => '/' . ltrim($request->path(), '/'), 'route_name' => $request->route()?->getName(), 'status_code' => $statusCode, 'duration_ms' => (int) round((microtime(true) - $startAt) * 1000), 'request_payload' => $this->buildPayload($request), 'user_agent' => substr((string) $request->userAgent(), 0, 255), ]); } catch (Throwable $exception) { Log::warning('Failed to record operation log.', [ 'error' => $exception->getMessage(), ]); } } /** * @return array */ private function buildPayload(Request $request): array { $payload = $request->all(); if (empty($payload)) { return []; } $redactKeys = array_map('strtolower', config('toolbox.operation_log.redact_keys', [])); $payload = $this->redactPayload($payload, $redactKeys); $encoded = json_encode($payload, JSON_UNESCAPED_UNICODE); if ($encoded === false) { return ['_unserializable' => true]; } $maxLength = (int) config('toolbox.operation_log.max_payload_length', 2000); if (strlen($encoded) <= $maxLength) { return $payload; } return [ '_truncated' => true, 'preview' => substr($encoded, 0, $maxLength), ]; } /** * @param array $payload * @param array $redactKeys * @return array */ private function redactPayload(array $payload, array $redactKeys): array { $redacted = []; foreach ($payload as $key => $value) { $lowerKey = strtolower((string) $key); if (in_array($lowerKey, $redactKeys, true)) { $redacted[$key] = '[redacted]'; continue; } if (is_array($value)) { $redacted[$key] = $this->redactPayload($value, $redactKeys); continue; } $redacted[$key] = $value; } return $redacted; } }