Add Sentry (#19)

* add sentry

* add sentry

* better log web3proxy

* Add managing and worker on sentry

* better log web3proxy
This commit is contained in:
Oda
2025-04-22 20:49:02 +02:00
committed by GitHub
parent df5f7185c8
commit 42a4cafd8d
40 changed files with 2959 additions and 146 deletions

View File

@@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Mvc;
using Sentry;
using System;
namespace Managing.Api.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class SentryTestController : ControllerBase
{
private readonly ILogger<SentryTestController> _logger;
public SentryTestController(ILogger<SentryTestController> logger)
{
_logger = logger;
}
[HttpGet("test-exception")]
public IActionResult TestException()
{
try
{
throw new Exception($"Test exception from SentryTestController - {DateTime.Now}");
}
catch (Exception ex)
{
// Add breadcrumbs for context
SentrySdk.AddBreadcrumb("About to capture test exception", "test");
// Add context to the error
SentrySdk.ConfigureScope(scope =>
{
scope.SetTag("test_type", "manual_exception");
scope.SetExtra("timestamp", DateTime.Now);
});
// Log to both Serilog and Sentry
_logger.LogError(ex, "Test exception captured in SentryTestController");
// Explicitly capture exception
SentrySdk.CaptureException(ex);
return Ok(new
{
message = "Exception manually captured and sent to Sentry",
exceptionMessage = ex.Message,
timestamp = DateTime.Now
});
}
}
[HttpGet("throw-exception")]
public IActionResult ThrowException()
{
_logger.LogInformation("About to throw an uncaught exception");
// This should be automatically captured by Sentry middleware
throw new InvalidOperationException($"Uncaught exception from ThrowException endpoint - {DateTime.Now}");
}
[HttpGet("test-message")]
public IActionResult TestMessage()
{
// Send a simple message to Sentry
SentrySdk.CaptureMessage("Test message from Managing API", SentryLevel.Info);
return Ok(new
{
message = "Test message sent to Sentry",
timestamp = DateTime.Now
});
}
}
}

View File

@@ -0,0 +1,79 @@
namespace Managing.Api.Exceptions;
/// <summary>
/// Exception thrown when validation fails (maps to 400 Bad Request)
/// </summary>
public class ValidationException : Exception
{
public ValidationException(string message) : base(message)
{
}
public ValidationException(string message, Exception innerException) : base(message, innerException)
{
}
}
/// <summary>
/// Exception thrown when a resource is not found (maps to 404 Not Found)
/// </summary>
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message)
{
}
public NotFoundException(string resourceType, string identifier)
: base($"{resourceType} with identifier '{identifier}' was not found.")
{
}
}
/// <summary>
/// Exception thrown when the user does not have permission (maps to 403 Forbidden)
/// </summary>
public class ForbiddenException : Exception
{
public ForbiddenException(string message) : base(message)
{
}
public ForbiddenException() : base("You do not have permission to access this resource.")
{
}
}
/// <summary>
/// Exception thrown when there is a conflict with the current state (maps to 409 Conflict)
/// </summary>
public class ConflictException : Exception
{
public ConflictException(string message) : base(message)
{
}
}
/// <summary>
/// Exception thrown when a rate limit is exceeded (maps to 429 Too Many Requests)
/// </summary>
public class RateLimitExceededException : Exception
{
public RateLimitExceededException(string message) : base(message)
{
}
public RateLimitExceededException() : base("Rate limit exceeded. Please try again later.")
{
}
}
/// <summary>
/// Exception thrown when an external service is unavailable (maps to 503 Service Unavailable)
/// </summary>
public class ServiceUnavailableException : Exception
{
public ServiceUnavailableException(string serviceName)
: base($"The service '{serviceName}' is currently unavailable. Please try again later.")
{
}
}

View File

@@ -1,15 +1,20 @@
using System.Net;
using System.Text.Json;
using Sentry;
namespace Managing.Api.Exceptions;
public class GlobalErrorHandlingMiddleware
{
private readonly RequestDelegate _next;
public GlobalErrorHandlingMiddleware(RequestDelegate next)
private readonly ILogger<GlobalErrorHandlingMiddleware> _logger;
public GlobalErrorHandlingMiddleware(RequestDelegate next, ILogger<GlobalErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
@@ -21,42 +26,162 @@ public class GlobalErrorHandlingMiddleware
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode status;
var exceptionType = exception.GetType();
string errorMessage;
if (exceptionType == typeof(Exception))
// Determine the appropriate status code based on exception type
status = exception switch
{
status = HttpStatusCode.InternalServerError;
}
else if (exceptionType == typeof(NotImplementedException))
// 400 Bad Request
ArgumentException => HttpStatusCode.BadRequest,
ValidationException => HttpStatusCode.BadRequest,
FormatException => HttpStatusCode.BadRequest,
InvalidOperationException => HttpStatusCode.BadRequest,
// 401 Unauthorized
UnauthorizedAccessException => HttpStatusCode.Unauthorized,
// 403 Forbidden
ForbiddenException => HttpStatusCode.Forbidden,
// 404 Not Found
KeyNotFoundException => HttpStatusCode.NotFound,
FileNotFoundException => HttpStatusCode.NotFound,
DirectoryNotFoundException => HttpStatusCode.NotFound,
NotFoundException => HttpStatusCode.NotFound,
// 408 Request Timeout
TimeoutException => HttpStatusCode.RequestTimeout,
// 409 Conflict
ConflictException => HttpStatusCode.Conflict,
// 429 Too Many Requests
RateLimitExceededException => HttpStatusCode.TooManyRequests,
// 501 Not Implemented
NotImplementedException => HttpStatusCode.NotImplemented,
// 503 Service Unavailable
ServiceUnavailableException => HttpStatusCode.ServiceUnavailable,
// 500 Internal Server Error (default)
_ => HttpStatusCode.InternalServerError
};
// Log the error with appropriate severity based on status code
var isServerError = (int)status >= 500;
if (isServerError)
{
status = HttpStatusCode.NotImplemented;
}
else if (exceptionType == typeof(UnauthorizedAccessException))
{
status = HttpStatusCode.Unauthorized;
}
else if (exceptionType == typeof(ArgumentException))
{
status = HttpStatusCode.Unauthorized;
}
else if (exceptionType == typeof(KeyNotFoundException))
{
status = HttpStatusCode.Unauthorized;
_logger.LogError(exception, "Server Error: {StatusCode} on {Path}", (int)status, context.Request.Path);
}
else
{
status = HttpStatusCode.InternalServerError;
_logger.LogWarning(exception, "Client Error: {StatusCode} on {Path}", (int)status, context.Request.Path);
}
var message = exception.Message;
var stackTrace = exception.StackTrace;
var exceptionResult = JsonSerializer.Serialize(new { error = message, stackTrace });
// Capture exception in Sentry with request context
var sentryId = SentrySdk.CaptureException(exception, scope =>
{
// Add HTTP request information
scope.SetTag("http.method", context.Request.Method);
scope.SetTag("http.url", context.Request.Path);
// Add request details
scope.SetExtra("query_string", context.Request.QueryString.ToString());
// Add custom tags to help with filtering
scope.SetTag("error_type", exception.GetType().Name);
scope.SetTag("status_code", ((int)status).ToString());
scope.SetTag("host", context.Request.Host.ToString());
scope.SetTag("path", context.Request.Path.ToString());
// Add any correlation IDs if available
if (context.Request.Headers.TryGetValue("X-Correlation-ID", out var correlationId))
{
scope.SetTag("correlation_id", correlationId.ToString());
}
// Additional context based on exception type
if (exception is ValidationException)
{
scope.SetTag("error_category", "validation");
}
else if (exception is NotFoundException)
{
scope.SetTag("error_category", "not_found");
}
// Add additional context from exception data if available
foreach (var key in exception.Data.Keys)
{
if (key is string keyStr && exception.Data[key] != null)
{
scope.SetExtra(keyStr, exception.Data[key].ToString());
}
}
// Add breadcrumb for the request
scope.AddBreadcrumb(
message: $"Request to {context.Request.Path}",
category: "request",
level: BreadcrumbLevel.Info
);
});
// Use a more user-friendly error message in production
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
{
// For 5xx errors, use a generic message
if (isServerError)
{
errorMessage = "An unexpected error occurred. Our team has been notified.";
}
else
{
// For 4xx errors, keep the original message since it's likely helpful for the user
errorMessage = exception.Message;
}
}
else
{
errorMessage = exception.Message;
}
// Create the error response
var errorResponse = new ErrorResponse
{
StatusCode = (int)status,
Message = errorMessage,
TraceId = sentryId.ToString()
};
// Only include stack trace in development environment
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production")
{
errorResponse.StackTrace = exception.StackTrace;
}
var result = JsonSerializer.Serialize(errorResponse, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)status;
return context.Response.WriteAsync(exceptionResult);
return context.Response.WriteAsync(result);
}
// Custom error response class
private class ErrorResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public string TraceId { get; set; }
public string StackTrace { get; set; }
}
}

View File

@@ -0,0 +1,109 @@
using Sentry;
namespace Managing.Api.Exceptions;
/// <summary>
/// Utility class for capturing errors with Sentry across the application
/// </summary>
public static class SentryErrorCapture
{
/// <summary>
/// Captures an exception in Sentry with additional context
/// </summary>
/// <param name="exception">The exception to capture</param>
/// <param name="contextName">A descriptive name for where the error occurred</param>
/// <param name="extraData">Optional dictionary of additional data to include</param>
/// <returns>The Sentry event ID</returns>
public static SentryId CaptureException(Exception exception, string contextName, IDictionary<string, object> extraData = null)
{
return SentrySdk.CaptureException(exception, scope =>
{
// Add context information
scope.SetTag("context", contextName);
scope.SetTag("error_type", exception.GetType().Name);
// Add any extra data provided
if (extraData != null)
{
foreach (var kvp in extraData)
{
scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null");
}
}
// Add extra info from the exception's Data dictionary if available
foreach (var key in exception.Data.Keys)
{
if (key is string keyStr && exception.Data[key] != null)
{
scope.SetExtra($"exception_data_{keyStr}", exception.Data[key].ToString());
}
}
// Add a breadcrumb for context
scope.AddBreadcrumb(
message: $"Exception in {contextName}",
category: "error",
level: BreadcrumbLevel.Error
);
});
}
/// <summary>
/// Enriches an exception with additional context data before throwing
/// </summary>
/// <param name="exception">The exception to enrich</param>
/// <param name="contextData">Dictionary of context data to add</param>
/// <returns>The enriched exception for chaining</returns>
public static Exception EnrichException(Exception exception, IDictionary<string, object> contextData)
{
if (contextData != null)
{
foreach (var item in contextData)
{
exception.Data[item.Key] = item.Value;
}
}
return exception;
}
/// <summary>
/// Captures a message in Sentry with additional context
/// </summary>
/// <param name="message">The message to capture</param>
/// <param name="level">The severity level</param>
/// <param name="contextName">A descriptive name for where the message originated</param>
/// <param name="extraData">Optional dictionary of additional data to include</param>
/// <returns>The Sentry event ID</returns>
public static SentryId CaptureMessage(string message, SentryLevel level, string contextName, IDictionary<string, object> extraData = null)
{
// First capture the message with the specified level
var id = SentrySdk.CaptureMessage(message, level);
// Then add context via a scope
SentrySdk.ConfigureScope(scope =>
{
// Add context information
scope.SetTag("context", contextName);
// Add any extra data provided
if (extraData != null)
{
foreach (var kvp in extraData)
{
scope.SetExtra(kvp.Key, kvp.Value?.ToString() ?? "null");
}
}
// Add a breadcrumb for context
scope.AddBreadcrumb(
message: $"Message from {contextName}",
category: "message",
level: BreadcrumbLevel.Info
);
});
return id;
}
}

View File

@@ -18,6 +18,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.7" />
<PackageReference Include="Sentry.AspNetCore" Version="5.5.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
@@ -34,6 +35,7 @@
<ItemGroup>
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj" />
<ProjectReference Include="..\Managing.Aspire.ServiceDefaults\Managing.Aspire.ServiceDefaults.csproj" />
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,90 @@
using Sentry;
using System.Text;
namespace Managing.Api.Middleware
{
public class SentryDiagnosticsMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<SentryDiagnosticsMiddleware> _logger;
public SentryDiagnosticsMiddleware(RequestDelegate next, ILogger<SentryDiagnosticsMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Only activate for the /api/sentry-diagnostics endpoint
if (context.Request.Path.StartsWithSegments("/api/sentry-diagnostics"))
{
await HandleDiagnosticsRequest(context);
return;
}
await _next(context);
}
private async Task HandleDiagnosticsRequest(HttpContext context)
{
var response = new StringBuilder();
response.AppendLine("Sentry Diagnostics Report");
response.AppendLine("========================");
response.AppendLine($"Timestamp: {DateTime.Now}");
response.AppendLine();
// Check if Sentry is initialized
response.AppendLine("## Sentry SDK Status");
response.AppendLine($"Sentry Enabled: {SentrySdk.IsEnabled}");
response.AppendLine($"Application Environment: {Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}");
response.AppendLine();
// Send a test event
response.AppendLine("## Test Event");
try
{
var id = SentrySdk.CaptureMessage($"Diagnostics test from {context.Request.Host} at {DateTime.Now}", SentryLevel.Info);
response.AppendLine($"Test Event ID: {id}");
response.AppendLine("Test event was sent to Sentry. Check your Sentry dashboard to confirm it was received.");
// Try to send an exception too
try
{
throw new Exception("Test exception from diagnostics middleware");
}
catch (Exception ex)
{
var exceptionId = SentrySdk.CaptureException(ex);
response.AppendLine($"Test Exception ID: {exceptionId}");
}
}
catch (Exception ex)
{
response.AppendLine($"Error sending test event: {ex.Message}");
response.AppendLine(ex.StackTrace);
}
response.AppendLine();
response.AppendLine("## Connectivity Check");
response.AppendLine("If events are not appearing in Sentry, check the following:");
response.AppendLine("1. Verify your DSN is correct in appsettings.json");
response.AppendLine("2. Ensure your network allows outbound HTTPS connections to sentry.apps.managing.live");
response.AppendLine("3. Check Sentry server logs for any ingestion issues");
response.AppendLine("4. Verify your Sentry project is correctly configured to receive events");
// Return the diagnostic information
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(response.ToString());
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SentryDiagnosticsMiddlewareExtensions
{
public static IApplicationBuilder UseSentryDiagnostics(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SentryDiagnosticsMiddleware>();
}
}
}

View File

@@ -1,12 +1,12 @@
using System.Text;
using System.Text.Json.Serialization;
using Managing.Api.Authorization;
using Managing.Api.Exceptions;
using Managing.Api.Filters;
using Managing.Api.Workers;
using Managing.Application.Hubs;
using Managing.Bootstrap;
using Managing.Common;
using Managing.Core.Middleawares;
using Managing.Infrastructure.Databases.InfluxDb.Models;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using Managing.Infrastructure.Evm.Models.Privy;
@@ -26,10 +26,46 @@ using Microsoft.Extensions.Hosting;
using HealthChecks.UI.Client;
using OpenApiSecurityRequirement = Microsoft.OpenApi.Models.OpenApiSecurityRequirement;
using OpenApiSecurityScheme = NSwag.OpenApiSecurityScheme;
using Sentry;
// Builder
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json")
.AddJsonFile($"config.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddUserSecrets<Program>();
SentrySdk.Init(options =>
{
// A Sentry Data Source Name (DSN) is required.
// See https://docs.sentry.io/concepts/key-terms/dsn-explainer/
// You can set it in the SENTRY_DSN environment variable, or you can set it in code here.
options.Dsn = builder.Configuration["Sentry:Dsn"];
// When debug is enabled, the Sentry client will emit detailed debugging information to the console.
// This might be helpful, or might interfere with the normal operation of your application.
// We enable it here for demonstration purposes when first trying Sentry.
// You shouldn't do this in your applications unless you're troubleshooting issues with Sentry.
options.Debug = false;
// Adds request URL and headers, IP and name for users, etc.
options.SendDefaultPii = true;
// This option is recommended. It enables Sentry's "Release Health" feature.
options.AutoSessionTracking = true;
// Enabling this option is recommended for client applications only. It ensures all threads use the same global scope.
options.IsGlobalModeEnabled = false;
// Example sample rate for your transactions: captures 10% of transactions
options.TracesSampleRate = 0.1;
});
// Add Service Defaults - using extension methods directly
builder.Services.AddServiceDiscovery();
builder.Services.AddHealthChecks()
@@ -45,15 +81,6 @@ builder.Services.AddHealthChecks()
.AddUrlGroup(new Uri($"{influxUrl}/health"), name: "influxdb", tags: ["database"])
.AddUrlGroup(new Uri($"{web3ProxyUrl}/health"), name: "web3proxy", tags: ["api"]);
builder.Configuration.SetBasePath(AppContext.BaseDirectory);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json")
.AddJsonFile($"config.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true);
builder.Configuration.AddEnvironmentVariables();
builder.Configuration.AddUserSecrets<Program>();
builder.Host.UseSerilog((hostBuilder, loggerConfiguration) =>
{
var envName = builder.Environment.EnvironmentName.ToLower().Replace(".", "-");
@@ -178,7 +205,11 @@ app.UseSwaggerUI(c =>
app.UseCors("CorsPolicy");
app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware));
// Add Sentry diagnostics middleware (now using shared version)
app.UseSentryDiagnostics();
// Using shared GlobalErrorHandlingMiddleware from core project
app.UseMiddleware<GlobalErrorHandlingMiddleware>();
app.UseMiddleware<JwtMiddleware>();
@@ -196,12 +227,12 @@ app.UseEndpoints(endpoints =>
endpoints.MapHub<BotHub>("/bothub");
endpoints.MapHub<BacktestHub>("/backtesthub");
endpoints.MapHub<CandleHub>("/candlehub");
endpoints.MapHealthChecks("/health", new HealthCheckOptions
endpoints.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
endpoints.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),

View File

@@ -0,0 +1,90 @@
# Error Handling in Managing API
This document describes the centralized error handling approach used in the Managing API to ensure consistent error responses and logging, with Sentry integration for error monitoring.
## Architecture
The error handling architecture consists of:
1. **Global Error Handling Middleware**: Captures all unhandled exceptions and formats consistent responses
2. **Sentry Integration**: Sends detailed error information to Sentry for monitoring and analysis
3. **MediatR Pipeline**: Adds request context to exceptions before they reach the global middleware
4. **SentryErrorCapture Utility**: Provides methods for manually capturing errors with context
## Global Error Handling Middleware
The `GlobalErrorHandlingMiddleware` is registered in `Program.cs` and handles all unhandled exceptions:
```csharp
app.UseMiddleware(typeof(GlobalErrorHandlingMiddleware));
```
When an exception occurs, the middleware:
1. Determines the appropriate HTTP status code based on exception type
2. Logs the error with request details
3. Captures the exception in Sentry with appropriate context
4. Returns a standardized JSON error response to the client
## Sentry Integration
Sentry is integrated in two ways:
1. **Global Configuration**: Set up in `Program.cs` with environment-specific settings
2. **Error Capture**: In the global middleware and utility methods
The captured data includes:
- HTTP request details (path, method, query strings)
- Exception details (type, message, stack trace)
- Additional context from exception data
- Tags for better categorization and filtering
## MediatR Exception Handling
The `UnhandledExceptionBehaviour` in the MediatR pipeline:
1. Catches exceptions in request handlers
2. Logs the error with request details
3. Adds MediatR-specific context to the exception's Data dictionary
4. Rethrows the exception to be caught by the global middleware
This approach allows the global middleware to have full context about where the exception occurred without duplicating reporting to Sentry.
## Manual Error Reporting
For manually reporting errors to Sentry, use the `SentryErrorCapture` utility:
```csharp
// Capture an exception with context
SentryErrorCapture.CaptureException(ex, "MyService", new Dictionary<string, object> {
{ "userId", user.Id },
{ "operation", "ProcessImport" }
});
// Enrich an exception before throwing
throw SentryErrorCapture.EnrichException(new ValidationException("Invalid data"),
new Dictionary<string, object> {
{ "validationErrors", errors }
});
```
## Error Response Format
The standard error response format is:
```json
{
"statusCode": 400,
"message": "The error message",
"traceId": "sentry-event-id",
"stackTrace": "Only included in non-production environments"
}
```
## Best Practices
1. **Don't catch exceptions unless necessary**: Let the global middleware handle most exceptions
2. **Add context to exceptions**: Use the Data dictionary to add context that will be captured
3. **Use appropriate exception types**: Different exception types map to different HTTP status codes
4. **Avoid sensitive data**: Never include sensitive data (passwords, tokens) in error messages or context

View File

@@ -0,0 +1,98 @@
# Sentry Integration for Managing API
This document describes how Sentry is integrated into the Managing API for error monitoring and performance tracking.
## Setup
Sentry has been integrated into the Managing API using the official `Sentry.AspNetCore` package (version 5.5.1). The integration follows the recommended approach using `WebHost.UseSentry()` in Program.cs:
```csharp
// In Program.cs
builder.WebHost.UseSentry();
```
This approach automatically picks up Sentry configuration from appsettings.json and environment variables. It's the official recommended method for ASP.NET Core applications.
The integration captures:
- Unhandled exceptions
- HTTP request information
- Performance metrics
- Custom errors and events
- User information and PII (when SendDefaultPii is enabled)
## Configuration
Sentry is configured through `appsettings.json`. Here are the available settings:
```json
"Sentry": {
"Dsn": "YOUR_SENTRY_DSN",
"MinimumEventLevel": "Error",
"SendDefaultPii": false,
"MaxBreadcrumbs": 50,
"SampleRate": 1.0,
"TracesSampleRate": 0.2,
"Debug": false
}
```
### Required Settings
- `Dsn`: Your Sentry project's Data Source Name (required). This should be obtained from your Sentry project settings.
### Optional Settings
- `MinimumEventLevel`: Minimum log level that triggers an event to be sent to Sentry (default: Error)
- `SampleRate`: Percentage of errors to send to Sentry (1.0 = 100%)
- `TracesSampleRate`: Percentage of transactions to send to Sentry (0.2 = 20%)
- `Debug`: Enable debug mode for Sentry SDK (set to true in Development environment)
## Environment Configuration
Each environment can have specific Sentry settings:
- Development: Uses settings from `appsettings.Development.json` (sampling rate set to 100% for easy testing)
- Production: Uses settings from `appsettings.Production.json`
## Custom Error Tracking
You can manually track errors and events using the `SentrySdk` class:
```csharp
try
{
// Your code
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
// Additional error handling
}
```
## Testing the Integration
A test endpoint has been added to verify Sentry integration:
```
GET /test-sentry
```
This endpoint throws and captures a test exception, confirming that events are being sent to Sentry.
## Deployment Considerations
When deploying to production:
1. Update the Sentry DSN in your production configuration
2. Consider setting appropriate sampling rates based on traffic volume
3. Ensure PII (Personally Identifiable Information) handling complies with your privacy policies
## Performance Impact
The Sentry SDK has minimal performance impact with default settings. If needed, you can adjust sampling rates to reduce the number of events sent to Sentry.
## Security
Sentry DSNs should be treated as secrets and not committed directly to source control. Use environment variables or secret management for production deployments.

View File

@@ -1,13 +1,21 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Error",
"Default": "Information",
"System": "Error",
"Microsoft": "Warning"
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200/"
},
"Sentry": {
"Debug": true,
"TracesSampleRate": 1.0,
"SendDefaultPii": true,
"DiagnosticLevel": "Debug"
}
}

View File

@@ -24,6 +24,9 @@
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Sentry": {
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1"
},
"Discord": {
"ApplicationId": "",
"PublicKey": "",

View File

@@ -27,5 +27,8 @@
"ElasticConfiguration": {
"Uri": "http://elasticsearch:9200"
},
"Sentry": {
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1"
},
"AllowedHosts": "*"
}

View File

@@ -24,6 +24,15 @@
"Web3Proxy": {
"BaseUrl": "http://localhost:4111"
},
"Sentry": {
"Dsn": "https://698e00d7cb404b049aff3881e5a47f6b@bugcenter.apps.managing.live/1",
"MinimumEventLevel": "Error",
"SendDefaultPii": true,
"MaxBreadcrumbs": 50,
"SampleRate": 1.0,
"TracesSampleRate": 0.2,
"Debug": false
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",