101 lines
3.4 KiB
C#
101 lines
3.4 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace JobTrackerApi.Controllers
|
|
{
|
|
[ApiController]
|
|
[Route("api/client-errors")]
|
|
[RequestSizeLimit(32 * 1024)]
|
|
public class ClientErrorsController : ControllerBase
|
|
{
|
|
private const int MaxFieldLength = 512;
|
|
private const int MaxStackSummaryLength = 1024;
|
|
|
|
private readonly ILogger<ClientErrorsController> _logger;
|
|
|
|
public ClientErrorsController(ILogger<ClientErrorsController> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public sealed record ClientErrorReport(
|
|
string? ErrorId,
|
|
string? Message,
|
|
string? Stack,
|
|
string? ComponentStack,
|
|
string? Url,
|
|
string? UserAgent,
|
|
string? At
|
|
);
|
|
|
|
[HttpPost]
|
|
public IActionResult Report([FromBody] ClientErrorReport report)
|
|
{
|
|
var errorId = Normalize(report.ErrorId, 128) ?? "unknown";
|
|
var at = Normalize(report.At, 128) ?? "unknown";
|
|
var url = Normalize(report.Url, MaxFieldLength) ?? "unknown";
|
|
var userAgent = Normalize(report.UserAgent, MaxFieldLength) ?? "unknown";
|
|
var message = Normalize(report.Message, MaxFieldLength) ?? "unknown";
|
|
|
|
var stackHash = Hash(report.Stack);
|
|
var componentStackHash = Hash(report.ComponentStack);
|
|
var stackPreview = SummarizeStack(report.Stack);
|
|
var componentPreview = SummarizeStack(report.ComponentStack);
|
|
|
|
_logger.LogError(
|
|
"ClientError {ErrorId} at {At} url={Url} ua={UserAgent} msg={Message} stackHash={StackHash} componentHash={ComponentStackHash} stackPreview={StackPreview} componentPreview={ComponentPreview}",
|
|
errorId,
|
|
at,
|
|
url,
|
|
userAgent,
|
|
message,
|
|
stackHash,
|
|
componentStackHash,
|
|
stackPreview,
|
|
componentPreview
|
|
);
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
internal static string? Normalize(string? value, int maxLength)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
|
|
var normalized = value.Trim().Replace("\r", " ").Replace("\n", " ");
|
|
if (normalized.Length <= maxLength)
|
|
{
|
|
return normalized;
|
|
}
|
|
|
|
return normalized[..maxLength];
|
|
}
|
|
|
|
internal static string? SummarizeStack(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
|
|
var lines = value
|
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Select(line => line.Replace("\r", string.Empty).Trim())
|
|
.Where(line => line.Length > 0)
|
|
.Take(2)
|
|
.ToArray();
|
|
|
|
if (lines.Length == 0) return null;
|
|
|
|
var summary = string.Join(" | ", lines);
|
|
return summary.Length <= MaxStackSummaryLength ? summary : summary[..MaxStackSummaryLength];
|
|
}
|
|
|
|
internal static string? Hash(string? value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value)) return null;
|
|
|
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(value));
|
|
return Convert.ToHexString(bytes).ToLowerInvariant();
|
|
}
|
|
}
|
|
}
|