Logging to Application Insights
Send structured logs from your Benzene services to Azure Application Insights with correlation and custom properties.
Problem Statement
You're running Benzene services (in Azure Functions, AWS Lambda, or containers) and need to:
- Send all logs to Application Insights for centralized monitoring
- Include correlation IDs to trace requests across services
- Add custom properties (topic, transport, handler) to filter and analyze logs
- Use structured logging for better querying in Application Insights
Prerequisites
- A Benzene service (Azure Functions, AWS Lambda, or ASP.NET Core)
- An Application Insights resource in Azure
- The Application Insights instrumentation key or connection string
Installation
Install the required NuGet packages:
dotnet add package Benzene.Diagnostics --prerelease
dotnet add package Microsoft.ApplicationInsights.AspNetCore
# OR for non-ASP.NET hosts:
dotnet add package Microsoft.Extensions.Logging.ApplicationInsights --prerelease
Step-by-Step Implementation
1. Add Application Insights Configuration
Set your Application Insights connection string in configuration. For Azure Functions, add it to local.settings.json (local) or Function App settings (deployed):
{
"Values": {
"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=your-key-here;..."
}
}
For AWS Lambda, use an environment variable:
APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=your-key-here;...
2. Configure Services
In your BenzeneStartUp class, configure logging and diagnostics:
using Benzene.Abstractions.Hosting;
using Benzene.Diagnostics;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
public class StartUp : BenzeneStartUp
{
public override IConfiguration GetConfiguration()
{
return new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("local.settings.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
// Add Application Insights
services.AddApplicationInsightsTelemetry(options =>
{
options.ConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
});
// Configure logging with Application Insights
services.AddLogging(builder =>
{
builder.AddApplicationInsights(
configureTelemetryConfiguration: (config) =>
{
config.ConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
},
configureApplicationInsightsLoggerOptions: (options) =>
{
// Include scopes to capture structured properties
options.IncludeScopes = true;
});
// Optional: Set minimum log level
builder.SetMinimumLevel(LogLevel.Information);
// Optional: Filter out noisy logs
builder.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft", LogLevel.Warning);
builder.AddFilter<ApplicationInsightsLoggerProvider>("System", LogLevel.Warning);
});
// Add Benzene diagnostics for automatic instrumentation
services.UsingBenzene(x => x
.AddDiagnostics()
.AddMessageHandlers(typeof(MyHandler).Assembly));
}
public override void Configure(IBenzeneApplicationBuilder app, IConfiguration configuration)
{
app.UseAwsLambda(eventPipeline => eventPipeline
.UseApiGateway(apiGatewayApp => apiGatewayApp
// Add structured log enrichment
.UseBenzeneEnrichment()
.UseMessageHandlers()));
}
}
3. Use Structured Logging in Your Handlers
Inject ILogger<T> into your message handlers and use structured logging:
using Benzene.Abstractions.MessageHandlers;
using Benzene.Abstractions.Results;
using Benzene.Core.MessageHandlers;
using Benzene.Results;
using Microsoft.Extensions.Logging;
[Message("order:create")]
public class CreateOrderHandler : IMessageHandler<CreateOrderRequest, CreateOrderResponse>
{
private readonly ILogger<CreateOrderHandler> _logger;
private readonly IOrderRepository _orderRepository;
public CreateOrderHandler(
ILogger<CreateOrderHandler> logger,
IOrderRepository orderRepository)
{
_logger = logger;
_orderRepository = orderRepository;
}
public async Task<IBenzeneResult<CreateOrderResponse>> HandleAsync(CreateOrderRequest request)
{
// Structured logging with named properties
_logger.LogInformation(
"Creating order for customer {CustomerId} with {ItemCount} items",
request.CustomerId,
request.Items.Count);
try
{
var order = await _orderRepository.CreateAsync(request);
_logger.LogInformation(
"Order {OrderId} created successfully for customer {CustomerId}",
order.Id,
request.CustomerId);
return BenzeneResult.Ok(new CreateOrderResponse { OrderId = order.Id });
}
catch (Exception ex)
{
_logger.LogError(
ex,
"Failed to create order for customer {CustomerId}",
request.CustomerId);
return BenzeneResult.ServiceUnavailable<CreateOrderResponse>("Order creation failed");
}
}
}
4. Query Logs in Application Insights
The structured properties are available in the customDimensions field. Query them with KQL:
traces
| where timestamp > ago(1h)
| where message contains "Creating order"
| extend CustomerId = customDimensions.CustomerId
| extend ItemCount = customDimensions.ItemCount
| extend Topic = customDimensions["benzene.topic"]
| extend CorrelationId = customDimensions["benzene.invocationId"]
| project timestamp, message, CustomerId, ItemCount, Topic, CorrelationId
Find errors for a specific customer:
traces
| where timestamp > ago(24h)
| where severityLevel >= 3 // Warning or higher
| extend CustomerId = customDimensions.CustomerId
| where CustomerId == "customer-123"
| project timestamp, message, severityLevel, customDimensions
Trace a request across multiple handlers:
traces
| where timestamp > ago(1h)
| extend CorrelationId = customDimensions["benzene.invocationId"]
| where CorrelationId == "correlation-id-from-request"
| order by timestamp asc
| project timestamp, message, customDimensions["benzene.topic"], customDimensions["benzene.handler"]
Testing
Local Testing
Run your function locally and check the console output. With Application Insights configured, logs should appear in both the console and Application Insights (with a slight delay for the latter).
Verify in Application Insights
- In Azure Portal, navigate to your Application Insights resource
- Go to Logs (under Monitoring)
- Run a query to see your recent traces:
traces
| where timestamp > ago(5m)
| order by timestamp desc
| take 20
Test Correlation
- Make a request and note the correlation ID from the response headers
- Query Application Insights with that correlation ID:
traces
| where customDimensions["benzene.invocationId"] == "your-correlation-id"
| order by timestamp asc
You should see all logs from that request in order.
Troubleshooting
Logs Not Appearing in Application Insights
Problem: Logs appear locally but not in Application Insights.
Solution:
- Verify the connection string is correct and accessible
- Check the
IncludeScopesoption is enabled (required forcustomDimensions) - Wait 2-5 minutes for logs to appear (Application Insights has a delay)
- Check the log level - Application Insights may filter out
Debuglevel logs
Missing Custom Properties
Problem: Logs appear but customDimensions doesn't include Benzene properties.
Solution:
- Ensure
UseBenzeneEnrichment()is in your middleware pipeline - Verify
IncludeScopes = truein Application Insights logger options - Check that
AddDiagnostics()is called in service configuration
High Log Volume / Cost
Problem: Too many logs are being sent, increasing Azure costs.
Solution:
- Increase minimum log level:
builder.SetMinimumLevel(LogLevel.Warning); - Add filters for noisy namespaces:
builder.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft", LogLevel.Error); - Use sampling in Application Insights:
services.AddApplicationInsightsTelemetry(options => { options.ConnectionString = configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]; options.EnableAdaptiveSampling = true; });
Variations
Using Serilog Instead
For richer structured logging, use Serilog with the Application Insights sink:
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.ApplicationInsights
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
var logger = new LoggerConfiguration()
.WriteTo.ApplicationInsights(
configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"],
TelemetryConverter.Traces)
.WriteTo.Console()
.CreateLogger();
services.AddLogging(builder =>
{
builder.AddSerilog(logger, dispose: true);
});
services.UsingBenzene(x => x
.AddDiagnostics()
.AddMessageHandlers(typeof(MyHandler).Assembly));
}
Adding Custom Telemetry Initializers
To add custom properties to all telemetry:
public class BenzeneTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.GlobalProperties["environment"] = "production";
telemetry.Context.GlobalProperties["application"] = "benzene-api";
}
}
// Register in ConfigureServices:
services.AddSingleton<ITelemetryInitializer, BenzeneTelemetryInitializer>();
Further Reading
- Monitoring & Diagnostics - Benzene's observability features
- Common Middleware -
UseBenzeneEnrichment()details - Microsoft.Extensions.Logging
- Application Insights for .NET
- Structured Logging with Serilog
- Distributed Tracing with OpenTelemetry