diff --git a/config/pulse.php b/config/pulse.php index d2297b245..ae3173ccc 100644 --- a/config/pulse.php +++ b/config/pulse.php @@ -209,9 +209,7 @@ */ 'cache_keys' => [ - '^post:139$' => 'Post 139', - '^server:1\d{2}$' => 'Servers 100 - 199', - '^flight:.*' => 'All flights', + '/(.*)/' => '\1', ], /* diff --git a/src/Recorders/CacheInteractions.php b/src/Recorders/CacheInteractions.php index 3dae64bb0..d96d16e50 100644 --- a/src/Recorders/CacheInteractions.php +++ b/src/Recorders/CacheInteractions.php @@ -5,6 +5,7 @@ use Carbon\CarbonImmutable; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; +use Illuminate\Config\Repository; use Illuminate\Support\Str; use Laravel\Pulse\Entry; use Laravel\Pulse\Pulse; @@ -34,6 +35,7 @@ class CacheInteractions */ public function __construct( protected Pulse $pulse, + protected Repository $config, ) { // } @@ -45,15 +47,31 @@ public function record(CacheHit|CacheMissed $event): ?Entry { $now = new CarbonImmutable(); - if (Str::startsWith($event->key, ['illuminate:', 'laravel:pulse'])) { + if (Str::startsWith($event->key, ['illuminate:', 'laravel:pulse:'])) { return null; } return new Entry($this->table, [ 'date' => $now->toDateTimeString(), 'hit' => $event instanceof CacheHit, - 'key' => $event->key, + 'key' => fn () => $this->normalize($event->key), 'user_id' => $this->pulse->authenticatedUserIdResolver(), ]); } + + /** + * Normalize the cache key. + */ + protected function normalize(string $key): string + { + foreach ($this->config->get('pulse.cache_keys') as $pattern => $replacement) { + $normalized = preg_replace($pattern, $replacement, $key, count: $count); + + if ($count > 0) { + return $normalized; + } + } + + return $key; + } } diff --git a/tests/Feature/CacheInteractionsTest.php b/tests/Feature/CacheInteractionsTest.php index bfd0399dc..be298cf53 100644 --- a/tests/Feature/CacheInteractionsTest.php +++ b/tests/Feature/CacheInteractionsTest.php @@ -20,16 +20,11 @@ Carbon::setTestNow('2000-01-02 03:04:05'); Cache::get('cache-key'); - - expect(Pulse::entries())->toHaveCount(1); - Pulse::ignore(fn () => expect(DB::table('pulse_cache_interactions')->count())->toBe(0)); - Pulse::store(app(Ingest::class)); - expect(Pulse::entries())->toHaveCount(0); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(1); - expect((array) $cacheHits[0])->toEqual([ + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect($interactions[0])->toHaveProperties([ 'date' => '2000-01-02 03:04:05', 'user_id' => null, 'key' => 'cache-key', @@ -37,43 +32,39 @@ ]); }); -it('ignores any internal illuminate cache interactions', function () { - Carbon::setTestNow('2000-01-02 03:04:05'); - +it('ignores internal illuminate cache interactions', function () { Cache::get('illuminate:'); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(0); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(0); }); -it('ignores any internal pulse cache interactions', function () { - Carbon::setTestNow('2000-01-02 03:04:05'); - - Cache::get('laravel:pulse'); +it('ignores internal pulse cache interactions', function () { + Cache::get('laravel:pulse:foo'); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(0); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(0); }); -it('captures hits and misses', function () { +it('ingests hits', function () { Carbon::setTestNow('2000-01-02 03:04:05'); - Cache::put('hit', 123); + Cache::put('hit', 123); Cache::get('hit'); Cache::get('miss'); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(2); - expect((array) $cacheHits[0])->toEqual([ + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(2); + expect($interactions[0])->toHaveProperties([ 'date' => '2000-01-02 03:04:05', 'user_id' => null, 'key' => 'hit', 'hit' => 1, ]); - expect((array) $cacheHits[1])->toEqual([ + expect($interactions[1])->toHaveProperties([ 'date' => '2000-01-02 03:04:05', 'user_id' => null, 'key' => 'miss', @@ -87,9 +78,9 @@ Cache::get('cache-key'); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(1); - expect($cacheHits[0]->user_id)->toBe('567'); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect($interactions[0]->user_id)->toBe('567'); }); it('captures the authenticated user if they login after the interaction', function () { @@ -97,9 +88,9 @@ Auth::login(User::make(['id' => '567'])); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(1); - expect($cacheHits[0]->user_id)->toBe('567'); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect($interactions[0]->user_id)->toBe('567'); }); it('captures the authenticated user if they logout after the interaction', function () { @@ -109,9 +100,9 @@ Auth::logout(); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(1); - expect($cacheHits[0]->user_id)->toBe('567'); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect($interactions[0]->user_id)->toBe('567'); }); it('does not trigger an inifite loop when retriving the authenticated user from the database', function () { @@ -140,9 +131,9 @@ public function user() Cache::get('cache-key'); Pulse::store(app(Ingest::class)); - $cacheHits = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); - expect($cacheHits)->toHaveCount(1); - expect($cacheHits[0]->user_id)->toBe(null); + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect($interactions[0]->user_id)->toBe(null); }); it('quietly fails if an exception is thrown while preparing the entry payload', function () { @@ -178,3 +169,96 @@ public function hasUser() expect($interactions[1]->user_id)->toBe('567'); expect($interactions[2]->user_id)->toBe('789'); }); + +it('stores the original keys by default', function () { + Carbon::setTestNow('2000-01-02 03:04:05'); + + Cache::get('users:1234:profile'); + Pulse::store(app(Ingest::class)); + + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect((array) $interactions[0])->toEqual([ + 'date' => '2000-01-02 03:04:05', + 'user_id' => null, + 'key' => 'users:1234:profile', + 'hit' => 0, + ]); +}); + +it('can normalize cache keys', function () { + Carbon::setTestNow('2000-01-02 03:04:05'); + + Config::set('pulse.cache_keys', [ + '/users:\d+:profile/' => 'users:{user}:profile', + ]); + Cache::get('users:1234:profile'); + Pulse::store(app(Ingest::class)); + + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect((array) $interactions[0])->toEqual([ + 'date' => '2000-01-02 03:04:05', + 'user_id' => null, + 'key' => 'users:{user}:profile', + 'hit' => 0, + ]); +}); + +it('can use back references in normalized cache keys', function () { + Carbon::setTestNow('2000-01-02 03:04:05'); + + Config::set('pulse.cache_keys', [ + '/^([^:]+):([^:]+):baz/' => '\2:\1', + ]); + Cache::get('foo:bar:baz'); + Pulse::store(app(Ingest::class)); + + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect((array) $interactions[0])->toEqual([ + 'date' => '2000-01-02 03:04:05', + 'user_id' => null, + 'key' => 'bar:foo', + 'hit' => 0, + ]); +}); + +it('uses the original key if no matching pattern is found', function () { + Carbon::setTestNow('2000-01-02 03:04:05'); + + Config::set('pulse.cache_keys', [ + '/\d/' => 'foo', + ]); + Cache::get('actual-key'); + Pulse::store(app(Ingest::class)); + + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect((array) $interactions[0])->toEqual([ + 'date' => '2000-01-02 03:04:05', + 'user_id' => null, + 'key' => 'actual-key', + 'hit' => 0, + ]); +}); + +it('can provide regex flags in normalization key', function () { + Carbon::setTestNow('2000-01-02 03:04:05'); + + Config::set('pulse.cache_keys', [ + '/foo/i' => 'lowercase-key', + '/FOO/i' => 'uppercase-key', + ]); + Cache::get('FOO'); + Pulse::store(app(Ingest::class)); + + $interactions = Pulse::ignore(fn () => DB::table('pulse_cache_interactions')->get()); + expect($interactions)->toHaveCount(1); + expect((array) $interactions[0])->toEqual([ + 'date' => '2000-01-02 03:04:05', + 'user_id' => null, + 'key' => 'lowercase-key', + 'hit' => 0, + ]); +});