Skip to main content

.NET Core / C# Integration Guide

Overview

This guide explains how to integrate the AmpliServ Backend SDK with your .NET Core / C# application. The integration enables JSON logging, correlation ID propagation, and automatic log collection for the AmpliServ SDK.

Prerequisites

  • .NET Core 3.1 or higher (.NET 6/7/8 recommended)
  • Visual Studio 2022 or VS Code
  • AmpliServ SDK binary

Step 1: Create a New .NET Project

dotnet new webapi -n YourErpBackend
cd YourErpBackend

Step 2: Add Required NuGet Packages

# Serilog for structured JSON logging
dotnet add package Serilog
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Formatting.Compact

# For correlation ID
dotnet add package CorrelationId

Or add to your .csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="2.0.0" />
<PackageReference Include="CorrelationId" Version="3.0.1" />
</ItemGroup>
</Project>

Step 3: Configure Serilog JSON Logging

Create appsettings.json:

{
"Logging": {
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
}
},
"AllowedHosts": "*",
"Application": {
"Name": "YourErpBackend",
"Environment": "development"
}
}

Step 4: Configure Program.cs

using Serilog;
using Serilog.Formatting.Compact;
using CorrelationId;

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog for JSON logging
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", builder.Configuration["Application:Name"])
.Enrich.WithProperty("Environment", builder.Configuration["Application:Environment"])
.WriteTo.Console(new CompactJsonFormatter())
.WriteTo.File(
new CompactJsonFormatter(),
"app.log", // This is the file AmpliServ SDK will read
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 7
)
.CreateLogger();

builder.Host.UseSerilog();

// Add services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Add Correlation ID support
builder.Services.AddCorrelationId(options =>
{
options.Header = "X-Correlation-ID";
options.IncludeInResponse = true;
options.UpdateTraceIdentifier = true;
});

var app = builder.Build();

// Configure pipeline
app.UseCorrelationId(); // Must be early in pipeline

// Custom correlation ID middleware to add to logging context
app.Use(async (context, next) =>
{
var correlationId = context.TraceIdentifier;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogInformation(
"Incoming {Method} {Path} - CorrelationId: {CorrelationId}",
context.Request.Method,
context.Request.Path,
correlationId
);

await next();
}
});

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

Step 5: Create Global Exception Handler (Captures Full Stack Traces)

Create Middlewares/GlobalExceptionMiddleware.cs:

using System.Net;
using System.Text.Json;
using Serilog.Context;

namespace YourErpBackend.Middlewares;

public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;

public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}

public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}

private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
// This captures the FULL stack trace
_logger.LogError(exception, "An unhandled exception occurred: {Message}", exception.Message);

var response = new
{
message = exception.Message,
correlationId = context.TraceIdentifier,
timestamp = DateTime.UtcNow,
statusCode = (int)HttpStatusCode.InternalServerError
};

context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

await context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
}

Register the middleware in Program.cs:

app.UseMiddleware<GlobalExceptionMiddleware>();

Step 6: Create Test Controller

Create Controllers/TestController.cs:

using Microsoft.AspNetCore.Mvc;
using Serilog.Context;

namespace YourErpBackend.Controllers;

[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
private readonly ILogger<TestController> _logger;

public TestController(ILogger<TestController> logger)
{
_logger = logger;
}

[HttpGet("info")]
public IActionResult Info()
{
var correlationId = HttpContext.TraceIdentifier;

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogInformation("Info endpoint called - This is a normal log");
return Ok(new { message = "Info logged successfully", correlationId });
}
}

[HttpGet("warn")]
public IActionResult Warn()
{
var correlationId = HttpContext.TraceIdentifier;

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogWarning("Warning endpoint called - This is a warning log");
return Ok(new { message = "Warning logged", correlationId });
}
}

[HttpGet("error")]
public IActionResult Error()
{
var correlationId = HttpContext.TraceIdentifier;

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogInformation("Test error endpoint called");

try
{
SimulateDeepError();
}
catch (Exception ex)
{
// Full stack trace captured here
_logger.LogError(ex, "Test error occurred: {Message}", ex.Message);

return StatusCode(500, new
{
message = "Error triggered",
correlationId,
error = ex.Message
});
}

return Ok(new { message = "No error", correlationId });
}
}

[HttpGet("dashboard")]
public IActionResult Dashboard()
{
var correlationId = HttpContext.TraceIdentifier;

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogInformation("Dashboard endpoint called");

var dashboard = new
{
totalProducts = 100,
totalOrders = 50,
totalCustomers = 75,
totalRevenue = 250000,
correlationId
};

_logger.LogDebug("Dashboard data: {@Dashboard}", dashboard);

return Ok(dashboard);
}
}

private void SimulateDeepError()
{
Level1();
}

private void Level1()
{
Level2();
}

private void Level2()
{
Level3();
}

private void Level3()
{
throw new Exception("This is a test error with deep stack trace from .NET!");
}
}

Step 7: Create Business Service with Error Simulation

Create Services/BusinessService.cs:

using Serilog.Context;

namespace YourErpBackend.Services;

public interface IBusinessService
{
Task<decimal> CalculateOrderTotal(int orderId);
}

public class BusinessService : IBusinessService
{
private readonly ILogger<BusinessService> _logger;

public BusinessService(ILogger<BusinessService> logger)
{
_logger = logger;
}

public async Task<decimal> CalculateOrderTotal(int orderId)
{
var correlationId = CorrelationContext.GetCorrelationId();

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogInformation("Calculating total for order {OrderId}", orderId);

// Simulate business logic error
if (orderId <= 0)
{
var ex = new ArgumentException($"Invalid order ID: {orderId}");
_logger.LogError(ex, "Business validation failed");
throw ex;
}

await Task.Delay(100); // Simulate async operation
return 250.50m;
}
}
}

// Helper class for correlation context
public static class CorrelationContext
{
private static readonly AsyncLocal<string> _currentCorrelationId = new();

public static string GetCorrelationId()
{
return _currentCorrelationId.Value ?? "unknown";
}

public static void SetCorrelationId(string correlationId)
{
_currentCorrelationId.Value = correlationId;
}
}

Register the service in Program.cs:

builder.Services.AddScoped<IBusinessService, BusinessService>();

Step 8: Create Auth Controller (JWT Example)

Create Controllers/AuthController.cs:

using Microsoft.AspNetCore.Mvc;
using Serilog.Context;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace YourErpBackend.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IConfiguration _configuration;

public AuthController(ILogger<AuthController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest request)
{
var correlationId = HttpContext.TraceIdentifier;

using (LogContext.PushProperty("CorrelationId", correlationId))
{
_logger.LogInformation("Login attempt for user: {Username}", request.Username);

// Simple validation
if (request.Username == "admin" && request.Password == "admin123")
{
var token = GenerateJwtToken(request.Username);

_logger.LogInformation("User logged in successfully: {Username}", request.Username);

return Ok(new
{
token,
user = new { username = request.Username },
correlationId
});
}

_logger.LogWarning("Authentication failed: Invalid credentials for user: {Username}", request.Username);

return Unauthorized(new { message = "Invalid username or password", correlationId });
}
}

private string GenerateJwtToken(string username)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-super-secret-key-here-minimum-32-chars-long"));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var claims = new[]
{
new Claim(ClaimTypes.Name, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};

var token = new JwtSecurityToken(
issuer: "YourErpBackend",
audience: "YourErpBackend",
claims: claims,
expires: DateTime.UtcNow.AddHours(24),
signingCredentials: credentials
);

return new JwtSecurityTokenHandler().WriteToken(token);
}
}

public class LoginRequest
{
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
}

Step 9: Configure AmpliServ SDK

Create run-agent.bat (Windows):

@echo off
set AMPLISERV_API_KEY=your-api-key-here
set AMPLISERV_COLLECTOR_MODE=file
set AMPLISERV_ENDPOINT=http://dev-backend.ampliserv.com/api/ingestion/v1/ingest
set AMPLISERV_SERVICE_NAME=ingestion-service
set AMPLISERV_ENV=development
set AMPLISERV_LOG_PATH=C:\path\to\your\project\app.log
set AMPLISERV_BATCH_SIZE=50
set AMPLISERV_FLUSH_INTERVAL_SEC=5
set AMPLISERV_INCIDENT_ENABLED=true

echo Starting AmpliServ Agent...
ampliserv-agent.exe

Create run-agent.sh (Linux/Mac):

#!/bin/bash
export AMPLISERV_API_KEY="your-api-key-here"
export AMPLISERV_COLLECTOR_MODE="file"
export AMPLISERV_ENDPOINT="http://dev-backend.ampliserv.com/api/ingestion/v1/ingest"
export AMPLISERV_SERVICE_NAME="ingestion-service"
export AMPLISERV_ENV="development"
export AMPLISERV_LOG_PATH="/path/to/your/project/app.log"
export AMPLISERV_BATCH_SIZE="50"
export AMPLISERV_FLUSH_INTERVAL_SEC="5"
export AMPLISERV_INCIDENT_ENABLED="true"

echo "Starting AmpliServ Agent..."
./ampliserv-agent

Step 10: Build and Run

Build the application:

dotnet build

Run the application:

dotnet run

In a separate terminal, start the AmpliServ SDK:

# Windows
run-agent.bat

# Linux/Mac
chmod +x run-agent.sh
./run-agent.sh

Step 11: Test the Integration

# Test info endpoint
curl http://localhost:5000/api/Test/info

# Test warning endpoint
curl http://localhost:5000/api/Test/warn

# Test error endpoint (generates stack trace)
curl http://localhost:5000/api/Test/error

# Test dashboard
curl http://localhost:5000/api/Test/dashboard

# Test login
curl -X POST http://localhost:5000/api/Auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'

Sample JSON Log Output

{
"@t": "2026-03-23T10:30:45.1234567Z",
"@mt": "Test error occurred: {Message}",
"@l": "Error",
"@x": "System.Exception: This is a test error with deep stack trace from .NET!\n at YourErpBackend.Controllers.TestController.Level3() in /app/Controllers/TestController.cs:line 85\n at YourErpBackend.Controllers.TestController.Level2() in /app/Controllers/TestController.cs:line 79\n at YourErpBackend.Controllers.TestController.Level1() in /app/Controllers/TestController.cs:line 73\n at YourErpBackend.Controllers.TestController.SimulateDeepError() in /app/Controllers/TestController.cs:line 67\n at YourErpBackend.Controllers.TestController.Error() in /app/Controllers/TestController.cs:line 48",
"Message": "This is a test error with deep stack trace from .NET!",
"CorrelationId": "a1270278-7ef9-4e59-803e-e9862c257d73",
"Application": "YourErpBackend",
"Environment": "development"
}

Docker Deployment

Create Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["YourErpBackend.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet build -c Release -o /app/build

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "YourErpBackend.dll"]

Docker Compose:

version: '3.8'
services:
app:
build: .
ports:
- "5000:80"
volumes:
- ./app.log:/app/app.log
environment:
- ASPNETCORE_ENVIRONMENT=Production

ampliserv-agent:
image: ampliserv/agent:latest
volumes:
- ./app.log:/app/app.log:ro
environment:
- AMPLISERV_API_KEY=${AMPLISERV_API_KEY}
- AMPLISERV_COLLECTOR_MODE=file
- AMPLISERV_LOG_PATH=/app/app.log
- AMPLISERV_SERVICE_NAME=dotnet-erp-backend
- AMPLISERV_ENV=production

Verification Checklist

  • .NET application starts without errors
  • app.log file is created with JSON content
  • Logs use CompactJsonFormatter format
  • AmpliServ SDK shows "Starting AmpliServ Agent"
  • SDK displays "Mode: file"
  • Test endpoints produce logs
  • Full stack traces are visible in logs
  • Correlation ID is present in all logs

Common Issues

IssueSolution
Log file not createdCheck write permissions in application directory
JSON parsing errorsEnsure CompactJsonFormatter is used
SDK can't read fileRun SDK with same user as .NET app
Correlation ID missingVerify UseCorrelationId() is called early
Stack traces truncatedSerilog automatically includes full stack traces

Performance Optimization

For production, configure rolling file sizes:

.WriteTo.File(
new CompactJsonFormatter(),
"app.log",
rollingInterval: RollingInterval.Day,
fileSizeLimitBytes: 10485760, // 10MB
retainedFileCountLimit: 10,
rollOnFileSizeLimit: true
)