Production-ready, zero-boilerplate structured logging for .NET 8+ services and ASP.NET Core APIs.
Dragonfire.Logging — core (framework-agnostic)
Dragonfire.Logging.AspNetCore — HTTP request/response logging for MVC controllers
Dragonfire.Logging.Generator — Roslyn source generator (compile-time proxies, no runtime overhead)
Dragonfire.Logging.ApplicationInsights — Application Insights enrichment via ITelemetryInitializer
Every log entry writes individually named structured properties — not one opaque JSON blob — so you can filter, alert, and build dashboards directly against each field in Application Insights, Seq, Loki, Datadog, or any ILogger-compatible sink.
- What problem does this solve?
- Package overview
- Source generator — service-layer logging
- ASP.NET MVC — controller request/response logging
- Application Insights enrichment
- [LogProperty] reference
- [Log] attribute reference
- Runtime options reference
- KQL queries in Application Insights
- Requirements
Writing logging code by hand is tedious, inconsistent, and almost always incomplete:
// What every team ends up with
public async Task<Order> CreateOrderAsync(string tenantId, CreateOrderRequest req)
{
_logger.LogInformation("CreateOrder called tenantId={TenantId}", tenantId);
var sw = Stopwatch.StartNew();
try
{
var result = await _db.InsertAsync(req);
_logger.LogInformation("CreateOrder succeeded in {Ms}ms", sw.ElapsedMilliseconds);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "CreateOrder failed"); // no elapsed time, no context
throw;
}
}Dragonfire.Logging automates this at compile time. The Roslyn source generator reads your attributes at build time and emits a concrete proxy class that:
- Times every call with sub-millisecond precision (
Stopwatch.Elapsed.TotalMilliseconds) - Logs success and failure with a consistent structured format
- Promotes
[LogProperty]-annotated fields directly tocustomDimensions— no JSON parsing in KQL - Uses only
ILogger<T>— zero runtime reflection, zero third-party dependencies - Is fully AOT/trim-safe and debuggable (stack traces point to the generated
.g.csfile)
| Package | Target | Purpose |
|---|---|---|
Dragonfire.Logging |
net8.0 |
Core: ILoggable, [Log], [LogProperty], [LogIgnore] attributes |
Dragonfire.Logging.AspNetCore |
net8.0 |
MVC action filter for controller request/response logging |
Dragonfire.Logging.Generator |
netstandard2.0 (analyzer) |
Roslyn source generator — emits compile-time proxy classes |
Dragonfire.Logging.ApplicationInsights |
net8.0 |
ITelemetryInitializer that copies [LogProperty] fields to all AI telemetry types |
At build time, the generator:
- Finds every
classthat implementsILoggable(directly or via an interface) - Collects the service interfaces that class also implements (
IOrderService, etc.) - Reads
[Log],[LogIgnore], and[LogProperty]attributes from the Roslyn semantic model — no runtime attribute scanning - Emits one concrete proxy class per service, plus shared helpers in a single extensions file
The generated proxy uses only ILogger<T> and a small generated options class. It has no dependency on any Dragonfire runtime service — the generator is self-contained.
Runtime proxy (DispatchProxy) |
Compile-time proxy (generator) | |
|---|---|---|
| Reflection at startup | Yes | None |
| Reflection per call | Yes (MethodInfo.Invoke) |
None — direct virtual dispatch |
| AOT / NativeAOT | ❌ | ✅ |
| Trimming | ❌ | ✅ |
| Debuggability | DispatchProxy internals in stack trace |
Real .g.cs file, full IDE navigation |
| Build-time visibility | None | Inspectable in obj/Generated/ |
Project reference (monorepo / local development):
<!-- YourApi.csproj -->
<ItemGroup>
<ProjectReference Include="..\Dragonfire.Logging\Dragonfire.Logging.csproj" />
<!-- ReferenceOutputAssembly="false" — the generator only runs at build time,
its DLL is never added to your app's dependencies. -->
<ProjectReference
Include="..\Dragonfire.Logging.Generator\Dragonfire.Logging.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>NuGet (published package):
<ItemGroup>
<PackageReference Include="Dragonfire.Logging" Version="1.0.0" />
<PackageReference Include="Dragonfire.Logging.Generator" Version="1.0.0" />
</ItemGroup>ILoggable can live on the interface or the class — both patterns work:
using Dragonfire.Logging.Abstractions;
using Dragonfire.Logging.Attributes;
using Microsoft.Extensions.Logging;
// ── Pattern A: ILoggable on the interface ────────────────────────────────────
// Attributes live on the interface. The class needs no logging attributes at all.
public interface IOrderService : ILoggable
{
[Log]
Task<Order> GetOrderAsync(
[LogProperty("TenantId")] string tenantId, // → customDimensions["Request.TenantId"]
[LogProperty] string orderId); // → customDimensions["Request.orderId"]
[Log(Level = LogLevel.Debug)]
void ProcessOrder(string orderId);
[LogIgnore] // pass-through, zero logging overhead
string GetVersion();
}
public class OrderService : IOrderService // no ILoggable here — inherited via interface
{
public Task<Order> GetOrderAsync(string tenantId, string orderId) { ... }
public void ProcessOrder(string orderId) { ... }
public string GetVersion() => "1.0";
}
// ── Pattern B: ILoggable on the class ────────────────────────────────────────
// Attributes can be on the interface, the class, or both.
public interface IInventoryService
{
Task<int> GetStockAsync([LogProperty("Sku")] string sku);
}
public class InventoryService : IInventoryService, ILoggable
{
public Task<int> GetStockAsync(string sku) => Task.FromResult(42);
}Mark properties on request/response DTOs to promote them individually to customDimensions:
public class CreateOrderRequest
{
[LogProperty("OrderRef")] // → customDimensions["Request.OrderRef"]
public string ExternalReference { get; set; }
[LogProperty] // → customDimensions["Request.Region"]
public string Region { get; set; }
public List<OrderLineDto> Lines { get; set; } // not promoted — not annotated
}
public class Order
{
[LogProperty] // → customDimensions["Response.OrderId"]
public string OrderId { get; set; }
[LogProperty("Price")] // → customDimensions["Response.Price"]
public decimal TotalAmount { get; set; }
}The generator reads these annotations at build time and emits direct property reads — __scope["Response.Price"] = __result.TotalAmount; — no runtime reflection.
// Program.cs
// 1. Register your services normally
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<IInventoryService, InventoryService>();
// 2. Wrap all ILoggable services with their generated proxies.
// The configure lambda is optional — omit it to use all defaults.
builder.Services.AddDragonfireGeneratedLogging(options =>
{
options.LogRequestProperties = true; // include Request.* fields (default: true)
options.LogResponseProperties = true; // include Response.* fields (default: true)
options.LogStackTrace = true; // include Dragonfire.StackTrace on errors (default: true)
options.LogNullResponse = false; // warn when a [Log] method returns null (default: false)
options.OverrideLevel = null; // null = use [Log(Level=...)] per method
// Suppress specific fields by bare name (matches both Request.X and Response.X)
options.ExcludeProperties.Add("InternalId");
options.ExcludeProperties.Add("RawPayload");
});AddDragonfireGeneratedLogging() is itself generated — it contains exactly one DecorateService call per discovered ILoggable class. No Scrutor or decoration library is needed.
For the IOrderService above, the generator emits OrderServiceLoggingProxy.g.cs:
// <auto-generated/> — lives in obj/Generated/ at build time
internal sealed class OrderServiceLoggingProxy : global::MyApp.Services.IOrderService
{
private readonly global::MyApp.Services.IOrderService _innerIOrderService;
private readonly global::Microsoft.Extensions.Logging.ILogger<global::MyApp.Services.OrderService> _logger;
private readonly global::Dragonfire.Logging.Generated.DragonfireGeneratedLoggingOptions _options;
public async global::System.Threading.Tasks.Task<global::MyApp.Services.Order> GetOrderAsync(
string tenantId, string orderId)
{
var __scope = new global::Dragonfire.Logging.Generated.__DragonfireScopeState(
"[Dragonfire] OrderService.GetOrderAsync");
__scope["Dragonfire.ServiceName"] = "OrderService";
__scope["Dragonfire.MethodName"] = "GetOrderAsync";
// Request fields — guarded by options, resolved at compile time, zero reflection
if (_options.LogRequestProperties)
{
// [LogProperty("TenantId")] on parameter 'tenantId'
if (!_options.IsExcluded("TenantId"))
__scope["Request.TenantId"] = tenantId;
// [LogProperty] on parameter 'orderId'
if (!_options.IsExcluded("orderId"))
__scope["Request.orderId"] = orderId;
}
global::MyApp.Services.Order __result = default!;
var __sw = global::System.Diagnostics.Stopwatch.StartNew();
try
{
__result = await _innerIOrderService.GetOrderAsync(tenantId, orderId)
.ConfigureAwait(false);
// Response fields — from [LogProperty] on Order properties
if (_options.LogResponseProperties && __result is not null)
{
// [LogProperty] on result.OrderId
if (!_options.IsExcluded("OrderId"))
__scope["Response.OrderId"] = __result.OrderId;
// [LogProperty("Price")] on result.TotalAmount
if (!_options.IsExcluded("Price"))
__scope["Response.Price"] = __result.TotalAmount;
}
// Null-response warning — only fires when [Log] is present and options.LogNullResponse = true
if (_options.LogNullResponse && __result is null)
__scope["Dragonfire.NullResponse"] = true;
__sw.Stop();
__scope["Dragonfire.ElapsedMs"] = __sw.Elapsed.TotalMilliseconds;
using (_logger.BeginScope(__scope))
_logger.Log(
_options.OverrideLevel ?? global::Microsoft.Extensions.Logging.LogLevel.Information,
"[Dragonfire] {Dragonfire_ServiceName}.{Dragonfire_MethodName} completed in {Dragonfire_ElapsedMs}ms",
"OrderService", "GetOrderAsync", __sw.Elapsed.TotalMilliseconds);
}
catch (global::System.Exception __ex)
{
__sw.Stop();
__scope["Dragonfire.ElapsedMs"] = __sw.Elapsed.TotalMilliseconds;
__scope["Dragonfire.IsError"] = true;
__scope["Dragonfire.ErrorMessage"] = __ex.Message;
if (_options.LogStackTrace)
__scope["Dragonfire.StackTrace"] = __ex.StackTrace;
using (_logger.BeginScope(__scope))
_logger.Log(
_options.OverrideLevel ?? global::Microsoft.Extensions.Logging.LogLevel.Error,
__ex,
"[Dragonfire] {Dragonfire_ServiceName}.{Dragonfire_MethodName} FAILED in {Dragonfire_ElapsedMs}ms — {Dragonfire_ErrorMessage}",
"OrderService", "GetOrderAsync", __sw.Elapsed.TotalMilliseconds, __ex.Message);
throw;
}
return __result;
}
// [LogIgnore] → pure pass-through, zero logging overhead
public string GetVersion() => _innerIOrderService.GetVersion();
}Key properties:
- No
MethodInfo.Invoke— all calls are direct virtual dispatch - No JSON serialisation — only explicitly annotated
[LogProperty]fields are captured - No runtime reflection — DTO property reads are direct C# property accesses, resolved at build time
[LogIgnore]produces a one-liner — zero overhead
For a successful GetOrderAsync call:
{
"Message": "[Dragonfire] OrderService.GetOrderAsync",
"Dragonfire.ServiceName": "OrderService",
"Dragonfire.MethodName": "GetOrderAsync",
"Request.TenantId": "acme-corp",
"Request.orderId": "ORD-123",
"Response.OrderId": "ORD-123",
"Response.Price": 49.99,
"Dragonfire.ElapsedMs": 1.847
}When a method returns null and options.LogNullResponse = true:
{
"Message": "[Dragonfire] OrderService.GetOrderAsync",
"Dragonfire.NullResponse": true,
"Dragonfire.ElapsedMs": 0.412
}On error:
{
"Message": "[Dragonfire] OrderService.GetOrderAsync FAILED",
"Dragonfire.IsError": true,
"Dragonfire.ErrorMessage": "Value cannot be null.",
"Dragonfire.StackTrace": " at OrderService.GetOrderAsync ...",
"Dragonfire.ElapsedMs": 3.201
}Add to your .csproj to write .g.cs files to disk during build:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>Files land at:
obj/Debug/net8.0/Generated/
Dragonfire.Logging.Generator/
Dragonfire.Logging.Generator.LoggingProxyGenerator/
OrderServiceLoggingProxy.g.cs ← one per ILoggable service
InventoryServiceLoggingProxy.g.cs
DragonfireGeneratedExtensions.g.cs ← options class + scope wrapper + AddDragonfireGeneratedLogging()
Only two file types are emitted: one proxy per service, and one shared extensions file containing DragonfireGeneratedLoggingOptions, __DragonfireScopeState, and the DI registration helper.
The default console logger drops scope data. Use the JSON console formatter to see every customDimension locally:
// Program.cs — development only
builder.Logging.AddJsonConsole(o =>
{
o.IncludeScopes = true;
o.JsonWriterOptions = new System.Text.Json.JsonWriterOptions { Indented = true };
});Or in appsettings.Development.json (no code change needed):
{
"Logging": {
"Console": {
"FormatterName": "json",
"FormatterOptions": {
"IncludeScopes": true,
"JsonWriterOptions": { "Indented": true }
}
}
}
}Sample console output for a successful call:
{
"Timestamp": "2026-04-13T10:22:01.443Z",
"Level": "Information",
"Message": "[Dragonfire] OrderService.GetOrderAsync completed in 1.847ms",
"Scopes": [
{
"Message": "[Dragonfire] OrderService.GetOrderAsync",
"Dragonfire.ServiceName": "OrderService",
"Dragonfire.MethodName": "GetOrderAsync",
"Request.TenantId": "acme-corp",
"Request.orderId": "ORD-123",
"Response.OrderId": "ORD-123",
"Response.Price": 49.99,
"Dragonfire.ElapsedMs": 1.847
}
]
}For a full local UI with search and filters — identical to Application Insights — run Seq in Docker:
docker run --name seq -d -e ACCEPT_EULA=Y -p 5341:5341 -p 80:80 datalust/seqdotnet add package Seq.Extensions.Loggingbuilder.Logging.AddSeq("http://localhost:5341");Open http://localhost — every scope property is a searchable field.
Dragonfire.Logging.AspNetCore registers a global IAsyncActionFilter (DragonfireLoggingFilter) that runs at int.MinValue (outermost filter position). It:
- Reads action parameters before execution directly from MVC's already-bound argument dictionary — no raw body parsing, no reflection on the request stream
- Executes the inner pipeline via
await next() - Reads the response from
ObjectResult.ValueorJsonResult.Value— the response stream is never replaced or buffered - Promotes only
[LogProperty]-annotated parameters and DTO properties tocustomDimensionsasRequest.*/Response.*— direct property reads, no serialisation - Writes a
X-Correlation-IDresponse header (echoes inbound value or generates a new GUID) - Stores the named properties in
HttpContext.Itemsfor downstream enrichers (e.g.DragonfireTelemetryInitializer)
[Log], [LogProperty], and [LogIgnore] work on controllers and actions the same way they work on services.
<!-- YourApi.csproj -->
<ItemGroup>
<ProjectReference Include="..\Dragonfire.Logging.AspNetCore\Dragonfire.Logging.AspNetCore.csproj" />
<!-- or: <PackageReference Include="Dragonfire.Logging.AspNetCore" Version="1.0.0" /> -->
</ItemGroup>// Program.cs
builder.Services.AddControllers();
builder.Services.AddDragonfireAspNetCore(
core: opt =>
{
opt.IncludeStackTraceOnError = true;
},
http: opt =>
{
opt.EnableRequestLogging = true;
opt.EnableResponseLogging = true;
opt.LogValidationErrors = true;
opt.ExcludePaths = new[] { "/health", "/metrics", "/swagger" };
});
var app = builder.Build();
app.MapControllers();
app.Run();Note:
AddDragonfireAspNetCoreregisters the global MVC filter. There is no separateUseDragonfireLogging()middleware call required for controller-based APIs.
using Dragonfire.Logging.Attributes;
using Microsoft.Extensions.Logging;
// [Log] on the controller sets defaults for all actions.
// Individual actions can override or opt out.
[ApiController]
[Route("api/[controller]")]
[Log(Level = LogLevel.Information)]
public class OrdersController : ControllerBase
{
// Uses controller-level [Log] — logs request parameters and ObjectResult response
[HttpGet("{orderId}")]
public async Task<ActionResult<Order>> GetOrder(
[LogProperty("TenantId")] string tenantId, // → customDimensions["Request.TenantId"]
string orderId) // not annotated — not promoted
{
var order = await _orderService.GetOrderAsync(tenantId, orderId);
return Ok(order);
}
// Override log level per action
[HttpPost]
[Log(Level = LogLevel.Warning)]
public async Task<ActionResult<Order>> CreateOrder(
[LogProperty("TenantId")] string tenantId,
[FromBody] CreateOrderRequest request)
{
var order = await _orderService.CreateOrderAsync(tenantId, request);
return Created($"/api/orders/{order.OrderId}", order);
}
// Exempt sensitive actions from logging entirely
[HttpDelete("{orderId}")]
[LogIgnore]
public async Task<IActionResult> DeleteOrder(string orderId)
{
await _orderService.DeleteOrderAsync(orderId);
return NoContent();
}
}The filter reads [LogProperty] in two places:
- Directly on action parameters — the value is taken from MVC's bound argument dictionary
- On properties of a bound DTO — the filter walks the object's public properties via reflection and reads only the annotated ones
// Request DTO — annotated properties surface as Request.* in customDimensions
public class CreateOrderRequest
{
[LogProperty("OrderRef")] // → customDimensions["Request.OrderRef"]
public string ExternalReference { get; set; }
[LogProperty] // → customDimensions["Request.Region"]
public string Region { get; set; }
public string InternalNotes { get; set; } // not promoted
}
// Response DTO — annotated properties surface as Response.* in customDimensions
// Works for both ObjectResult and JsonResult return types
public class Order
{
[LogProperty] // → customDimensions["Response.OrderId"]
public string OrderId { get; set; }
[LogProperty("Price")] // → customDimensions["Response.Price"]
public decimal TotalAmount { get; set; }
public string InternalState { get; set; } // not promoted
}For a successful POST /api/orders call:
{
"Message": "[Dragonfire] POST /api/orders",
"Dragonfire.HttpMethod": "POST",
"Dragonfire.Path": "/api/orders",
"Dragonfire.StatusCode": 201,
"Dragonfire.ElapsedMs": 23.441,
"Dragonfire.CorrelationId": "a3f2b1c4-...",
"Request.TenantId": "acme-corp",
"Request.OrderRef": "EXT-9981",
"Request.Region": "EU-WEST",
"Response.OrderId": "ORD-456",
"Response.Price": 149.99
}On a 500 error:
{
"Dragonfire.IsError": true,
"Dragonfire.StatusCode": 500,
"Dragonfire.ErrorMessage": "Database timeout",
"Dragonfire.StackTrace": " at OrderService.CreateOrderAsync ..."
}http: opt => opt.ExcludePaths = new[] { "/health", "/metrics", "/swagger", "/favicon.ico" }Every request gets a X-Correlation-ID header echoed back in the response. If the caller sends one it is reused; otherwise a new GUID is generated and appears in every log entry for that request.
Dragonfire.Logging.ApplicationInsights bridges the [LogProperty] fields captured by the MVC filter into all Application Insights telemetry types — not just traces, but also requests, dependencies, exceptions, and custom events — so the same Request.TenantId / Response.Price keys appear consistently across every telemetry item for a given HTTP request.
DragonfireLoggingFilter stores the collected [LogProperty] dictionary in HttpContext.Items after the action executes. DragonfireTelemetryInitializer reads that dictionary and copies every entry into ISupportProperties.Properties — the standard Application Insights property bag — for any telemetry that passes through the initializer within the same HTTP request.
<!-- YourApi.csproj -->
<ItemGroup>
<ProjectReference Include="..\Dragonfire.Logging.ApplicationInsights\Dragonfire.Logging.ApplicationInsights.csproj" />
<!-- or: <PackageReference Include="Dragonfire.Logging.ApplicationInsights" Version="1.0.0" /> -->
</ItemGroup>// Program.cs
builder.Services.AddApplicationInsightsTelemetry();
builder.Services.AddDragonfireAspNetCore(...); // must be registered first
builder.Services.AddDragonfireApplicationInsights();AddDragonfireApplicationInsights() registers:
IHttpContextAccessor(if not already registered)DragonfireTelemetryInitializerasITelemetryInitializersingleton
No other configuration is needed. The initializer fires for every telemetry item AI captures and copies all Request.* / Response.* fields that the filter promoted during the request.
After enabling the initializer, every AI telemetry item for a request that went through DragonfireLoggingFilter will carry:
customDimensions["Request.TenantId"] = "acme-corp"
customDimensions["Response.OrderId"] = "ORD-456"
customDimensions["Response.Price"] = "149.99"
This means the same tenant / order context is visible on the RequestTelemetry, all DependencyTelemetry items (outbound HTTP, SQL, etc.), any ExceptionTelemetry, and every TraceTelemetry produced during that request — without any manual TelemetryClient calls.
| Usage | Syntax | customDimensions key |
|---|---|---|
| Parameter — named | [LogProperty("TenantId")] string tenantId |
Request.TenantId |
| Parameter — use param name | [LogProperty] string orderId |
Request.orderId |
| DTO property — named | [LogProperty("OrderRef")] public string Ext { get; set; } |
Request.OrderRef (when on a param type) or Response.OrderRef (when on a return type) |
| DTO property — use prop name | [LogProperty] public string Region { get; set; } |
Request.Region / Response.Region |
[LogProperty] can go on:
- Service interface method parameters (picked up by the source generator at build time)
- Service class method parameters
- Controller action parameters (picked up by the ASP.NET Core filter at runtime)
- Properties of any DTO class that appears as a parameter or return type
The Request. / Response. prefix is applied automatically — you never write it in the attribute.
Apply to a class (default for all members) or a method (overrides class-level default):
[Log(
Level = LogLevel.Information // log level for the success path
)]// On a method — proxy passes through with ZERO logging overhead
[LogIgnore]
public string GetHealthStatus() => "ok";
// On a class — disables logging for all methods on this service
[LogIgnore]
public class DiagnosticsService : IDiagnosticsService, ILoggable { ... }DragonfireGeneratedLoggingOptions is itself generated — it lives in your assembly alongside the proxy classes. Configure it once at DI registration time; the same instance is injected into every proxy:
builder.Services.AddDragonfireGeneratedLogging(options =>
{
options.LogRequestProperties = true;
options.LogResponseProperties = true;
options.LogStackTrace = true;
options.LogNullResponse = false;
options.OverrideLevel = null;
options.ExcludeProperties.Add("SensitiveField");
});| Option | Type | Default | Description |
|---|---|---|---|
LogRequestProperties |
bool |
true |
Include all Request.* scope entries from [LogProperty] on parameters and their DTO types |
LogResponseProperties |
bool |
true |
Include all Response.* scope entries from [LogProperty] on the return type's properties |
LogStackTrace |
bool |
true |
Include Dragonfire.StackTrace in the error scope. Disable to reduce log size in production |
LogNullResponse |
bool |
false |
Add Dragonfire.NullResponse = true to the scope when a [Log]-annotated method returns null. Only applies to methods with the [Log] attribute; has no effect on [LogIgnore] or un-annotated methods |
ExcludeProperties |
ISet<string> |
empty | Bare property names to suppress (case-insensitive). "OrderId" suppresses both Request.OrderId and Response.OrderId |
OverrideLevel |
LogLevel? |
null |
Override the success log level for all methods. null = use the [Log(Level = ...)] attribute value per method. Error logs always use LogLevel.Error regardless |
// All failed service calls in the last hour
traces
| where timestamp > ago(1h)
| where customDimensions["Dragonfire.IsError"] == "True"
| project timestamp,
customDimensions["Dragonfire.ServiceName"],
customDimensions["Dragonfire.MethodName"],
customDimensions["Dragonfire.ErrorMessage"],
customDimensions["Dragonfire.ElapsedMs"]
| order by timestamp desc
// P95 latency per method
traces
| where isnotempty(customDimensions["Dragonfire.ElapsedMs"])
| summarize p95 = percentile(todouble(customDimensions["Dragonfire.ElapsedMs"]), 95)
by tostring(customDimensions["Dragonfire.MethodName"])
| order by p95 desc
// All orders for a specific tenant — direct indexed lookup, no JSON parsing
traces
| where customDimensions["Request.TenantId"] == "acme-corp"
| project timestamp,
customDimensions["Dragonfire.MethodName"],
customDimensions["Request.orderId"],
customDimensions["Response.Price"],
customDimensions["Dragonfire.ElapsedMs"]
// HTTP 500 errors on a specific controller action
traces
| where customDimensions["Dragonfire.StatusCode"] == "500"
and customDimensions["Dragonfire.MethodName"] == "CreateOrder"
| project timestamp,
customDimensions["Request.TenantId"],
customDimensions["Request.OrderRef"],
customDimensions["Dragonfire.ErrorMessage"]
| order by timestamp desc
// Slow requests (> 500 ms) across all services and controllers
traces
| where todouble(customDimensions["Dragonfire.ElapsedMs"]) > 500
| project timestamp,
customDimensions["Dragonfire.ServiceName"],
customDimensions["Dragonfire.MethodName"],
customDimensions["Dragonfire.ElapsedMs"]
| order by todouble(customDimensions["Dragonfire.ElapsedMs"]) desc
// Find all requests where a service returned null
traces
| where customDimensions["Dragonfire.NullResponse"] == "True"
| project timestamp,
customDimensions["Dragonfire.ServiceName"],
customDimensions["Dragonfire.MethodName"],
customDimensions["Request.TenantId"]
| order by timestamp desc| Version | |
|---|---|
| .NET | 8.0+ |
| ASP.NET Core | 8.0+ (Dragonfire.Logging.AspNetCore only) |
| Roslyn | 4.8+ (shipped with .NET 8 SDK) |
| Application Insights SDK | 2.22+ (Dragonfire.Logging.ApplicationInsights only) |
Runtime dependencies:
| Package | Used by |
|---|---|
Microsoft.Extensions.Logging.Abstractions |
Core, Generator (generated code) |
Microsoft.Extensions.DependencyInjection.Abstractions |
Core, Generator (generated DI helpers) |
Microsoft.AspNetCore.Mvc.Core |
AspNetCore |
Microsoft.ApplicationInsights.AspNetCore |
ApplicationInsights |
The Generator package has zero runtime dependencies — Microsoft.CodeAnalysis.CSharp is PrivateAssets="all" and never flows to the consuming project. The generated code depends only on Microsoft.Extensions.Logging and Microsoft.Extensions.DependencyInjection, which are already present in any .NET 8 application.
MIT