coder-csharp-logging
npx skills add https://github.com/ozerohax/assistagents --skill coder-csharp-logging
Agent 安装分布
Skill 文档
<skill_overview> Implement effective logging and observability for .NET applications Setting up logging infrastructure Writing log statements Configuring Serilog Adding health checks Implementing distributed tracing Microsoft Logging Documentation Serilog Wiki </skill_overview> <log_levels> <use_for>Detailed debugging, method entry/exit, variable states</use_for> Never enabled <use_for>Development info, SQL queries, API calls</use_for> Rarely enabled <use_for>Application flow, business events, significant actions</use_for> Default level Order {OrderId} created for customer {CustomerId} <use_for>Recoverable issues, retries, deprecated usage</use_for> Always enabled Retrying API call, attempt {Attempt} <use_for>Failures requiring investigation, exceptions</use_for> Always enabled Failed to process payment for order {OrderId} <use_for>System failures, data loss, immediate attention needed</use_for> Always enabled + alerts Database connection lost </log_levels> <structured_logging> Use message templates, NOT string interpolation
[LoggerMessage(
EventId = 2000,
Level = LogLevel.Warning,
Message = "Payment retry {Attempt} for order {OrderId}")]
public static partial void PaymentRetry(
this ILogger logger,
int attempt,
string orderId);
[LoggerMessage(
EventId = 3000,
Level = LogLevel.Error,
Message = "Failed to process order {OrderId}: {ErrorCode}")]
public static partial void OrderFailed(
this ILogger logger,
string orderId,
string errorCode);
} // Usage _logger.OrderCreated(order.Id, order.CustomerId); _logger.PaymentRetry(attempt, orderId); </loggermessage_source_generator> Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .MinimumLevel.Override(“Microsoft”, LogEventLevel.Warning) .MinimumLevel.Override(“Microsoft.Hosting.Lifetime”, LogEventLevel.Information)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithProperty("Application", "MyApp")
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/app-.txt",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30)
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
// ASP.NET Core integration builder.Host.UseSerilog(); // Request logging middleware app.UseSerilogRequestLogging(); <log_context> Add properties to all logs in a scope // In middleware – adds to all logs in request using (LogContext.PushProperty(“UserId”, userId)) using (LogContext.PushProperty(“CorrelationId”, correlationId)) { await next(context); } // All logs inside automatically include UserId and CorrelationId _logger.LogInformation(“Processing request”); // Output: UserId=123, CorrelationId=abc-def, Message=”Processing request” </log_context> <correlation_ids> public class CorrelationIdMiddleware { private readonly RequestDelegate _next;
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers["X-Correlation-ID"]
.FirstOrDefault() ?? Guid.NewGuid().ToString();
context.Response.Headers["X-Correlation-ID"] = correlationId;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await _next(context);
}
}
} // Register before other middleware app.UseMiddleware<CorrelationIdMiddleware>(); </correlation_ids> <exception_logging> Always pass exception object to logger Include context (IDs, operation name) Don’t duplicate exception message in log message try { await ProcessOrderAsync(order); } catch (Exception ex) { _logger.LogError(ex, “Failed to process order {OrderId} for customer {CustomerId}”, order.Id, order.CustomerId); throw; } // WRONG: Missing exception object _logger.LogError(“Error: ” + ex.Message); // WRONG: Duplicating exception message _logger.LogError(ex, “Error: {Message}”, ex.Message); </exception_logging> <sensitive_data> <never_log> Passwords (even hashed) Credit card numbers Social security numbers API keys and secrets JWT tokens Personal health information </never_log> // Redact sensitive data before logging _logger.LogInformation(“User {Email} logged in from {IpAddress}”, RedactEmail(user.Email), RedactIp(ipAddress)); private string RedactEmail(string email) { var parts = email.Split(‘@’); return parts[0][..Math.Min(3, parts[0].Length)] + “***@” + parts[1]; } </sensitive_data> <health_checks> builder.Services.AddHealthChecks() .AddCheck(“self”, () => HealthCheckResult.Healthy()) .AddSqlServer(connectionString, name: “database”, tags: new[] { “db” }) .AddRedis(redisConnection, name: “redis”, tags: new[] { “cache” }) .AddUrlGroup(new Uri(“https://api.example.com/health“), name: “external-api”); // Endpoints app.MapHealthChecks(“/health”); // All checks app.MapHealthChecks(“/health/ready”, new HealthCheckOptions { Predicate = check => check.Tags.Contains(“ready”) }); app.MapHealthChecks(“/health/live”, new HealthCheckOptions { Predicate = check => check.Tags.Contains(“live”) }); <custom_check> public class ExternalApiHealthCheck : IHealthCheck { private readonly HttpClient _http;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken ct = default)
{
try
{
var response = await _http.GetAsync("/health", ct);
return response.IsSuccessStatusCode
? HealthCheckResult.Healthy()
: HealthCheckResult.Degraded($"Status: {response.StatusCode}");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("API unavailable", ex);
}
}
} builder.Services.AddHealthChecks() .AddCheck<ExternalApiHealthCheck>(“external-api”); </custom_check> </health_checks> builder.Services.AddOpenTelemetry() .ConfigureResource(r => r.AddService(“MyApp”)) .WithTracing(tracing => { tracing.AddAspNetCoreInstrumentation(); tracing.AddHttpClientInstrumentation(); tracing.AddSqlClientInstrumentation(); tracing.AddSource(“MyApp”); }) .WithMetrics(metrics => { metrics.AddAspNetCoreInstrumentation(); metrics.AddRuntimeInstrumentation(); metrics.AddMeter(“MyApp”); }); // Export to Azure Application Insights builder.Services.AddOpenTelemetry() .UseAzureMonitor(options => { options.ConnectionString = config[“APPLICATIONINSIGHTS_CONNECTION_STRING”]; }); <custom_activity> using var activity = Activity.StartActivity(“ProcessOrder”); activity?.SetTag(“order.id”, orderId); activity?.SetTag(“customer.id”, customerId); try { await ProcessAsync(); activity?.SetStatus(ActivityStatusCode.Ok); } catch (Exception ex) { activity?.RecordException(ex); activity?.SetStatus(ActivityStatusCode.Error, ex.Message); throw; } </custom_activity> <custom_metrics> public class OrderMetrics { private readonly Counter<long> _ordersProcessed; private readonly Histogram<double> _processingTime;
public OrderMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Orders");
_ordersProcessed = meter.CreateCounter<long>(
"orders.processed",
description: "Number of orders processed");
_processingTime = meter.CreateHistogram<double>(
"order.processing.duration",
unit: "ms",
description: "Order processing time");
}
public void RecordOrderProcessed(string status) =>
_ordersProcessed.Add(1, new KeyValuePair<string, object?>("status", status));
public void RecordProcessingTime(double ms) =>
_processingTime.Record(ms);
} </custom_metrics>