Introduction
Ledgerly Export Drivers are responsible for delivering exported audit entries to an external destination.
Drivers define how transformed ledger entries are written to:
- files
- cloud storage
- data warehouses
- message queues
- streaming platforms
- ingestion APIs
Export Drivers are executed after:
- the ledger entry has been converted to an export format
- metadata has been assembled
- transformers have been applied
They are the final stage in the export pipeline.
When Should You Write a Custom Driver?
Custom Export Drivers are typically used when exporting audit logs to:
- Amazon S3
- Google Cloud Storage
- Kafka
- Snowflake
- BigQuery
- Elasticsearch
- internal ingestion APIs
Drivers are not responsible for:
- transforming entries
- filtering records
- modifying metadata
- validation
These concerns should be handled by:
- Entry Transformers
- Metadata Resolvers
Creating a Driver
To create an Export Driver, implement the:
Ledgerly\Core\Contracts\ExportDriver
interface.
Example
use Ledgerly\Core\Contracts\ExportDriver;
use Ledgerly\Core\Export\ExportContext;
class JsonFileExportDriver
implements ExportDriver
{
protected $handle;
public function __construct(
protected string $path
) {}
public function start(ExportContext $context): void
{
$this->handle = fopen($this->path, 'w');
}
public function write(array $payload): void
{
fwrite(
$this->handle,
json_encode($payload).PHP_EOL
);
}
public function finish(): void
{
fclose($this->handle);
}
public function getConstructorArgs(): array
{
return [
'path' => $this->path,
];
}
}
Driver Lifecycle
Each Export Driver participates in the export lifecycle:
start() → write() → finish()
start()
Invoked before export begins.
Typically used to:
- open connections
- initialize file handles
- prepare upload sessions
write()
Called once per transformed ledger entry.
Drivers should:
- treat payloads as immutable
- avoid buffering large datasets in memory
- write entries incrementally
finish()
Invoked after export completes.
Used to:
- flush buffers
- finalize uploads
- close connections
- commit transactions
Queue-Safe Drivers
Queued exports require drivers to be reconstructible.
Drivers must implement:
public function getConstructorArgs(): array;
Example:
class S3ExportDriver
implements ExportDriver
{
public function __construct(
protected string $bucket,
protected string $path
) {}
public function getConstructorArgs(): array
{
return [
'bucket' => $this->bucket,
'path' => $this->path,
];
}
}
Ledgerly will serialize constructor arguments and reconstruct the driver when running queued exports.
Registering a Driver
Export Drivers may be registered globally using an extension.
Implement:
Ledgerly\Core\Extension\RegistersLedgerlyExtensions
Example Extension
use Ledgerly\Core\Extension\ExtensionRegistry;
use Ledgerly\Core\Extension\RegistersLedgerlyExtensions;
class S3ExportExtension
implements RegistersLedgerlyExtensions
{
public function registerLedgerlyExtensions(
ExtensionRegistry $registry
): void {
$registry->exportDriver(
's3',
S3ExportDriver::class
);
}
}
Alias may now reference registered drivers.
Using a Registered Driver
LedgerExporter::make()
->usingDriver('s3', [
'bucket' => 'audit-logs',
'path' => 'ledger.jsonl',
])
->export();
Ledgerly will automatically:
- resolve the driver
- construct it using provided arguments
- execute the export pipeline
Handling Large Datasets
Drivers should:
- write entries incrementally
- avoid loading entire datasets into memory
- support streaming or chunked uploads
Ledgerly may export entries in chunks when configured to do so.
Best Practices
When writing Export Drivers:
- keep drivers stateless where possible
- ensure constructor args are serializable
- avoid long-lived connections in write()
- do not mutate payloads
- treat delivery as at-least-once
- handle partial failures gracefully
Drivers should focus solely on delivering already-prepared payloads.
Next Steps
See:
- Writing Transformers
- Exporting Guide
- Webhook Integrations
to further customize Ledgerly export behavior.