Skip to main content

Context

Context allows you to attach values that automatically apply to multiple ledger entries during a period of execution.

Context is commonly used for:

  • Tenant identifiers
  • Feature flags
  • Service identifiers
  • Batch or import identifiers
  • Business workflow identifiers

Unlike explicit metadata, context is applied automatically to every entry until it is changed or cleared.


Why Context Exists

In real applications, many events share the same information.

Example:

  • A request belongs to a tenant
  • A batch process processes multiple records
  • A job handles a single import

Without context, you would need to repeat metadata on every log call.

Context solves this problem.


Setting Global Context

You can define context values:

ledgerly()->context([
'tenant_id' => 42,
]);

All subsequent entries will include this metadata.

Example:

ledgerly()->context(['tenant_id' => 42]);

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

Both entries will include:

tenant_id = 42

Scoped Context

Scoped context applies only inside a block:

ledgerly()->withContext(['tenant_id' => 42], function () {

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

});

After the block finishes, context is automatically restored.


Nested Context

Context scopes can be nested:

ledgerly()->withContext(['tenant_id' => 1], function () {

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

ledgerly()->withContext(['feature' => 'beta'], function () {
ledgerly()->action('billing.invoice.updated')->log();
});

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

});

Resulting metadata:

EntryMetadata
invoice.createdtenant_id = 1
invoice.updatedtenant_id = 1, feature = beta
invoice.senttenant_id = 1

Ledgerly restores context automatically after each scope.


Context vs Metadata

Use context when:

  • A value applies to many entries
  • The value represents the runtime state
  • The value should be inherited automatically

Use explicit metadata when:

  • A value applies to one entry only
  • The value describes a specific event

Example:

Good use of context:

tenant_id
batch_id
import_id
workflow_id

Good use of metadata:

channel = email
retry_attempt = 2
payment_gateway = stripe

Context vs Transactions

Context and transactions serve different purposes.

FeatureContextTransaction
PurposeAttach shared metadataGroup related entries
ScopeRuntime stateWorkflow grouping
Adds metadataYesYes
Adds correlation idNoYes

You can use both together.


Context and Metadata Precedence

Context is merged into metadata with the following priority:

1. Metadata resolvers
2. Transaction metadata
3. Context metadata
4. Explicit metadata

Explicit metadata always overrides context values.


Automatic Restoration

Ledgerly ensures context is restored even if an exception occurs:

ledgerly()->withContext(['tenant_id' => 42], function () {
throw new RuntimeException();
});

After the exception, context is no longer applied.

This prevents accidental context leakage.


Best Practices

Recommended uses for context:

  • Tenant identifiers
  • Batch processing identifiers
  • Service identifiers
  • Feature flags
  • Workflow identifiers

Avoid using context for:

  • Large datasets
  • Sensitive secrets
  • Values unique to a single event

How Context Works Internally

Internally, Ledgerly maintains a stack of context layers.

Each scoped context:

  1. Pushes a new layer
  2. Applies it to entries
  3. Pops the layer after execution

This ensures:

  • Isolation
  • Nesting support
  • Exception safety

Next Step

Continue to:

➡️ Transactions