Log Management for Laravel SaaS Applications
Learn best practices for logging in multi-tenant Laravel SaaS applications. Handle tenant isolation, debug customer issues, and scale your logging infrastructure.
Building a SaaS application with Laravel brings unique logging challenges. You need to track issues across multiple tenants, debug customer-specific problems, and maintain log isolation for security and compliance. Here's how to build a robust logging strategy for your Laravel SaaS.
The Multi-Tenant Logging Challenge
Unlike single-tenant applications, SaaS logging must answer questions like:
- Which tenant experienced this error?
- How do I view all logs for a specific customer?
- Are errors affecting one tenant or all tenants?
- How do I share relevant logs with a customer without exposing others?
Tenant-Aware Logging Context
The foundation of SaaS logging is adding tenant context to every log entry:
<?php
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Log;
public function boot(): void
{
// Add tenant context to all logs
Log::shareContext([
'tenant_id' => fn () => tenant()?->id,
'tenant_name' => fn () => tenant()?->name,
]);
}
For packages like Spatie's multitenancy:
<?php
// Using Spatie Laravel Multitenancy
use Spatie\Multitenancy\Events\MakingTenantCurrentEvent;
Event::listen(MakingTenantCurrentEvent::class, function ($event) {
Log::shareContext([
'tenant_id' => $event->tenant->id,
'tenant_domain' => $event->tenant->domain,
]);
});
Structured Logging for SaaS
Structure your logs for easy filtering and analysis:
<?php
// Good: Structured with context
Log::info('Subscription upgraded', [
'tenant_id' => $tenant->id,
'plan_from' => $oldPlan->name,
'plan_to' => $newPlan->name,
'mrr_change' => $newPlan->price - $oldPlan->price,
]);
// Bad: Unstructured message
Log::info("Tenant {$tenant->id} upgraded from {$oldPlan} to {$newPlan}");
Customer-Specific Log Channels
For enterprise customers, you might need dedicated log streams:
<?php
// config/logging.php
'channels' => [
'tenant' => [
'driver' => 'custom',
'via' => App\Logging\TenantLoggerFactory::class,
],
],
// app/Logging/TenantLoggerFactory.php
class TenantLoggerFactory
{
public function __invoke(array $config): LoggerInterface
{
$tenant = tenant();
return new Logger('tenant', [
new StreamHandler(
storage_path("logs/tenants/{$tenant->id}.log"),
Level::Debug
),
]);
}
}
Handling High-Volume SaaS Logging
Queue Log Processing
<?php
// Don't block requests with logging
Log::channel('queue')->info('User action', $context);
// config/logging.php
'queue' => [
'driver' => 'custom',
'via' => App\Logging\QueuedLoggerFactory::class,
],
Sampling for High-Traffic Tenants
<?php
// Log sampling for high-volume events
if (tenant()->plan === 'enterprise' || random_int(1, 100) <= 10) {
Log::debug('Request processed', $context);
}
Error Tracking with Tenant Context
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (Throwable $e) {
// Add tenant context to all exception reports
if ($tenant = tenant()) {
Log::error($e->getMessage(), [
'tenant_id' => $tenant->id,
'tenant_plan' => $tenant->plan,
'exception' => $e,
]);
}
});
})
Building a Customer Debug View
Let customers see their own logs (carefully filtered):
<?php
// app/Http/Controllers/Tenant/LogsController.php
class LogsController extends Controller
{
public function index(Request $request)
{
$logs = LogEntry::query()
->where('tenant_id', tenant()->id)
->where('level', '>=', $request->get('level', 'warning'))
->latest()
->paginate(50);
return view('tenant.logs.index', compact('logs'));
}
}
Security Considerations
Log Isolation
- Never log sensitive data (passwords, API keys, PII)
- Ensure log queries are always tenant-scoped
- Use separate log storage for enterprise compliance requirements
Audit Logging
<?php
// Critical actions need audit trails
Log::channel('audit')->info('User permission changed', [
'tenant_id' => tenant()->id,
'actor_id' => auth()->id(),
'target_user_id' => $user->id,
'permission_added' => $permission,
'ip' => request()->ip(),
]);
Centralized Log Management
Forward all tenant logs to a central service for analysis:
<?php
// config/logging.php
'stack' => [
'driver' => 'stack',
'channels' => ['single', '401clicks'],
],
'401clicks' => [
'driver' => 'custom',
'via' => App\Logging\FourOhOneClicksLogger::class,
'api_key' => env('401CLICKS_API_KEY'),
],
Useful Log Queries for SaaS
With proper context, you can answer important questions:
# Errors by tenant (last 24h)
tenant_id:* level:error | stats count() by tenant_id
# Slow requests for specific tenant
tenant_id:"abc123" response_time:>1000
# Feature usage across tenants
message:"feature_used" feature:"export" | stats count() by tenant_id
Metrics from Logs
Extract business metrics from your logs:
<?php
// Log events that matter for business metrics
Log::info('subscription_event', [
'tenant_id' => $tenant->id,
'event' => 'trial_converted',
'plan' => $plan->name,
'value' => $plan->price,
]);
// Then aggregate in your log management tool
Integration with 401 Clicks
401 Clicks makes SaaS logging simple:
- Filter logs by tenant ID instantly
- Set up alerts for tenant-specific error thresholds
- Share read-only log views with enterprise customers
- Track error patterns across your entire customer base
Conclusion
Effective SaaS logging is about context. Add tenant information to every log entry, structure your data for querying, and use a centralized log management solution that can handle multi-tenant filtering. Your future self (and your support team) will thank you when debugging that urgent customer issue at 2 AM.
Admin
Published on January 15, 2026