![]() |
VOOZH | about |
dotnet add package SiLA2.Audit --version 10.2.4
NuGet\Install-Package SiLA2.Audit -Version 10.2.4
<PackageReference Include="SiLA2.Audit" Version="10.2.4" />
<PackageVersion Include="SiLA2.Audit" Version="10.2.4" />Directory.Packages.props
<PackageReference Include="SiLA2.Audit" />Project file
paket add SiLA2.Audit --version 10.2.4
#r "nuget: SiLA2.Audit, 10.2.4"
#:package SiLA2.Audit@10.2.4
#addin nuget:?package=SiLA2.Audit&version=10.2.4Install as a Cake Addin
#tool nuget:?package=SiLA2.Audit&version=10.2.4Install as a Cake Tool
| NuGet Package | SiLA2.Audit on NuGet.org |
| Repository | https://gitlab.com/SiLA2/sila_csharp |
| SiLA Standard | https://sila-standard.com |
| License | MIT |
Add comprehensive audit logging to your entire SiLA2 server with just 3 lines:
builder.Services.AddAuditServiceWithInterceptor();
builder.Services.AddGrpc(options => options.Interceptors.Add<AuditInterceptor>());
app.MapGrpcService<YourFeatureService>(); // All calls automatically audited!
That's it! Every command, property access, and error is now automatically logged. Zero code changes needed in your service implementations.
SiLA2.Audit is an optional module within the broader for laboratory automation. Understanding how this audit module fits into the overall architecture will help you integrate it effectively into your SiLA2 servers.
The SiLA2 C# implementation follows a Feature Definition Language (FDL) โ XSLT โ Proto โ C# pipeline architecture:
.sila.xml files.proto filesSiLA2.Audit sits at the gRPC layer, using interceptors to automatically capture all RPC calls without requiring changes to your feature implementations. This design means:
SiLA2.Audit is categorized as an Optional Module in the SiLA2 ecosystem, alongside:
SiLA2.Database.SQL - EntityFramework Core integrationSiLA2.Database.NoSQL - NoSQL database abstractionsSiLA2.IPC.NetMQ - Inter-process communicationSiLA2.AnIML - Scientific data format supportCore dependencies:
SiLA2.Core - Server implementation and domain modelsSiLA2.AspNetCore - ASP.NET Core integration and DI extensionsAdd SiLA2.Audit to your project when you need:
For comprehensive information about the SiLA2 C# implementation:
This document focuses on SiLA2.Audit-specific features. Refer to the main README for:
With the interceptor enabled, every gRPC call is automatically logged with:
โ
Command Started
- Timestamp (UTC, millisecond precision)
- Command identifier (e.g., "SetTemperature")
- Feature identifier (extracted from gRPC path)
- Request parameters (JSON serialized)
- Client metadata (IP, headers, user identity)
- Correlation ID (for tracking related events)
โ
Command Completed
- Response data (JSON serialized)
- Execution duration (milliseconds)
- Success status
โ
Command Failed (if error occurs)
- Error message
- Full stack trace
- Error type (RpcException, ValidationError, etc.)
- Execution duration up to failure
โ
Command Initiated
- Command execution UUID
- Initial parameters
โ
Progress Updates (automatically tracked)
- Execution status (Running, Completed, Failed)
- Progress percentage
- Estimated remaining time
โ
Result Retrieved
- Final response data
- Total execution time
โ
Subscription Started
- Property identifier
- Subscriber information
โ
Subscription Ended
- Duration
- Number of values emitted
โ
Error Event
- Exception type and message
- Stack trace
- Context (which command/property)
- Associated correlation ID
All of this happens automatically with zero code in your service methods!
You'd need to manually add audit code to every single RPC method:
public override async Task<Response> MyCommand(Parameters request, ServerCallContext context)
{
// โ Manual audit code in EVERY method
var correlationId = Guid.NewGuid().ToString();
var stopwatch = Stopwatch.StartNew();
await _audit.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandStarted,
// ... 20 lines of boilerplate ...
});
try
{
var response = DoWork(request);
stopwatch.Stop();
await _audit.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandCompleted,
// ... another 20 lines ...
});
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
await _audit.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandFailed,
// ... more boilerplate ...
});
throw;
}
}
Problems:
public override Task<Response> MyCommand(Parameters request, ServerCallContext context)
{
// โ
Just your business logic - auditing happens automatically!
return Task.FromResult(DoWork(request));
}
Benefits:
Code Savings: For a typical SiLA2 server with 20 commands:
SiLA2.Audit provides automatic, zero-code auditing for SiLA2 laboratory automation servers:
All audit events inherit from AuditEvent base class and include:
1. Install Package (when published)
dotnet add package SiLA2.Audit
2. Add to Program.cs
using SiLA2.Audit.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add auditing with automatic interception (in-memory storage for dev/testing)
builder.Services.AddAuditServiceWithInterceptor();
// Register gRPC with interceptor
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<AuditInterceptor>();
});
var app = builder.Build();
app.MapGrpcService<MyFeatureService>(); // โ
Automatically audited!
app.Run();
Done! Every RPC call is now automatically logged with:
No code changes needed in your service implementations!
For production environments, use SQL Server or PostgreSQL for robust, queryable audit logs:
using Microsoft.EntityFrameworkCore;
using SiLA2.Audit.Extensions;
using SiLA2.Audit.Storage.Database;
var builder = WebApplication.CreateBuilder(args);
// Production: SQL Server with automatic auditing
builder.Services.AddSqlAuditService(
auditOptions =>
{
auditOptions.MinimumSeverity = AuditSeverity.Information;
auditOptions.ExcludedEventTypes.Add(AuditEventType.PropertyRead); // Too verbose
auditOptions.EnablePiiMasking = true; // GDPR compliance
auditOptions.BufferSize = 500;
},
dbOptions =>
{
// SQL Server (recommended for enterprise)
dbOptions.UseSqlServer(
builder.Configuration.GetConnectionString("AuditDatabase"),
sqlOptions => sqlOptions.EnableRetryOnFailure()
);
});
// Register interceptor for automatic auditing
builder.Services.AddAuditInterceptor();
// Add gRPC with interceptor
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<AuditInterceptor>();
});
var app = builder.Build();
// Ensure database exists
using (var scope = app.Services.CreateScope())
{
var dbContextFactory = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<AuditDbContext>>();
using var dbContext = dbContextFactory.CreateDbContext();
dbContext.Database.Migrate(); // Apply migrations
}
app.MapGrpcService<MyFeatureService>(); // โ
All calls audited to SQL Server!
app.Run();
Connection Strings (appsettings.json):
{
"ConnectionStrings": {
"AuditDatabase": "Server=localhost;Database=SiLA2Audit;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
builder.Services.AddSqlAuditService(
auditOptions => { /* same as above */ },
dbOptions =>
{
// PostgreSQL
dbOptions.UseNpgsql(
builder.Configuration.GetConnectionString("AuditDatabase"),
pgOptions => pgOptions.EnableRetryOnFailure()
);
});
Connection String:
{
"ConnectionStrings": {
"AuditDatabase": "Host=localhost;Database=sila2audit;Username=postgres;Password=yourpassword"
}
}
Add NuGet Package:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
Perfect for edge devices or when database isn't available:
builder.Services.AddFileAuditServiceWithInterceptor(
auditOptions =>
{
auditOptions.MinimumSeverity = AuditSeverity.Information;
auditOptions.BufferSize = 100;
},
fileOptions =>
{
fileOptions.StoragePath = "/var/log/sila2/audit";
fileOptions.RotationPolicy = FileRotationPolicy.Daily;
fileOptions.RetentionDays = 90;
fileOptions.MaxFileSize = 100 * 1024 * 1024; // 100 MB
});
builder.Services.AddGrpc(options => options.Interceptors.Add<AuditInterceptor>());
Features:
For local development or when SQL Server/PostgreSQL aren't available:
builder.Services.AddSqlAuditService(
auditOptions => { /* config */ },
dbOptions =>
{
// SQLite - simple file-based database
dbOptions.UseSqlite("Data Source=audit.db");
});
builder.Services.AddAuditInterceptor();
builder.Services.AddGrpc(options => options.Interceptors.Add<AuditInterceptor>());
Note: SQLite is great for development but consider SQL Server or PostgreSQL for production due to:
builder.Services.AddAuditServiceWithInterceptor(options =>
{
// Only log warnings and errors
options.MinimumSeverity = AuditSeverity.Warning;
// Exclude verbose events
options.ExcludedEventTypes.Add(AuditEventType.PropertyRead);
options.ExcludedEventTypes.Add(AuditEventType.ObservablePropertyValueChanged);
// Only audit critical features
options.IncludedFeatures.Add("org.silastandard/core/SiLAService/v1");
options.IncludedFeatures.Add("com.mycompany/devices/CriticalDevice/v1");
});
builder.Services.AddSqlAuditService(
auditOptions =>
{
// Enable PII masking
auditOptions.EnablePiiMasking = true;
// Customize masked fields
auditOptions.PiiMaskingFields.Clear();
auditOptions.PiiMaskingFields.Add("UserId");
auditOptions.PiiMaskingFields.Add("RemoteEndpoint");
auditOptions.PiiMaskingFields.Add("SessionId");
},
dbOptions => dbOptions.UseSqlServer(connectionString));
Result: user@example.com โ us****om, 192.168.1.100 โ 19****00
Perfect for development and testing:
// Program.cs
using SiLA2.Audit.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register audit service with in-memory storage
builder.Services.AddAuditService(options =>
{
options.MinimumSeverity = AuditSeverity.Information;
options.BufferSize = 100;
options.FlushInterval = TimeSpan.FromSeconds(5);
});
var app = builder.Build();
app.Run();
For persistent audit logs:
// Program.cs
using SiLA2.Audit.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register audit service with file-based storage
builder.Services.AddFileAuditService(
auditOptions =>
{
auditOptions.MinimumSeverity = AuditSeverity.Information;
auditOptions.BufferSize = 100;
},
fileOptions =>
{
fileOptions.StoragePath = "/var/log/sila2/audit";
fileOptions.RotationPolicy = FileRotationPolicy.Daily;
fileOptions.RetentionDays = 90;
});
var app = builder.Build();
app.Run();
using SiLA2.Audit.Core;
using SiLA2.Audit.Models;
using System.Text.Json;
public class MyFeatureService : MyFeature.MyFeatureBase
{
private readonly Feature _siLA2Feature;
private readonly IAuditService _auditService;
public MyFeatureService(ISiLA2Server siLA2Server, IAuditService auditService)
{
_siLA2Feature = siLA2Server.ReadFeature(
Path.Combine("Features", "MyFeature-v1_0.sila.xml"));
_auditService = auditService;
}
public override async Task<Response> MyCommand(
Parameters request,
ServerCallContext context)
{
var correlationId = Guid.NewGuid().ToString();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Audit command start
await _auditService.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandStarted,
CommandIdentifier = "MyCommand",
ParametersJson = JsonSerializer.Serialize(request),
CorrelationId = correlationId,
FeatureIdentifier = _siLA2Feature.FeatureIdentifier,
Metadata = new AuditMetadata
{
ClientId = context.GetHttpContext()?.Connection.RemoteIpAddress?.ToString(),
UserId = context.GetHttpContext()?.User?.Identity?.Name
}
});
try
{
// Execute command logic
var response = DoWork(request);
stopwatch.Stop();
// Audit command completion
await _auditService.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandCompleted,
CommandIdentifier = "MyCommand",
ResponseJson = JsonSerializer.Serialize(response),
CorrelationId = correlationId,
FeatureIdentifier = _siLA2Feature.FeatureIdentifier,
DurationMs = stopwatch.ElapsedMilliseconds
});
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
// Audit command failure
await _auditService.RecordEventAsync(new CommandAuditEvent
{
EventType = AuditEventType.CommandFailed,
CommandIdentifier = "MyCommand",
ErrorMessage = ex.Message,
StackTrace = ex.StackTrace,
CorrelationId = correlationId,
FeatureIdentifier = _siLA2Feature.FeatureIdentifier,
DurationMs = stopwatch.ElapsedMilliseconds,
Severity = AuditSeverity.Error
});
throw;
}
}
}
builder.Services.AddAuditService(options =>
{
// Only audit warnings and errors
options.MinimumSeverity = AuditSeverity.Warning;
// Exclude verbose property reads
options.ExcludedEventTypes.Add(AuditEventType.PropertyRead);
options.ExcludedEventTypes.Add(AuditEventType.ObservablePropertyValueChanged);
// Only audit specific features
options.IncludedFeatures.Add("org.silastandard/core/SiLAService/v1");
options.IncludedFeatures.Add("de.cetoni/controllers/TemperatureController/v1");
});
builder.Services.AddAuditService(options =>
{
// Enable PII masking for GDPR compliance
options.EnablePiiMasking = true;
// Customize which fields to mask
options.PiiMaskingFields.Add("UserId");
options.PiiMaskingFields.Add("ClientId");
options.PiiMaskingFields.Add("RemoteEndpoint");
options.PiiMaskingFields.Add("SessionId");
});
// Inject IAuditEventStore to query events
public class AuditAnalysisService
{
private readonly IAuditEventStore _auditStore;
public AuditAnalysisService(IAuditEventStore auditStore)
{
_auditStore = auditStore;
}
public async Task<IEnumerable<AuditEvent>> GetWorkflowEvents(string correlationId)
{
return await _auditStore.GetEventsByCorrelationIdAsync(correlationId);
}
public async Task<IEnumerable<AuditEvent>> GetTodaysEvents()
{
var today = DateTime.UtcNow.Date;
return await _auditStore.GetEventsByTimeRangeAsync(
today,
today.AddDays(1));
}
public async Task<IEnumerable<AuditEvent>> GetFailedCommands()
{
return await _auditStore.GetEventsByTypeAsync(
AuditEventType.CommandFailed);
}
}
Choose the right storage backend for your needs:
| Feature | SQL Server | PostgreSQL | File-Based | SQLite | In-Memory |
|---|---|---|---|---|---|
| Production Ready | โ Best | โ Best | โ Good | โ ๏ธ Dev Only | โ Testing Only |
| Concurrent Writes | โ Excellent | โ Excellent | โ ๏ธ Limited | โ ๏ธ Limited | โ Excellent |
| Query Performance | โ Fast | โ Fast | โ ๏ธ Slow | โ ๏ธ Slow | โ Very Fast |
| Data Persistence | โ Yes | โ Yes | โ Yes | โ Yes | โ Lost on restart |
| Backup/Recovery | โ Enterprise | โ Enterprise | โ ๏ธ Manual | โ ๏ธ Manual | โ N/A |
| Scalability | โ High | โ High | โ ๏ธ Limited | โ Low | โ ๏ธ Memory Limited |
| Setup Complexity | Medium | Medium | โ Simple | โ Simple | โ None |
| External Dependencies | Database | Database | None | None | None |
| Best For | Enterprise | Enterprise | Edge Devices | Development | Unit Tests |
Recommendations:
1. Install EF Core Tools:
dotnet tool install --global dotnet-ef
2. Create Migration:
cd src/SiLA2.Audit
dotnet ef migrations add InitialCreate --context AuditDbContext --output-dir Migrations
3. Apply Migration:
// In Program.cs startup
using var scope = app.Services.CreateScope();
var dbContextFactory = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<AuditDbContext>>();
using var dbContext = dbContextFactory.CreateDbContext();
dbContext.Database.Migrate();
Or use EF CLI:
dotnet ef database update --context AuditDbContext
1. Add NuGet Package:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
2. Configure in Program.cs:
builder.Services.AddSqlAuditService(
auditOptions => { /* config */ },
dbOptions => dbOptions.UseNpgsql(connectionString)
);
3. Create Database (psql):
CREATE DATABASE sila2audit;
CREATE USER sila2user WITH PASSWORD 'yourpassword';
GRANT ALL PRIVILEGES ON DATABASE sila2audit TO sila2user;
4. Apply Migrations:
dotnet ef migrations add InitialCreate --context AuditDbContext
dotnet ef database update --context AuditDbContext
All configuration is done through AuditOptions:
builder.Services.AddAuditService(options =>
{
// Enable/disable auditing globally
options.Enabled = true;
// Minimum severity to record
options.MinimumSeverity = AuditSeverity.Information;
// Include only specific event types
options.IncludedEventTypes.Add(AuditEventType.CommandStarted);
options.IncludedEventTypes.Add(AuditEventType.CommandFailed);
// Exclude verbose event types
options.ExcludedEventTypes.Add(AuditEventType.PropertyRead);
// Include only specific features
options.IncludedFeatures.Add("org.silastandard/core/SiLAService/v1");
// Exclude features
options.ExcludedFeatures.Add("com.test/debug/DebugFeature/v1");
// Buffering settings
options.BufferSize = 100; // Events before auto-flush
options.FlushInterval = TimeSpan.FromSeconds(5); // Max time before flush
// Retention
options.RetentionPeriod = TimeSpan.FromDays(90); // Auto-delete after 90 days
// PII Masking
options.EnablePiiMasking = true;
options.PiiMaskingFields.Add("UserId");
options.PiiMaskingFields.Add("ClientId");
options.PiiMaskingFields.Add("RemoteEndpoint");
});
File Storage Options:
builder.Services.AddFileAuditStore(fileOptions =>
{
fileOptions.StoragePath = "/var/log/sila2/audit";
fileOptions.RotationPolicy = FileRotationPolicy.Daily; // or Hourly, Size
fileOptions.MaxFileSize = 100 * 1024 * 1024; // 100 MB (for Size rotation)
fileOptions.RetentionDays = 90;
fileOptions.EnableIndexing = true; // Fast queries
fileOptions.WriteBatchSize = 100;
});
โ Production Ready - The audit system is fully functional with:
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.