Skip to main content

Real-World Examples

This page demonstrates how Ledgerly can be used in real applications.
The examples focus on common patterns such as SaaS systems, background jobs, and billing workflows.


Multi-Tenant SaaS Application

In a multi-tenant system, nearly every action belongs to a tenant.

Instead of repeating tenant metadata on every log call, use context.

Example middleware:

ledgerly()->context([
'tenant_id' => $tenant->id,
]);

Now every entry automatically includes tenant information:

ledgerly()->action('invoice.created')->log();
ledgerly()->action('invoice.sent')->log();

Resulting metadata:

tenant_id = 42

This makes filtering and reporting easy.


Billing Workflow

A billing operation often involves multiple steps:

  • Invoice created
  • Payment authorized
  • Receipt generated
  • Email sent

These events should be correlated.

Example:

ledgerly()->transaction(function () use ($invoice, $payment) {

ledgerly()->action('invoice.created')
->target($invoice)
->log();

ledgerly()->action('payment.authorized')
->target($payment)
->log();

ledgerly()->action('invoice.receipt_sent')
->target($invoice)
->withMetadata(['channel' => 'email'])
->log();

});

All entries share the same correlation identifier, allowing the workflow to be reconstructed later.


Recording Model Changes

When updating a model, record the diff:

$before = $invoice->amount;

$invoice->update([
'amount' => 1500,
]);

ledgerly()
->actor(auth()->user())
->target($invoice)
->action('invoice.updated')
->withDiff([
'amount' => [$before, $invoice->amount],
])
->log();

Diffs make timelines and audits significantly more useful.


Background Job Processing

Queue workers often process many records.

Use transactions to group entries per job:

ledgerly()->transaction(function () use ($batch) {

foreach ($batch->records as $record) {
ledgerly()
->action('record.processed')
->target($record)
->log();
}

});

This allows identifying all entries related to a single job execution.


Import or Batch Processing

Batch operations are a perfect use case for context.

ledgerly()->withContext([
'import_id' => $import->id,
], function () use ($rows) {

foreach ($rows as $row) {
ledgerly()
->action('customer.imported')
->log();
}

});

Each entry automatically includes:

import_id

User Activity Timeline

Ledgerly works well for building activity timelines.

Example events:

user.registered
user.email_verified
subscription.started
subscription.cancelled

Each event records:

  • actor
  • timestamp
  • metadata
  • optional diff

These entries can be displayed in a UI timeline using ledgerly/ui.


Security and Compliance Logging

Security-sensitive systems often need to record important actions:

ledgerly()
->actor(auth()->user())
->action('user.password_changed')
->severity('warning')
->log();

Recommended events to log:

  • password changes
  • role updates
  • permission changes
  • account lockouts

Avoid storing sensitive values in metadata.


External API Integration

If an API gateway provides a correlation ID:

ledgerly()->context([
'correlation_id' => request()->header('X-Correlation-ID'),
]);

This allows tracing requests across multiple services.


Feature Flags and Experiments

When running A/B tests:

ledgerly()->withContext([
'feature' => 'checkout_v2',
], function () {

ledgerly()->action('checkout.started')->log();
ledgerly()->action('checkout.completed')->log();

});

This allows filtering events by feature rollout.


Scheduled Tasks

Scheduled jobs often run periodically:

ledgerly()->action('maintenance.cleanup_started')->log();

Ledgerly automatically records metadata such as:

  • source = scheduler
  • environment
  • timestamp

This makes scheduled activity traceable.


Error Tracking Example

Ledgerly can complement error logging:

try {
$service->process();
} catch (Throwable $e) {

ledgerly()
->action('payment.processing_failed')
->severity('error')
->withMetadata([
'error' => $e->getMessage(),
])
->log();

throw $e;
}

Ledgerly records the business impact, while application logs record technical details.


Best Practices for Real-World Use

In production systems:

  • Use context for tenant or workflow identifiers
  • Use transactions for multistep operations
  • Keep metadata small
  • Log meaningful business events
  • Keep action naming consistent