在Application Insights中查看POST请求正文

Dha*_*777 49 c# azure-application-insights

是否可以在Application Insights中查看POST请求正文?

我可以看到请求详细信息,但不能查看应用程序洞察中发布的有效负载.我是否需要通过一些编码来跟踪这个?

我正在构建一个MVC核心1.1 Web Api.

POST请求

yon*_*sha 33

您可以简单地实现自己的遥测初始化器:

例如,在提取有效负载并将其添加为请求遥测的自定义维度的实现下面:

public class RequestBodyInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        var requestTelemetry = telemetry as RequestTelemetry;
        if (requestTelemetry != null && (requestTelemetry.HttpMethod == HttpMethod.Post.ToString() || requestTelemetry.HttpMethod == HttpMethod.Put.ToString()))
        {
            using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
            {
                string requestBody = reader.ReadToEnd();
                requestTelemetry.Properties.Add("body", requestBody);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后通过配置文件或代码将其添加到配置中:

TelemetryConfiguration.Active.TelemetryInitializers.Add(new RequestBodyInitializer());
Run Code Online (Sandbox Code Playgroud)

然后在Google Analytics中查询:

requests | limit 1 | project customDimensions.body
Run Code Online (Sandbox Code Playgroud)

  • @ShyamalParikh你可以使用HttpContext.Current.Request.HttpMethod而不是requestTelemetry.HttpMethod (6认同)
  • 我也可以写"响应机构"吗?如果是,可以有人分享一些指南或代码段,如果可能的话. (4认同)
  • 请记住,此代码执行两次:一次在请求之前,一次在请求之后.请求体第二次为空,因此您可能需要在添加(或覆盖)属性之前检查输入流的长度! (3认同)
  • 我不得不重置请求inputStream的位置以便读取正文.HttpContext.Current.Request.InputStream.Position = 0; (3认同)
  • 看来requestTelemetry.HttpMethod已被弃用.我不知道去哪里查找HttpMethod. (2认同)
  • 此代码未按预期工作,因为请求正文已被释放。 (2认同)
  • 我也遇到了对象处置异常。此外,“TelemetryConfiguration.Active”现已弃用。https://github.com/microsoft/ApplicationInsights-dotnet/issues/1152 (2认同)

小智 19

@yonisha提供的解决方案在我看来是最干净的解决方案.但是你仍然需要在那里获得你的httpcontext,为此你需要更多的代码.我还插入了一些基于或取自上面的代码示例的注释.重置您的请求的位置很重要,否则您将丢失它的数据.

这是我测试的解决方案,并给了我jsonbody:

public class RequestBodyInitializer : ITelemetryInitializer
{
    readonly IHttpContextAccessor httpContextAccessor;

    public RequestBodyInitializer(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        if (telemetry is RequestTelemetry requestTelemetry)
        {
            if ((httpContextAccessor.HttpContext.Request.Method == HttpMethods.Post ||
                 httpContextAccessor.HttpContext.Request.Method == HttpMethods.Put) &&
                httpContextAccessor.HttpContext.Request.Body.CanRead)
            {
                const string jsonBody = "JsonBody";

                if (requestTelemetry.Properties.ContainsKey(jsonBody))
                {
                    return;
                }

                //Allows re-usage of the stream
                httpContextAccessor.HttpContext.Request.EnableRewind();

                var stream = new StreamReader(httpContextAccessor.HttpContext.Request.Body);
                var body = stream.ReadToEnd();

                //Reset the stream so data is not lost
                httpContextAccessor.HttpContext.Request.Body.Position = 0;
                requestTelemetry.Properties.Add(jsonBody, body);
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后还要确保将其添加到Startup - > ConfigureServices

services.AddSingleton<ITelemetryInitializer, RequestBodyInitializer>();
Run Code Online (Sandbox Code Playgroud)

编辑:

如果您还想获得响应体,我发现创建一个中间件(dotnet核心不确定框架)是有用的.起初我采用了上面的方法来记录响应和请求,但大多数时候你想要这些一起.

    public async Task Invoke(HttpContext context)
    {
        var reqBody = await this.GetRequestBodyForTelemetry(context.Request);

        var respBody = await this.GetResponseBodyForTelemetry(context);
        this.SendDataToTelemetryLog(reqBody, respBody, context);
    }
Run Code Online (Sandbox Code Playgroud)

这等待请求和响应,其中请求与上面的请求几乎相同,而不是它是一项任务.

对于我使用下面代码的响应主体,我也排除了204,因为它导致了nullref:

public async Task<string> GetResponseBodyForTelemetry(HttpContext context)
{
    var originalBody = context.Response.Body;

    try
    {
        using (var memStream = new MemoryStream())
        {
            context.Response.Body = memStream;

            //await the responsebody
            await next(context);
            if (context.Response.StatusCode == 204)
            {
                return null;
            }

            memStream.Position = 0;
            var responseBody = new StreamReader(memStream).ReadToEnd();

            //make sure to reset the position so the actual body is still available for the client
            memStream.Position = 0;
            await memStream.CopyToAsync(originalBody);

            return responseBody;
        }
    }
    finally
    {
        context.Response.Body = originalBody;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考,从 .NET Core 3.0 开始,EnableRewind() 已移至“httpContextAccessor.HttpContext.Request.EnableBuffering()” (6认同)
  • @JoanComasFdz:初始化程序不起作用,因为当它运行时,主体流已经被释放,因为请求已经被完成。你必须编写一个中间件。在中间件中获取遥测对象的神奇方法是“httpContext.Features.Get&lt;RequestTelemetry&gt;()”。有了这些知识和给定的答案,你应该可以让它发挥作用。 (3认同)
  • 我刚刚在 ASP .NET 5 项目中使用了这段代码,但有两个问题:1)不允许使用 ReadToAll(),需要异步。2)我总是收到错误:ObjectDisposeException:无法访问已处置的对象。对象名称:“HttpRequestStream”。 (2认同)

use*_*994 15

几天前,我收到了一个类似的要求,即在应用程序洞察中记录请求正文,并从有效负载中过滤掉敏感的输入用户数据。所以分享我的解决方案。以下解决方案是为 ASP.NET Core 2.0 Web API 开发的。

动作过滤器属性

我使用了ActionFilterAttributefrom ( Microsoft.AspNetCore.Mvc.Filtersnamespace) ,它提供了 Model viaActionArgument以便通过反射,可以提取那些标记为敏感的属性。

public class LogActionFilterAttribute : ActionFilterAttribute
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public LogActionFilterAttribute(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (context.HttpContext.Request.Method == HttpMethods.Post || context.HttpContext.Request.Method == HttpMethods.Put)
        {
            // Check parameter those are marked for not to log.
            var methodInfo = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)context.ActionDescriptor).MethodInfo;
            var noLogParameters = methodInfo.GetParameters().Where(p => p.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute))).Select(p => p.Name);

            StringBuilder logBuilder = new StringBuilder();

            foreach (var argument in context.ActionArguments.Where(a => !noLogParameters.Contains(a.Key)))
            {
                var serializedModel = JsonConvert.SerializeObject(argument.Value, new JsonSerializerSettings() { ContractResolver = new NoPIILogContractResolver() });
                logBuilder.AppendLine($"key: {argument.Key}; value : {serializedModel}");
            }

            var telemetry = this.httpContextAccessor.HttpContext.Items["Telemetry"] as Microsoft.ApplicationInsights.DataContracts.RequestTelemetry;
            if (telemetry != null)
            {
                telemetry.Context.GlobalProperties.Add("jsonBody", logBuilder.ToString());
            }

        }

        await next();
    }
}
Run Code Online (Sandbox Code Playgroud)

“LogActionFilterAttribute”作为过滤器注入到 MVC 管道中。

 services.AddMvc(options =>
 {
       options.Filters.Add<LogActionFilterAttribute>();
 });
Run Code Online (Sandbox Code Playgroud)

无日志属性

在上面的代码中,NoLogAttribute使用了应应用于模型/模型的属性或方法参数的属性,以指示不应记录该值。

public class NoLogAttribute : Attribute
{
}
Run Code Online (Sandbox Code Playgroud)

NoPIILogContractResolver

此外,NoPIILogContractResolver在使用JsonSerializerSettings期间,序列化过程中

internal class NoPIILogContractResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var properties = new List<JsonProperty>();

        if (!type.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute)))
        {
            IList<JsonProperty> retval = base.CreateProperties(type, memberSerialization);
            var excludedProperties = type.GetProperties().Where(p => p.GetCustomAttributes(true).Any(t => t.GetType() == typeof(NoLogAttribute))).Select(s => s.Name);
            foreach (var property in retval)
            {
                if (excludedProperties.Contains(property.PropertyName))
                {
                    property.PropertyType = typeof(string);
                    property.ValueProvider = new PIIValueProvider("PII Data");
                }

                properties.Add(property);
            }
        }

        return properties;
    }
}

internal class PIIValueProvider : IValueProvider
{
    private object defaultValue;

    public PIIValueProvider(string defaultValue)
    {
        this.defaultValue = defaultValue;
    }

    public object GetValue(object target)
    {
        return this.defaultValue;
    }

    public void SetValue(object target, object value)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

PIITelemetryInitializer

要注入RequestTelemetry对象,我必须使用它ITelemetryInitializer以便RequestTelemetry可以在LogActionFilterAttribute类中检索。

public class PIITelemetryInitializer : ITelemetryInitializer
{
    IHttpContextAccessor httpContextAccessor;

    public PIITelemetryInitializer(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public void Initialize(ITelemetry telemetry)
    {
        if (this.httpContextAccessor.HttpContext != null)
        {
            if (telemetry is Microsoft.ApplicationInsights.DataContracts.RequestTelemetry)
            {
                this.httpContextAccessor.HttpContext.Items.TryAdd("Telemetry", telemetry);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

PIITelemetryInitializer注册为

services.AddSingleton<ITelemetryInitializer, PIITelemetryInitializer>();
Run Code Online (Sandbox Code Playgroud)

测试功能

下面的代码演示了上面代码的用法

创建了一个控制器

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ILogger _logger;

    public ValuesController(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<ValuesController>();
    }

    // POST api/values
    [HttpPost]
    public void Post([FromBody, NoLog]string value)
    {

    }

    [HttpPost]
    [Route("user")]
    public void AddUser(string id, [FromBody]User user)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

其中User模型定义为

public class User
{
    [NoLog]
    public string Id { get; set; }

    public string Name { get; set; }

    public DateTime AnneviseryDate { get; set; }

    [NoLog]
    public int LinkId { get; set; }

    public List<Address> Addresses { get; set; }
}

public class Address
{
    public string AddressLine { get; set; }

    [NoLog]
    public string City { get; set; }

    [NoLog]
    public string Country { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

所以当 API 被 Swagger 工具调用时

在此处输入图片说明

jsonBody 在没有敏感数据的情况下登录请求。所有敏感数据都替换为“PII 数据”字符串文字。

在此处输入图片说明

  • 实际上你也可以使用`context.HttpContext.Features.Get&lt;RequestTelemetry&gt;()`来获取`ActionFilterAttribute`中的遥测实例 (2认同)

Mat*_*tze 6

我选择自定义中间件路径,因为它HttpContext已经在那里使事情变得更容易。

public class RequestBodyLoggingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var method = context.Request.Method;

        // Ensure the request body can be read multiple times
        context.Request.EnableBuffering();

        // Only if we are dealing with POST or PUT, GET and others shouldn't have a body
        if (context.Request.Body.CanRead && (method == HttpMethods.Post || method == HttpMethods.Put))
        {
            // Leave stream open so next middleware can read it
            using var reader = new StreamReader(
                context.Request.Body,
                Encoding.UTF8,
                detectEncodingFromByteOrderMarks: false,
                bufferSize: 512, leaveOpen: true);

            var requestBody = await reader.ReadToEndAsync();

            // Reset stream position, so next middleware can read it
            context.Request.Body.Position = 0;

            // Write request body to App Insights
            var requestTelemetry = context.Features.Get<RequestTelemetry>();                              
            requestTelemetry?.Properties.Add("RequestBody", requestBody);
        }

        // Call next middleware in the pipeline
        await next(context);
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我记录响应正文的方式

public class ResponseBodyLoggingMiddleware : IMiddleware
{
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        var originalBodyStream = context.Response.Body;

        try
        {
            // Swap out stream with one that is buffered and suports seeking
            using var memoryStream = new MemoryStream();
            context.Response.Body = memoryStream;

            // hand over to the next middleware and wait for the call to return
            await next(context);

            // Read response body from memory stream
            memoryStream.Position = 0;
            var reader = new StreamReader(memoryStream);
            var responseBody = await reader.ReadToEndAsync();

            // Copy body back to so its available to the user agent
            memoryStream.Position = 0;
            await memoryStream.CopyToAsync(originalBodyStream);

            // Write response body to App Insights
            var requestTelemetry = context.Features.Get<RequestTelemetry>();
            requestTelemetry?.Properties.Add("ResponseBody", responseBody);
        }
        finally
        {
            context.Response.Body = originalBodyStream;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

比添加扩展方法...

public static class ApplicationInsightExtensions
{
    public static IApplicationBuilder UseRequestBodyLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestBodyLoggingMiddleware>();
    }

    public static IApplicationBuilder UseResponseBodyLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ResponseBodyLoggingMiddleware>();
    }            
}
Run Code Online (Sandbox Code Playgroud)

...允许在内部进行干净的集成 Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        
        // Enable our custom middleware
        app.UseRequestBodyLogging();
        app.UseResponseBodyLogging();
    }
    
    // ...
}
Run Code Online (Sandbox Code Playgroud)

不要忘记在里面注册自定义中间件组件 ConfigureServices()

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
            
    services.AddTransient<RequestBodyLoggingMiddleware>();
    services.AddTransient<ResponseBodyLoggingMiddleware>();
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里阅读更多关于它的信息