Accigo.Integration.Logging 1.4.0

dotnet add package Accigo.Integration.Logging --version 1.4.0
                    
NuGet\Install-Package Accigo.Integration.Logging -Version 1.4.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Accigo.Integration.Logging" Version="1.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Accigo.Integration.Logging" Version="1.4.0" />
                    
Directory.Packages.props
<PackageReference Include="Accigo.Integration.Logging" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Accigo.Integration.Logging --version 1.4.0
                    
#r "nuget: Accigo.Integration.Logging, 1.4.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Accigo.Integration.Logging@1.4.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Accigo.Integration.Logging&version=1.4.0
                    
Install as a Cake Addin
#tool nuget:?package=Accigo.Integration.Logging&version=1.4.0
                    
Install as a Cake Tool

Accigo.Integration.Logging

A lightweight .NET logging convention for Azure Functions (isolated worker) integrations. Targets net8.0, net9.0, and net10.0. Emits structured ILogger events and ActivitySource spans so telemetry backends such as Azure Application Insights can correlate runs, steps, and errors across an integration flow.

No companion SDK. No HTTP callbacks. Integrations write structured log entries and spans; your normal telemetry pipeline handles ingestion, querying, and visualization.

Install

Install from NuGet like any other package:

dotnet add package Accigo.Integration.Logging

Requirements:

  • .NET 8, .NET 9, or .NET 10
  • Microsoft.Extensions.Logging registered in DI
  • Optional: Azure Application Insights or another OpenTelemetry-compatible backend if you want centralized telemetry

Register in your Functions host

using Accigo.Integration.Logging.Extensions;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        // Registers the ActivitySource for distributed tracing spans.
        services.AddIntegrationLogging();
    })
    .Build();

host.Run();

Usage

Inject ILogger<T> (standard .NET logging) and use the extension methods:

using Accigo.Integration.Logging.Extensions;

public class OrderSync(ILogger<OrderSync> logger)
{
    [Function("ProcessOrder")]
    public async Task Run([ServiceBusTrigger("orders")] ServiceBusReceivedMessage msg)
    {
        // BeginIntegrationRun emits IntegrationRunStarted for a new run (no runId
        // supplied). If runId is an existing run's id, the call is treated as a
        // continuation and emits a "resume" step instead — see "Emitted events" below.
        // Dispose emits IntegrationRunCompleted (suppressed if LogIntegrationRunFailed was called).
        // Seed runId from CorrelationId (the producing run id, shared by every
        // message of a run), NOT MessageId — MessageId is unique per message.
        // See "Correlating runs and Service Bus messages" below.
        using var run = logger.BeginIntegrationRun(
            integrationId: "order-sync",
            runId: msg.CorrelationId ?? msg.MessageId,
            triggerSource: "servicebus:orders",
            isNewRun: false,
            companyId: "company-123");

        try
        {
            // Log step information — timing comes from Application Insights timestamps
            logger.LogIntegrationStepInformation(run, "validate", "Validating payload");
            ValidatePayload(msg.Body);
            logger.LogIntegrationStepInformation(run, "validate", "Validation succeeded");

            logger.LogIntegrationStepInformation(run, "transform", "Transforming data");
            var transformed = TransformData(msg.Body);
            logger.LogIntegrationStepInformation(run, "transform", $"Transformed {transformed.Count} records");

            logger.LogIntegrationStepInformation(run, "load", "Writing to ERP");
            await WriteToErp(transformed);
            logger.LogIntegrationStepInformation(run, "load", "Successfully wrote all records");
        }
        catch (Exception ex)
        {
            // Marks the run failed so Dispose does not emit RunCompleted.
            logger.LogIntegrationRunFailed(run, ex, errorCode: "ERR_UNEXPECTED");
            throw;
        }
    }
}

Seed runId from the Service Bus CorrelationId (the producing run id) so every message of a run correlates to one run and the Hub can find and resend an individual dead-lettered message. See Correlating runs and Service Bus messages.

Pass companyId when the run belongs to a known company. Use the same stable identifier on every continuation of that run; it is descriptive run metadata, not a replacement for integration access or customer tenancy. It may be omitted for existing callers and runs without a company identifier. Surrounding Unicode whitespace is trimmed before emission, values that become blank are treated as absent, and the canonical value must be at most 128 characters. To preserve binary compatibility with prebuilt callers, company-tagged calls also pass runId, triggerSource, and isNewRun explicitly; use null for unused strings.

Starting with package version 1.4.0, if the company is only discovered after the run starts, pass it with any subsequent step log:

using var run = logger.BeginIntegrationRun("order-sync");
// ... companyId becomes available during processing ...
logger.LogIntegrationStepInformation(run, "load", "Writing to ERP", companyId);

The step key and message describe the work; neither is interpreted to identify the company. Only the supplied companyId binds the run. The step event immediately carries the company ID, so the Hub associates it with the existing run when telemetry arrives, without restarting it or adding an extra event. Later step, failure, and completion events and the run's span carry the ID too; earlier telemetry cannot be changed retroactively. Supplying the same normalized ID on later steps is allowed. Blank or overlong IDs, a different ID on the same run, or assigning one after disposal are rejected.

Correlating runs and Service Bus messages

A single integration run often fetches a batch and sends N messages — one run, many messages. When one of those messages fails downstream and dead-letters, an operator must find and resend that one message without disturbing its siblings. That only works if runs and messages are identified separately:

Broker field Meaning Cardinality
CorrelationId The run id (run.RunId) Shared by every message of the run
MessageId The message id (the resend handle) Unique per message

Producer — set both when you send each message:

using var run = logger.BeginIntegrationRun("order-sync", runId: Guid.NewGuid().ToString());

foreach (var order in await FetchOrdersAsync())
{
    var message = new ServiceBusMessage(Serialize(order))
    {
        // Unique per message — the handle used to resend exactly this one.
        MessageId = Guid.NewGuid().ToString(),
        // Shared across the run — groups the batch under run.RunId.
        CorrelationId = run.RunId,
    };
    await sender.SendMessageAsync(message);
}

Consumer — seed the run from CorrelationId, not MessageId:

runId: msg.CorrelationId ?? msg.MessageId  // fall back only if the producer set no CorrelationId

Why not runId = MessageId? With a unique MessageId per message, seeding the run from it makes every message look like its own run, so the N siblings of a batch no longer group together. Conversely, reusing one id for both MessageId and CorrelationId collapses the batch into a single dead-letter row (the Hub dedups on (integrationId, messageId)), so you can no longer resend an individual message. Keep them distinct.

Fan-out to independent child runs

The batch pattern above assumes each dispatched message is a sibling of the same run — fine when the messages are fungible units of one logical operation. It does not apply when a consumer receives a batch message and then itself dispatches further, independent units of work that each deserve their own pass/fail outcome (e.g. a trigger fans out N files, and each file's downstream import is its own customer-visible success or failure). Reusing the parent run's id all the way down that chain merges every child's status onto one run — a later child's success can silently overwrite an earlier child's failure.

For that shape, mint a new id per child and pass isNewRun: true so the child still gets its own IntegrationRunStarted (not a resume step), while the id is still known up front for correlating the outgoing message:

// Parent — dispatches N independent children, each with its own new run id.
foreach (var file in files)
{
    var childRunId = Guid.NewGuid().ToString();
    var message = new ServiceBusMessage(Serialize(file))
    {
        MessageId = Guid.NewGuid().ToString(),
        CorrelationId = childRunId, // the CHILD's run id, not the parent's
    };
    await sender.SendMessageAsync(message);
}

// Child — starts its OWN run using the id the parent chose.
using var run = logger.BeginIntegrationRun(
    "order-sync", runId: msg.CorrelationId, isNewRun: true);

Rule of thumb: reuse the parent's run.RunId (plain continuation) only when the dispatched messages are siblings of one run. Mint a fresh id + isNewRun: true whenever each dispatched message is its own independent unit of work.

Step information

Log free-form messages correlated to a step so your observability tooling can show what a step did. Timing comes from Application Insights timestamps — no need for explicit start/complete events:

logger.LogIntegrationStepInformation(run, "fetch", "Fetching orders from SAP");
// ... do the work ...
logger.LogIntegrationStepInformation(run, "fetch", "Fetched 142 orders from batch 7892");
logger.LogIntegrationStepInformation(run, "fetch", "Skipped 3 orders with missing customer ID");

Each call to LogIntegrationStepInformation emits one IntegrationStepInformation log event. Callers can call it multiple times per step.

Rules:

  • message must not be null or whitespace — ArgumentException is thrown.
  • Messages longer than 1024 characters are silently truncated.

Do not put PII or credentials in step messages. This data flows to your telemetry backend and any downstream observability store that ingests these events.

Emitted events

All events are emitted via ILogger.LogInformation / ILogger.LogError as structured log entries with named properties. When using Azure Application Insights, these events land in the traces table and the dimensions below appear in customDimensions.

Event When Extra dimensions
IntegrationRunStarted BeginIntegrationRun() with no runId (new run) integrationId, runId, triggerSource (optional), companyId (optional)
IntegrationRunCompanyIdentified run.SetCompanyId() once a company becomes known integrationId, runId, companyId
IntegrationStepInformation LogIntegrationStepInformation(), or BeginIntegrationRun() with an existing runId (stepKey resume) integrationId, runId, stepKey, stepMessage, companyId (optional)
IntegrationRunCompleted Disposing a run context without failure integrationId, runId, durationMs, companyId (optional)
IntegrationRunFailed LogIntegrationRunFailed() integrationId, runId, errorCode, errorMessage, severity, durationMs, exceptionType?, stackTrace?, companyId (optional)

Passing an existing run's runId to BeginIntegrationRun() (e.g. a trigger→queue→import split sharing one runId across function invocations) treats the call as a continuation: it emits the resume step instead of a second IntegrationRunStarted, so the run timeline doesn't show a spurious restart. Pass isNewRun: true to override this and force a fresh IntegrationRunStarted even with a supplied runId — see Fan-out to independent child runs.

Deprecated events (backward compatibility only):

  • IntegrationStepStarted — use IntegrationStepInformation instead; timing comes from Application Insights timestamps. Both include companyId when supplied.
  • IntegrationStepCompleted — use IntegrationStepInformation instead; timing comes from Application Insights timestamps. Both include companyId when supplied.

In addition to log entries, each run starts an ActivitySource span (Integration.Run.*) for OpenTelemetry-compatible collectors.

Consuming the emitted events

Any telemetry pipeline that preserves structured log properties can build dashboards, alerts, or ingestion jobs on top of these events. If you use Azure Application Insights, query traces by the event names and dimensions documented above.

Versioning

The package follows SemVer. Breaking changes to event names or required dimensions will bump the major version. Deprecated step lifecycle APIs remain for backward compatibility and will be removed in v2.0.0.

NuGet package versions are immutable. Before publishing, bump <Version> in the project and publish the matching tag (tools/logging/vX.Y.Z), or dispatch the publish workflow from the commit containing that version. The workflow stops if the package version already exists, so a symbols package from a different build cannot be uploaded against it.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.4.0 43 9/30/2026
1.3.1 87 9/24/2026
1.3.0 154 7/16/2026
1.2.2 123 7/6/2026
1.2.1 112 7/3/2026
1.2.0 115 6/10/2026
1.1.0 143 5/22/2026