# DirectoryTree Metrics DirectoryTree Metrics is a Laravel package that provides a simple and elegant way to record and query metrics in your application. It enables developers to track countable events such as page views, API calls, user signups, purchases, and any other measurable activity with minimal code. The package stores metrics in a database with automatic aggregation, supporting both synchronous and queued recording for high-performance scenarios. The library offers flexible metric recording through multiple interfaces (facade, helper function, or class-based), comprehensive date-based querying with built-in methods for common time periods, model association through Eloquent relationships, categorization for grouping related metrics, and custom attributes for dimensional analysis. It supports distributed applications through Redis-based capturing with batch commits, includes a full testing suite with fake implementations, and provides extensible architecture through interfaces for custom implementations. ## Recording Basic Metrics Record simple countable events in your application. ```php use DirectoryTree\Metrics\Facades\Metrics; use DirectoryTree\Metrics\MetricData; // Using the global helper (recommended) metric('signups')->record(); // Using the facade Metrics::record(new MetricData('signups')); // Using PendingMetric class use DirectoryTree\Metrics\PendingMetric; PendingMetric::make('signups')->record(); // Recording with custom values metric('api:requests')->record(10); metric('jobs:completed')->record(250); // Multiple recordings are automatically summed metric('auth:logins')->record(); // value: 1 metric('auth:logins')->record(); // value: 1 // Database contains one metric for today with value: 2 ``` ## Recording Metrics with Categories Organize metrics into categories for segmented tracking across different contexts. ```php // Track API requests by endpoint metric('api:requests')->category('users')->record(); metric('api:requests')->category('orders')->record(); metric('api:requests')->category('products')->record(); // Track errors by severity level metric('app:errors')->category('critical')->record(); metric('app:errors')->category('warning')->record(); metric('app:errors')->category('info')->record(); // Track purchases by payment method metric('purchases')->category('stripe')->record(100); metric('purchases')->category('paypal')->record(50); metric('purchases')->category('crypto')->record(25); // Categories create separate metric records $stripeRevenue = Metric::thisMonth() ->where('name', 'purchases') ->where('category', 'stripe') ->sum('value'); // Returns: 100 ``` ## Recording Metrics with Custom Dates Backfill historical data or record metrics for specific dates. ```php use Carbon\Carbon; // Backfill signup data from an import metric('signups') ->date(Carbon::parse('2025-01-15')) ->record(50); // Record yesterday's batch job completions metric('jobs:completed') ->date(Carbon::yesterday()) ->record(1250); // Record metrics for last month metric('revenue') ->date(Carbon::parse('2024-12-15')) ->record(15000); // Record with specific timestamp metric('data:imported') ->date(Carbon::create(2025, 10, 1, 0, 0, 0)) ->record(5000); // Combine with categories metric('orders') ->category('wholesale') ->date(Carbon::parse('2025-09-30')) ->record(150); ``` ## Recording Metrics for Eloquent Models Associate metrics with specific models using the HasMetrics trait. ```php use DirectoryTree\Metrics\HasMetrics; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; class User extends Model { use HasMetrics; } class Customer extends Model { use HasMetrics; } class ApiClient extends Model { use HasMetrics; } // Track logins per user metric('auth:logins') ->measurable(Auth::user()) ->record(); // Track orders per customer $customer = Customer::find(123); metric('orders') ->measurable($customer) ->record(); // Track API calls per client $client = ApiClient::find(456); metric('api:requests') ->measurable($client) ->category('users') ->record(5); // Query metrics through model relationship $totalLogins = $user->metrics() ->where('name', 'auth:logins') ->sum('value'); $ordersThisMonth = $customer->metrics() ->where('name', 'orders') ->thisMonth() ->sum('value'); ``` ## Recording Metrics with Custom Attributes Add dimensional data to metrics for advanced segmentation and analysis. ```php use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; // First, add custom columns via migration Schema::table('metrics', function (Blueprint $table) { $table->string('source')->nullable()->index(); $table->string('country')->nullable()->index(); $table->string('device')->nullable(); }); // Track page views with traffic source metric('page_views') ->with(['source' => 'google']) ->record(); // Track conversions with multiple dimensions metric('conversions') ->with([ 'source' => 'facebook', 'country' => 'US', 'device' => 'mobile', ]) ->record(); // Combine with model and category $post = Post::find(1); metric('views') ->measurable($post) ->category('blog') ->with([ 'source' => 'twitter', 'country' => 'CA', 'device' => 'desktop', ]) ->record(); // Different attributes create separate records metric('page_views')->with(['source' => 'google'])->record(); // Record #1 metric('page_views')->with(['source' => 'facebook'])->record(); // Record #2 metric('page_views')->with(['source' => 'google'])->record(); // Increments #1 ``` ## Querying Metrics with Date Filters Use the MetricBuilder query methods to retrieve metrics for specific time periods. ```php use DirectoryTree\Metrics\Metric; use Carbon\Carbon; // Query by predefined periods $todaySignups = Metric::today() ->where('name', 'signups') ->sum('value'); $thisWeekPurchases = Metric::thisWeek() ->where('name', 'purchases') ->sum('value'); $thisMonthRevenue = Metric::thisMonth() ->where('name', 'revenue') ->sum('value'); $thisYearUsers = Metric::thisYear() ->where('name', 'signups') ->sum('value'); // Query historical periods $yesterdayViews = Metric::yesterday() ->where('name', 'page_views') ->sum('value'); $lastWeekOrders = Metric::lastWeek() ->where('name', 'orders') ->sum('value'); $lastMonthRevenue = Metric::lastMonth() ->where('name', 'revenue') ->sum('value'); // Query between specific dates $q1Revenue = Metric::betweenDates( Carbon::parse('2025-01-01'), Carbon::parse('2025-03-31') )->where('name', 'revenue')->sum('value'); // Query specific date $launchDaySignups = Metric::onDate(Carbon::parse('2025-10-15')) ->where('name', 'signups') ->sum('value'); // Group and aggregate $apiUsageByEndpoint = Metric::thisMonth() ->where('name', 'api:requests') ->get() ->groupBy('category') ->map->sum('value'); ``` ## Querying Metrics with Custom Attributes Analyze metrics by custom dimensional attributes. ```php use DirectoryTree\Metrics\Metric; // Query by single attribute $googleViews = Metric::today() ->where('name', 'page_views') ->where('source', 'google') ->sum('value'); // Query by multiple attributes $mobileFacebookConversions = Metric::thisMonth() ->where('name', 'conversions') ->where('source', 'facebook') ->where('device', 'mobile') ->sum('value'); // Group by custom attribute $viewsBySource = Metric::thisWeek() ->where('name', 'page_views') ->get() ->groupBy('source') ->map->sum('value'); // Returns: ['google' => 1500, 'facebook' => 800, 'twitter' => 400] $conversionsByCountry = Metric::thisMonth() ->where('name', 'conversions') ->get() ->groupBy('country') ->map->sum('value'); // Returns: ['US' => 250, 'CA' => 120, 'UK' => 80] // Compare mobile vs desktop traffic $mobileViews = Metric::today() ->where('name', 'page_views') ->where('device', 'mobile') ->sum('value'); $desktopViews = Metric::today() ->where('name', 'page_views') ->where('device', 'desktop') ->sum('value'); ``` ## Capturing and Committing Metrics in Batches Improve performance by capturing metrics in memory and committing them in batches. ```php use DirectoryTree\Metrics\Facades\Metrics; // Manual capture and commit Metrics::capture(); metric('signups')->record(); metric('notifications:sent')->category('welcome')->record(); metric('api:requests')->category('users')->record(5); metric('signups')->record(); // Will be aggregated with first signup Metrics::commit(); // Commits all captured metrics to database Metrics::stopCapturing(); // Enable auto-capture for entire application // In app/Providers/AppServiceProvider.php use DirectoryTree\Metrics\Facades\Metrics; class AppServiceProvider extends ServiceProvider { public function boot(): void { // All metrics recorded during request lifecycle // will be automatically committed on termination Metrics::capture(); } } // Check if currently capturing if (Metrics::isCapturing()) { // Metrics are being captured } // Captured metrics are committed automatically on app termination // or manually via Metrics::commit() ``` ## Using Redis Driver for Distributed Systems Configure Redis-based metric storage for high-traffic distributed applications. ```php // config/metrics.php return [ 'driver' => 'redis', // or env('METRICS_DRIVER', 'redis') 'auto_commit' => false, // Disable auto-commit for manual control 'redis' => [ 'connection' => 'default', 'key' => 'metrics:pending', 'ttl' => 86400, // 1 day in seconds ], 'queue' => [ 'name' => 'metrics', 'connection' => 'redis', ], ]; // .env configuration METRICS_DRIVER=redis METRICS_AUTO_COMMIT=false METRICS_REDIS_CONNECTION=default METRICS_REDIS_KEY=metrics:pending METRICS_REDIS_TTL=86400 // Schedule periodic commits in app/Console/Kernel.php protected function schedule(Schedule $schedule): void { // Commit metrics from Redis to database every hour $schedule->command('metrics:commit')->hourly(); // Or more frequently for high-traffic apps $schedule->command('metrics:commit')->everyFifteenMinutes(); // Or at specific times $schedule->command('metrics:commit')->dailyAt('00:00'); } // Manual commit via Artisan php artisan metrics:commit // Output: Committed 1,247 metric(s). // Record metrics normally - they'll be stored in Redis metric('api:requests')->record(); metric('page_views')->category('blog')->record(); // Metrics are stored in Redis hash until next commit ``` ## Testing Metrics with Fake Implementation Use the MetricFake for testing without database interaction. ```php use DirectoryTree\Metrics\Facades\Metrics; use DirectoryTree\Metrics\Measurable; public function test_user_signup_records_metric() { Metrics::fake(); $this->post('register', [ 'email' => 'user@example.com', 'password' => 'password', ]); Metrics::assertRecorded('signups'); } public function test_api_call_records_metric_with_category() { Metrics::fake(); $this->getJson('api/users'); Metrics::assertRecorded(fn (Measurable $metric) => $metric->name() === 'api:requests' && $metric->category() === 'users' ); } public function test_failed_login_does_not_record_success_metric() { Metrics::fake(); $this->post('login', ['email' => 'wrong@example.com', 'password' => 'wrong']); Metrics::assertNotRecorded('auth:logins'); Metrics::assertRecorded('auth:attempts'); } public function test_purchase_records_metric_for_user() { Metrics::fake(); $user = User::factory()->create(); $this->actingAs($user)->post('purchases', ['product_id' => 1]); Metrics::assertRecorded(fn ($metric) => $metric->name() === 'purchases' && $metric->measurable()?->is($user) ); } public function test_batch_job_records_correct_number_of_metrics() { Metrics::fake(); Artisan::call('orders:process', ['--batch' => 100]); Metrics::assertRecordedTimes('orders:processed', 100); } public function test_no_metrics_recorded_for_guest() { Metrics::fake(); $this->getJson('api/profile'); Metrics::assertNothingRecorded(); } // Access recorded metrics for assertions Metrics::fake(); metric('api:requests')->category('users')->record(); $all = Metrics::recorded(); // All recorded metrics $apiCalls = Metrics::recorded('api:requests'); // By name $userEndpoint = Metrics::recorded(fn ($m) => $m->category() === 'users'); // By closure ``` ## Custom Metric Manager Implementation Extend the package with custom recording logic by implementing the MetricManager interface. ```php namespace App\Metrics; use DirectoryTree\Metrics\Measurable; use DirectoryTree\Metrics\MetricManager; use Illuminate\Support\Facades\Log; class CustomMetricManager implements MetricManager { protected array $captured = []; protected bool $capturing = false; public function record(Measurable $metric): void { if ($this->capturing) { $this->captured[] = $metric; } else { // Custom recording logic - e.g., send to external service Log::info('Metric recorded', [ 'name' => $metric->name(), 'value' => $metric->value(), 'category' => $metric->category(), ]); // Store in database DB::table('metrics')->insert([ 'name' => $metric->name(), 'category' => $metric->category(), 'value' => $metric->value(), // ... other fields ]); } } public function commit(): int { $count = count($this->captured); foreach ($this->captured as $metric) { $this->record($metric); } $this->captured = []; return $count; } public function capture(): void { $this->capturing = true; } public function isCapturing(): bool { return $this->capturing; } public function stopCapturing(): void { $this->capturing = false; } } // Register in AppServiceProvider use App\Metrics\CustomMetricManager; use DirectoryTree\Metrics\MetricManager; public function register(): void { $this->app->singleton(MetricManager::class, CustomMetricManager::class); } ``` ## Custom Metric Repository Implementation Create custom storage backends for captured metrics. ```php namespace App\Metrics; use DirectoryTree\Metrics\Measurable; use DirectoryTree\Metrics\MetricRepository; use Illuminate\Support\Facades\Cache; class CacheMetricRepository implements MetricRepository { protected string $cacheKey = 'metrics:captured'; public function add(Measurable $metric): void { $metrics = Cache::get($this->cacheKey, []); $metrics[] = $metric; Cache::put($this->cacheKey, $metrics, now()->addHour()); } public function all(): array { return Cache::get($this->cacheKey, []); } public function flush(): void { Cache::forget($this->cacheKey); } } // Register in AppServiceProvider use App\Metrics\CacheMetricRepository; use DirectoryTree\Metrics\MetricRepository; public function register(): void { $this->app->singleton(MetricRepository::class, CacheMetricRepository::class); } // Now all captured metrics use cache storage Metrics::capture(); metric('test')->record(); // Stored in cache Metrics::commit(); // Retrieved from cache and committed ``` ## DirectoryTree Metrics is a production-ready metrics tracking solution for Laravel applications that handles both simple and complex use cases. The package excels in high-traffic environments through its batching and queuing capabilities, reducing database load by aggregating identical metrics and supporting distributed architectures via Redis storage. It integrates seamlessly with Laravel's ecosystem using familiar patterns like facades, helper functions, Eloquent relationships, and scheduled commands. ## Common use cases include tracking user engagement metrics (logins, page views, feature usage), monitoring API usage per client or endpoint, recording e-commerce events (purchases, conversions, revenue) segmented by payment method or source, measuring job queue performance and batch processing results, and analyzing traffic patterns by source, country, device, or other custom dimensions. Integration patterns include middleware for automatic request tracking, event listeners for domain events, service layer calls for business metrics, background job reporting, and model observers for entity lifecycle metrics. The package's flexible architecture allows customization while maintaining simplicity for standard use cases.