DCTekSolutions.ExternalCallGuard.NLogTelemetrySink
1.0.0
dotnet add package DCTekSolutions.ExternalCallGuard.NLogTelemetrySink --version 1.0.0
NuGet\Install-Package DCTekSolutions.ExternalCallGuard.NLogTelemetrySink -Version 1.0.0
<PackageReference Include="DCTekSolutions.ExternalCallGuard.NLogTelemetrySink" Version="1.0.0" />
<PackageVersion Include="DCTekSolutions.ExternalCallGuard.NLogTelemetrySink" Version="1.0.0" />
<PackageReference Include="DCTekSolutions.ExternalCallGuard.NLogTelemetrySink" />
paket add DCTekSolutions.ExternalCallGuard.NLogTelemetrySink --version 1.0.0
#r "nuget: DCTekSolutions.ExternalCallGuard.NLogTelemetrySink, 1.0.0"
#:package DCTekSolutions.ExternalCallGuard.NLogTelemetrySink@1.0.0
#addin nuget:?package=DCTekSolutions.ExternalCallGuard.NLogTelemetrySink&version=1.0.0
#tool nuget:?package=DCTekSolutions.ExternalCallGuard.NLogTelemetrySink&version=1.0.0
ExternalCallGuard.NLogTelemetrySink
ExternalCallGuard.NLogTelemetrySink by DC Tek Solutions turns ExternalCallGuard lifecycle events into structured NLog records. It keeps logging out of the core resilience package while giving operations teams consistent fields for starts, retries, successes, failures, cancellations, and timeouts.
Free to use in personal and commercial projects. This package is closed-source proprietary software; see LICENSE.md.
Install
Applications normally install both the core package and this adapter:
dotnet add package DCTekSolutions.ExternalCallGuard --version 1.0.0
dotnet add package DCTekSolutions.ExternalCallGuard.NLogTelemetrySink --version 1.0.0
The sink itself depends only on DCTekSolutions.ExternalCallGuard.Abstractions. This keeps the adapter reusable and prevents it from reaching into resilience implementation details.
Quick start
using DCTekSolutions.ExternalCallGuard.DependencyInjectionSetups;
using DCTekSolutions.ExternalCallGuard.NLogTelemetrySink.DependencyInjectionSetups;
services.AddExternalCallGuard();
services.AddNLogTelemetrySink();
That is enough to log retries, terminal failures, caller cancellations, and guard timeouts. Started and successful events are intentionally quiet by default.
Enable the optional lower-severity events when you need a complete call timeline:
services.AddNLogTelemetrySink(options =>
{
options.LogStartedCalls = true;
options.LogSuccessfulCalls = true;
});
Calling AddNLogTelemetrySink more than once does not duplicate this sink. Other IExternalCallTelemetrySink registrations are preserved.
If registration is repeated with more than one options function, every function is retained and applied in registration order even though only one sink is added.
Configuration reference
AddNLogTelemetrySink accepts an optional Action<NLogTelemetrySinkOptions>. In simple terms, this is a function that receives the options object so the application can change it during service registration:
services.AddNLogTelemetrySink(options =>
{
options.LogStartedCalls = true;
options.LogSuccessfulCalls = true;
});
| Option | Default | Effect when enabled |
|---|---|---|
LogStartedCalls |
false |
Writes each started event at Debug. The NLog rules must allow Debug records for these to reach a target. |
LogSuccessfulCalls |
false |
Writes each successful event at Info. Leave this off when only exceptional activity is needed. |
Retry, terminal failure, caller cancellation, and timeout records do not have switches and are always sent to NLog. Whether an emitted record reaches a file, database, console, or other destination is controlled by the application's NLog rules and targets.
Registration details
AddNLogTelemetrySink(IServiceCollection, Action<NLogTelemetrySinkOptions>?) returns the same service collection, so calls can be chained. It throws ArgumentNullException when the service collection is null.
The extension registers NLogTelemetrySink as an IExternalCallTelemetrySink without replacing other sink implementations. It also preserves an existing NLog.ILogger. When no logger has been registered, it adds one named ExternalCallGuard.
ServiceRegistry is the public helper behind the extension method. Most applications do not need it, but explicit registration is equivalent:
var registry = new ServiceRegistry(options =>
{
options.LogStartedCalls = true;
options.LogSuccessfulCalls = true;
});
registry.RegisterServices(services);
RegisterServices also returns the same service collection and throws ArgumentNullException for a null collection.
Calling the sink directly
Dependency injection normally creates and calls the sink. A test or focused integration can construct it directly with an NLog logger and IOptions<NLogTelemetrySinkOptions>:
using System;
using System.Collections.Generic;
using System.Threading;
using DCTekSolutions.ExternalCallGuard.Abstractions.Models;
using DCTekSolutions.ExternalCallGuard.NLogTelemetrySink.Models;
using DCTekSolutions.ExternalCallGuard.NLogTelemetrySink.Sinks;
using Microsoft.Extensions.Options;
NLog.ILogger logger = NLog.LogManager.GetLogger("ExternalCallGuard");
var sinkOptions = Options.Create(new NLogTelemetrySinkOptions
{
LogSuccessfulCalls = true
});
var sink = new NLogTelemetrySink(logger, sinkOptions);
var call = new ExternalCallDescriptor(
"CatalogApi",
"GetProduct",
new ExternalCallPolicyKey("Standard"),
new Dictionary<string, object?> { ["region"] = "east" });
var telemetry = new ExternalCallSucceededEvent(
call,
DateTimeOffset.UtcNow,
TimeSpan.FromMilliseconds(42),
attemptCount: 1);
await sink.WriteAsync(telemetry, CancellationToken.None);
The constructor throws ArgumentNullException for a missing logger, options wrapper, or options value. WriteAsync throws ArgumentNullException for a missing event and OperationCanceledException when its token is already canceled. It completes synchronously after writing or ignoring the event. The token is checked before logging starts; it does not cancel an NLog target after logging has begun.
When called directly, an NLog target exception can surface if NLog is configured to throw. When the sink is registered with ExternalCallGuard, the guard isolates telemetry-sink failures from the guarded operation's result, exception, cancellation, and timing.
Event levels
| Guard event | NLog level | Written by default? |
|---|---|---|
| Started | Debug |
No; enable LogStartedCalls. |
| Retry scheduled | Warn |
Yes. |
| Succeeded | Info |
No; enable LogSuccessfulCalls. |
| Failed | Error |
Yes. |
| Cancelled by caller | Info |
Yes. |
| Timed out | Error |
Yes. |
Caller cancellation is logged separately from a timeout so dashboards do not mistake abandoned work for dependency failure.
Structured properties
Every emitted record includes these searchable NLog event properties:
| Property | Meaning |
|---|---|
Dependency |
Stable external-system name from the call descriptor. |
Operation |
Stable operation name from the call descriptor. |
Policy |
Policy key selected for the call. |
DurationMilliseconds |
Elapsed call duration when the event was emitted. |
Outcome |
Started, Retry, Succeeded, Failed, Cancelled, or TimedOut. |
Metadata |
The complete caller-supplied read-only metadata dictionary. |
Metadata.<key> |
Each metadata item flattened for structured-log queries. |
Event-specific properties are:
| Event | Additional properties |
|---|---|
| Started | None. |
| Retry | Attempt, RetryDelayMilliseconds, ExceptionType, and the original exception. |
| Succeeded | AttemptCount. |
| Failed | AttemptCount, ExceptionType, and the original exception. |
| Cancelled | AttemptCount, ExceptionType, and the original OperationCanceledException. |
| Timed out | AttemptCount, ExceptionType, and the original timeout exception. |
WriteAsync safely ignores an unknown ExternalCallTelemetryEvent subtype. It does not turn an unknown event into a misleading log record.
Example NLog configuration
The sink uses the logger name ExternalCallGuard unless an application supplies its own NLog.ILogger registration. JSON output makes the structured properties easy to ingest:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target xsi:type="File"
name="externalCalls"
fileName="${basedir}/logs/external-calls.json">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<attribute name="timestamp" layout="${longdate}" />
<attribute name="level" layout="${level}" />
<attribute name="logger" layout="${logger}" />
<attribute name="message" layout="${message}" />
<attribute name="exception" layout="${exception:format=tostring}" />
</layout>
</target>
</targets>
<rules>
<logger name="ExternalCallGuard" minlevel="Debug" writeTo="externalCalls" />
</rules>
</nlog>
If you leave started and successful events disabled, minlevel="Info" is normally sufficient. Keep Debug when started events are enabled.
Typical log queries
Because the outcome is semantic rather than inferred from message text, common operational questions are straightforward:
Outcome = "Retry" and Dependency = "CatalogApi"
Outcome = "TimedOut" and Operation = "GetProduct"
Outcome = "Failed" and ExceptionType = "System.Net.Http.HttpRequestException"
Metadata.region = "east"
The exact query syntax depends on the NLog target and log-analysis platform, but the property names stay consistent.
Use case: see retries without logging every success
The default settings are useful for normal production traffic:
services.AddExternalCallGuard();
services.AddNLogTelemetrySink();
Successful calls add no NLog noise. A transient failure produces a Warn record for each scheduled retry, and a final exhausted failure produces one Error record. This makes intermittent dependency trouble visible without a log entry for every healthy request.
Use case: capture a full diagnostic timeline
Temporarily enable started and successful events while diagnosing latency or policy selection:
services.AddNLogTelemetrySink(options =>
{
options.LogStartedCalls = true;
options.LogSuccessfulCalls = true;
});
Filter the records using stable Dependency, Operation, and metadata values to reconstruct a call's progression through retry and completion.
Use case: provide a custom NLog logger
Register an NLog.ILogger before adding the sink when you need a different logger name or factory strategy:
using NLog;
services.AddSingleton<ILogger>(_ =>
LogManager.GetLogger("Application.ExternalDependencies"));
services.AddNLogTelemetrySink();
The adapter respects an existing NLog.ILogger registration and does not replace it.
Combine with other telemetry sinks
The registration uses the enumerable dependency-injection pattern, so NLog can coexist with metrics, tracing, auditing, or test sinks:
services.AddSingleton<IExternalCallTelemetrySink, MetricsTelemetrySink>();
services.AddNLogTelemetrySink();
The core guard dispatches to the sinks in registration order. A sink exception or telemetry timeout does not change the business operation's result or replace its exception.
Security guidance
Descriptor metadata is flattened into NLog properties. Never put passwords, access tokens, authorization headers, cookies, connection strings, raw payloads, personal information, or other secrets in dependency names, operation names, or metadata. Prefer stable, low-cardinality labels such as service name, region, or request category.
Compatibility
- Single-targets .NET Standard 2.1 and can be consumed by runtimes that implement that standard. .NET Framework does not implement .NET Standard 2.1.
- Depends on
DCTekSolutions.ExternalCallGuard.Abstractions, NLog, and Microsoft dependency-injection/options abstractions. - Does not require a project reference to the core implementation.
- Implements only
IExternalCallTelemetrySink; it does not alter retry or timeout behavior.
License
Free for personal and commercial use under the included DC Tek Solutions license. Package license acceptance is required during installation.
Support / Donate
ExternalCallGuard is developed by DC Tek Solutions. For documentation and support, visit the ExternalCallGuard support wiki.
If this package saves you time, please consider supporting continued development:
Thank you for your support!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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 was computed. 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 was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- DCTekSolutions.ExternalCallGuard.Abstractions (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- NLog (>= 6.1.2)
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.0.0 | 84 | 9/21/2026 |
Initial standalone release of the structured NLog adapter for ExternalCallGuard telemetry.