true, 'http_observability.slow_request.threshold_ms' => 750, 'logging.channels.slow-http.driver' => 'single', 'logging.channels.slow-http.path' => slowHttpLogPath(), 'logging.channels.slow-http.level' => 'info', ]); Log::forgetChannel('slow-http'); File::delete(slowHttpLogPath()); } afterEach(function (): void { File::delete(slowHttpLogPath()); }); it('does not write a slow-http record for a fast request', function () { configureSlowHttpLog(); config(['http_observability.slow_request.test_duration_ms' => 120]); $this->get('/leaderboard')->assertOk(); expect(File::exists(slowHttpLogPath()))->toBeFalse(); }); it('writes one structured slow-http record using the route template', function () { configureSlowHttpLog(); config(['http_observability.slow_request.test_duration_ms' => 900]); $response = $this->get('/leaderboard'); $response->assertOk(); expect($response->headers->get('Server-Timing'))->toContain('app;desc="Laravel"'); $raw = File::get(slowHttpLogPath()); expect(substr_count(trim($raw), "\n"))->toBe(0); $line = trim($raw); preg_match('/\{.*\}\s*$/', $line, $matches); expect($matches[0] ?? null)->not->toBeNull(); $json = json_decode($matches[0], true, flags: JSON_THROW_ON_ERROR); expect($json['method'])->toBe('GET') ->and($json['status'])->toBe(200) ->and($json['duration_ms'])->toEqual(900) ->and($json['route_uri'])->not->toContain('?') ->and($json['route_uri'])->not->toContain('leaderboard/') ->and($json)->not->toHaveKey('query') ->and($json)->not->toHaveKey('ip') ->and($json)->not->toHaveKey('email') ->and($json)->not->toHaveKey('user_id') ->and($json['authenticated'])->toBeFalse(); }); it('does not log when slow-request logging is disabled', function () { configureSlowHttpLog(); config([ 'http_observability.slow_request.enabled' => false, 'http_observability.slow_request.test_duration_ms' => 5000, ]); $this->get('/leaderboard')->assertOk()->assertHeader('Server-Timing'); expect(File::exists(slowHttpLogPath()))->toBeFalse(); }); it('does not break a missing-file download response and still sends Server-Timing', function () { configureSlowHttpLog(); config(['http_observability.slow_request.test_duration_ms' => 100]); $artwork = Artwork::factory()->create([ 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), 'file_ext' => 'jpg', 'hash' => str_repeat('cd', 32), ]); $this->get('/download/artwork/'.$artwork->id) ->assertNotFound() ->assertHeader('Server-Timing'); }); it('does not log query strings or user identifiers on an authenticated slow request', function () { configureSlowHttpLog(); config(['http_observability.slow_request.test_duration_ms' => 800]); $user = User::factory()->create([ 'email' => 'slow-log-secret@example.com', 'username' => 'slowlogsecret', ]); $this->actingAs($user)->get('/leaderboard?period=weekly&debug=1')->assertOk(); $raw = File::get(slowHttpLogPath()); expect($raw)->not->toContain('slow-log-secret@example.com') ->and($raw)->not->toContain('period=weekly') ->and($raw)->not->toContain('debug=1') ->and($raw)->not->toContain('"user_id"') ->and($raw)->not->toContain($user->email); });