格式化堆栈跟踪以在 API 响应中手动返回 使用 StackTrace 和 StackFrame 类的异常信息模型

问题描述

目前它正在返回一个单一的难以理解的块,由多个这样的东西粘在一起,像这样:

在 ProyectX.Services.Service.Validate(IList 1 myParam) 中 C:\\Repositories\\projectx\\src\\ProyectX.Services\\Service.cs:line 116\r\n 在 ProyectX.Services.Service.Validate(IList 1 myParam) 中 C:\\Repositories\\projectx\\src\\ProyectX.Services\\Service.cs:line 116\r\n

目标:

在 C:\Repositories\projectx\src\ProyectX.Services\Service.cs:line 116 中的 ProyectX.Services.Service.Validate(IList 1 myParam)

在 C:\Repositories\projectx\src\ProyectX.Services\Service.cs:line 116 中的 ProyectX.Services.Service.Validate(IList 1 myParam)

我试过

Regex.Unescape(exception.StackTrace)

JsonSerializer.Serialize(exception.StackTrace,new JsonSerializerOptions() {WriteIndented = true });

中间件在 Startup.cs 中

public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
    app.UseMiddleware<ErrorHandlerMiddleware>();

中间件:

using System;
using System.Collections.Generic;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using ProyectX.Domain.Exceptions;
using ProyectX.Domain.Models;

namespace ProyectX.API.Middleware
{
    public class ErrorHandlerMiddleware
    {
        private readonly RequestDelegate _next;

        public ErrorHandlerMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context,IWebHostEnvironment env)
        {
            try
            {
                await _next(context);
            }
            catch (Exception exception)
            {
                var response = context.Response;
                response.ContentType = "application/json";

                switch (exception)
                {
                    case InvalidOperationException:
                        response.StatusCode = (int)HttpStatusCode.BadRequest;
                        break;
                    default:
                        response.StatusCode = (int)HttpStatusCode.InternalServerError;
                        break;
                }

                var details = new Dictionary<string,string>
                {
                    { "Message",exception.Message },};

                if (env.IsDevelopment())
                {
                    details.Add("StackTrace",exception.StackTrace);
                }

                var errorResponse = new ErrorResponseModel(exception,details);
                var result = JsonSerializer.Serialize(errorResponse);

                await response.WriteAsync(result);
            }
        }
    }
}

错误响应模型

using System;
using System.Collections.Generic;

namespace ProyectX.Domain.Models
{
    public class ErrorResponseModel
    {
        public ErrorResponseModel(Exception ex,Dictionary<string,string> details)
        {
            Type = ex.GetType().Name;
            Details = details;
        }

        public string Type { get; set; }

        public IDictionary<string,string> Details { get; set; }
    }
}

解决方法

我建议在您的 ErrorResponseModel 类中进行一些更改,以便您的字典接受对象类型作为值,例如 Dictionary<string,object>。这里的想法是,您可以添加字符串或字符串数​​组。

因此,对于您的跟踪跟踪,您可以使用 split by "\r\n" 将其拆分为多行并获取它的字符串数组,我可以将其传递给我的 StackTrace 值。

让我们来看看你的模型:

public class ErrorResponseModel
{
    public ErrorResponseModel(Exception ex,Dictionary<string,object> details)
    {
        Type = ex.GetType().Name;
        Details = details;
    }

    public string Type { get; set; }

    public IDictionary<string,object> Details { get; set; }
}

这里是处理异常的部分:

var details = new Dictionary<string,object>
{
    { "Message",exception.Message},};

var lines = exception.StackTrace?.Split("\r\n").Select(e => e.TrimStart());
details.Add("StackTrace",lines);

var errorResponse = new ErrorResponseModel(exception,details);

var result = JsonSerializer.Serialize(errorResponse,new JsonSerializerOptions() { WriteIndented = true });

所有这些返回如下:

enter image description here

,

使用 StackTrace 和 StackFrame 类的异常信息模型

有一个 StackTrace 类可以帮助您获取 StackFrame 对象的列表。每个帧,包括如下信息:

  • 行号
  • 方法(类、程序集)
  • 代码文件

查看 ASP.NET Core ErrorPageDeveloperExceptionPageMiddleware,您将看到该框架创建了一个模型类,包括堆栈帧等异常信息,然后在错误页面中格式化了模型并显示在一种可读的格式。

这就是 Exception 类生成 StackTrace 的方式:通过调用 StackTrace.ToString 获取堆栈帧和字符串格式的上述信息。

您也可以这样做并以更好的格式生成字符串,或者作为一个更好的主意,创建一个异常或堆栈跟踪模型并让格式设置在以后完成。

例如,这是您可以拥有的异常模型(包括堆栈跟踪):

{
  "Message":"Attempted to divide by zero.","StackFrames":[
    {
      "LineNumber":13,"Method":"Main(String[] args)","Class":"SampleConsoleApp.Program","AssemblyName":"SampleConsoleApp,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null","AssemblyFile":"file:///C:/SampleConsoleApp/bin/Debug/netcoreapp3.1/SampleConsoleApp.dll","CodeFile":"C:\\SampleConsoleApp\\Program.cs"
    },{
      "LineNumber":23,"Method":"Divide(Int32 x,Int32 y)","CodeFile":"C:\\SampleConsoleApp\\Program.cs"
     }
  ],"InnerException":null
}

上面的 json 字符串是从我的异常模型中创建的:

try
{
}
catch (Exception ex)
{
    var model = ExceptionModel.Create(ex);

    // You can convert it to json and store it
    // Or format it and write to log 
}

ExceptionModel 包括 StackTrace

以下是我实现异常模型的方式:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
public class ExceptionModel
{
    public string Message { get; set; }
    public IEnumerable<StackFrameModel> StackFrames { get; set; }
    public ExceptionModel InnerException { get; set; }
    public static ExceptionModel Create(Exception ex = null)
    {
        var trace = ex == null ? new StackTrace(true) : new StackTrace(ex,true);
        var model = new ExceptionModel();
        model.Message = ex?.Message;
        model.StackFrames = trace.GetFrames().Reverse()
            .Select(x => new StackFrameModel()
        {
            LineNumber = x.GetFileLineNumber(),Method = GetMethodSignature(x.GetMethod()),Class = x.GetMethod()?.DeclaringType?.FullName,AssemblyName = x.GetMethod()?.DeclaringType?.Assembly?.FullName,AssemblyFile = x.GetMethod()?.DeclaringType?.Assembly?.CodeBase,CodeFile = x.GetFileName(),});
        if (ex?.InnerException != null)
            model.InnerException = ExceptionModel.Create(ex.InnerException);
        return model;
    }
    private static string GetTypeName(Type type)
    {
        return type?.FullName?.Replace('+','.');
    }
    private static string GetMethodSignature(MethodBase mb)
    {
        var sb = new StringBuilder();
        sb.Append(mb.Name);

        // deal with the generic portion of the method
        if (mb is MethodInfo && ((MethodInfo)mb).IsGenericMethod)
        {
            Type[] typars = ((MethodInfo)mb).GetGenericArguments();
            sb.Append("[");
            int k = 0;
            bool fFirstTyParam = true;
            while (k < typars.Length)
            {
                if (fFirstTyParam == false)
                    sb.Append(",");
                else
                    fFirstTyParam = false;

                sb.Append(typars[k].Name);
                k++;
            }
            sb.Append("]");
        }

        // arguments printing
        sb.Append("(");
        ParameterInfo[] pi = mb.GetParameters();
        bool fFirstParam = true;
        for (int j = 0; j < pi.Length; j++)
        {
            if (fFirstParam == false)
                sb.Append(",");
            else
                fFirstParam = false;

            String typeName = "<UnknownType>";
            if (pi[j].ParameterType != null)
                typeName = pi[j].ParameterType.Name;
            sb.Append(typeName + " " + pi[j].Name);
        }
        sb.Append(")");
        return sb.ToString();
    }
}
public class StackFrameModel
{
    public int LineNumber { get; set; }
    public string Method { get; set; }
    public string Class { get; set; }
    public string AssemblyName { get; set; }
    public string AssemblyFile { get; set; }
    public string CodeFile { get; set; }
}