ToolUp.AuditSinks.AzureBlobArchive
0.22.0
Prefix Reserved
dotnet add package ToolUp.AuditSinks.AzureBlobArchive --version 0.22.0
NuGet\Install-Package ToolUp.AuditSinks.AzureBlobArchive -Version 0.22.0
<PackageReference Include="ToolUp.AuditSinks.AzureBlobArchive" Version="0.22.0" />
<PackageVersion Include="ToolUp.AuditSinks.AzureBlobArchive" Version="0.22.0" />
<PackageReference Include="ToolUp.AuditSinks.AzureBlobArchive" />
paket add ToolUp.AuditSinks.AzureBlobArchive --version 0.22.0
#r "nuget: ToolUp.AuditSinks.AzureBlobArchive, 0.22.0"
#:package ToolUp.AuditSinks.AzureBlobArchive@0.22.0
#addin nuget:?package=ToolUp.AuditSinks.AzureBlobArchive&version=0.22.0
#tool nuget:?package=ToolUp.AuditSinks.AzureBlobArchive&version=0.22.0
Azure Blob Archive audit sink
Phase 16c IAuditSink companion. Mirrors every audit event the SDK emits to a deploying-org-controlled Azure Blob container (or any IBlobStorage-compatible destination) as gzipped JSONL files. Cloud-idiomatic counterpart to ToolUp.AuditSinks.S3Archive for deployments targeting Azure.
Why "Azure Blob Archive" specifically
Azure Blob Storage with a Blob Immutability Policy (time-based retention or legal hold) provides compliance-grade WORM (Write-Once-Read-Many) semantics — once written, blobs cannot be modified or deleted until their retention period expires. The sink itself does not configure the immutability policy; that's a container-level deployment concern. The sink writes; the container enforces.
This companion is functionally identical to ToolUp.AuditSinks.S3Archive — both write through the abstract IBlobStorage interface. The cloud-specific name + defaults + this README's immutability documentation give Azure-targeting deployments an idiomatic reference; the implementation is the same gzipped-JSONL-per-batch shape.
How to enable
Add a
<ProjectReference>(or a<PackageReference>if you're pulling the published nupkg) so MSBuild builds it alongside the consuming project:<PackageReference Include="ToolUp.AuditSinks.AzureBlobArchive" />Construct the sink in the composition root and register it via
ServerApp.withAuditSink:open ToolUp.Platform.AuditSinks.AzureBlobArchive let auditSink = AzureBlobArchive.create "azure-prod-audit" // stable deployment-unique name { Container = "acme-audit-prod"; PathPrefix = Some "v1" } blobStorage // the deployment's IBlobStorage instance (typically AzureBlobStorage) ServerApp.empty |> ServerApp.withConfig config |> ServerApp.withStorage blobStorage |> ServerApp.withAuditSink auditSink |> ServerApp.run(Production only.) Configure the destination container with a Blob Immutability Policy:
# Time-based retention — 7 years, locked (cannot be reduced). az storage container immutability-policy create \ --account-name acmeauditprod \ --container-name acme-audit-prod \ --period 2555 \ --allow-protected-append-writes true az storage container immutability-policy lock \ --account-name acmeauditprod \ --container-name acme-audit-prodOr in Bicep / ARM, set the container's
immutableStorageWithVersioning.enabled = trueand the appropriatedefaultEncryptionScope/immutabilityPolicy. See Microsoft Learn for the full set of options (legalHolds, version-level policies, etc.).
Archive layout
One blob per delivered batch:
{PathPrefix}/{yyyy-MM-dd}/{HH-mm-ss-fffffff}-{sinkName}-{batchUuid}.jsonl.gz
Example: v1/2026-05-05/14-23-45-1234567-azure-prod-audit-9b2e7c5a4d3f6e8ba1c2d3e4f5a6b7c8.jsonl.gz
- Date bucket — leading
yyyy-MM-ddsegment. Auditors run date-range queries against the prefix; Azure Blob's list-blobs by prefix is O(1) per matching shard. - High-resolution timestamp —
HH-mm-ss-fffffff(ticks of a second). Multiple batches per second are routine under load. - Sink name — middle segment lets one container host archives from multiple sinks without collision.
- Batch UUID — guaranteed unique even if the timestamp resolution is exhausted.
File contents
Gzipped JSONL: one AuditEvent per line, JSON-serialised via FableJsonConverter (the SDK's canonical converter). LF line separator. Each line is independently parseable — auditors can az storage blob download ... | gunzip -c | jq -c '.' or feed directly to Azure Data Explorer / Synapse's GZIP-aware JSON parsers.
{"Case":"UserLoggedIn","Fields":[{"UserId":"u123","AuthProvider":"Header"}]}
{"Case":"FileUploaded","Fields":[{"UserId":"u123","TeamId":"t-acme","FileName":"sales.csv","FileSize":12345}]}
Idempotency on retry
The dispatcher retries the entire batch on Result.Error. Each retry generates a NEW batchUuid, so a retried batch lands as a new blob — duplicates are idempotent at the audit-trail level (the same events appear twice with different timestamps), not at the blob level. Auditors querying by event.Id will see exact-match deduplication on the wire-format Id field; querying by blob count will overcount.
Single-instance limitation
Same as S3Archive: the replicator's bounded channel and per-scope semaphores are in-process. Multiple silos running the same replicator + sink configuration will each consume the post-write hook and double-deliver every batch. Deployments running the SDK in multi-instance configurations should serialise the audit replicator to a single elected leader at the orchestrator level (e.g., Azure App Service with Always On = true on a single instance, or a Kubernetes Deployment with replicas: 1 for the audit-emitting tier) until the IDistributedLock-backed distributed companion ships.
Local development
Wire LocalFileStorage instead of AzureBlobStorage for dev:
let blobStorage = LocalFileStorage "/var/lib/myapp/storage" :> IBlobStorage
let auditSink =
AzureBlobArchive.create
"local-dev"
{ Container = "audit-archive"; PathPrefix = None }
blobStorage
Archived batches land at /var/lib/myapp/storage/audit-archive/2026-05-05/...jsonl.gz — inspect them with gunzip -c | jq like a real container.
See also
ToolUp.AuditSinks.S3Archive— sibling AWS-flavoured audit-sink companion (same implementation, S3 Object Lock WORM mechanism).ToolUp.AuditSinks.GcsArchive— sibling GCP-flavoured audit-sink companion (same implementation, GCS retention policy WORM mechanism).ToolUp.Cloud.Azure— Azure umbrella package that transitively pulls this sink alongside the rest of the Azure companion set.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ToolUp.Platform.Core (>= 0.22.0)
- ToolUp.Platform.Server (>= 0.22.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on ToolUp.AuditSinks.AzureBlobArchive:
| Package | Downloads |
|---|---|
|
ToolUp.Cloud.Azure
Azure cloud umbrella for ToolUp.Platform — transitively pulls ToolUp.Storage.AzureBlob, ToolUp.Secrets.AzureKeyVault, ToolUp.AuditSinks.AzureBlobArchive, ToolUp.Metrics.OpenTelemetry, plus the Azure Monitor OTel exporter. One PackageReference replaces five per-cloud entries; consumers drop the umbrella with no behavioural change to reference the individual companions instead. |
GitHub repositories
This package is not used by any popular GitHub repositories.