是否可以在Application Insights中查看POST请求正文?
我可以看到请求详细信息,但不能查看应用程序洞察中发布的有效负载。我是否需要通过编码来跟踪这个?
我正在构建一个MVC核心1.1 Web Api。
答案 0 :(得分:26)
您可以简单地实现自己的Telemetry Initializer:
例如,在提取有效负载并将其添加为请求遥测的自定义维度的实现下面:
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);
}
}
}
}
然后通过configuration file或通过代码
将其添加到配置中TelemetryConfiguration.Active.TelemetryInitializers.Add(new RequestBodyInitializer());
然后在Google Analytics中查询:
requests | limit 1 | project customDimensions.body
答案 1 :(得分:14)
@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);
}
}
}
然后还要确保将此添加到您的启动 - > ConfigureServices
services.AddSingleton<ITelemetryInitializer, RequestBodyInitializer>();
编辑:
如果您还想获得响应体,我发现创建一个中间件(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);
}
这等待请求和响应,其中请求与上面的请求几乎相同,而不是它是一项任务。
对于我使用下面代码的响应主体,我也排除了204,因为它导致了nullref:
public async Task<string> GetResponseBodyForTelemetry(HttpContext context)
{
Stream 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;
}
}
答案 2 :(得分:5)
我选择自定义中间件路径,因为 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);
}
}
这就是我记录响应正文的方式
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;
}
}
}
比添加一个扩展方法...
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>();
}
}
...允许在 Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Enable our custom middleware
app.UseRequestBodyLogging();
app.UseResponseBodyLogging();
}
// ...
}
不要忘记在 ConfigureServices()
内注册自定义中间件组件
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
services.AddTransient<RequestBodyLoggingMiddleware>();
services.AddTransient<ResponseBodyLoggingMiddleware>();
}
您可以阅读更多相关信息here
答案 3 :(得分:3)
在Asp.Net核心中,看起来我们不必使用ITelemetryInitializer。我们可以使用中间件将请求记录到应用程序见解中。感谢@IanKemp https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/686
public async Task Invoke(HttpContext httpContext)
{
var requestTelemetry = httpContext.Features.Get<RequestTelemetry>();
//Handle Request
var request = httpContext.Request;
if (request?.Body?.CanRead == true)
{
request.EnableBuffering();
var bodySize = (int)(request.ContentLength ?? request.Body.Length);
if (bodySize > 0)
{
request.Body.Position = 0;
byte[] body;
using (var ms = new MemoryStream(bodySize))
{
await request.Body.CopyToAsync(ms);
body = ms.ToArray();
}
request.Body.Position = 0;
if (requestTelemetry != null)
{
var requestBodyString = Encoding.UTF8.GetString(body);
requestTelemetry.Properties.Add("RequestBody", requestBodyString);
}
}
}
await _next(httpContext); // calling next middleware
}
答案 4 :(得分:2)
我为此实现了一个中间件,
调用方法,
if (context.Request.Method == "POST" || context.Request.Method == "PUT")
{
var bodyStr = GetRequestBody(context);
var telemetryClient = new TelemetryClient();
var traceTelemetry = new TraceTelemetry
{
Message = bodyStr,
SeverityLevel = SeverityLevel.Verbose
};
//Send a trace message for display in Diagnostic Search.
telemetryClient.TrackTrace(traceTelemetry);
}
其中,GetRequestBody就像,
private static string GetRequestBody(HttpContext context)
{
var bodyStr = "";
var req = context.Request;
//Allows using several time the stream in ASP.Net Core.
req.EnableRewind();
//Important: keep stream opened to read when handling the request.
using (var reader = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
{
bodyStr = reader.ReadToEnd();
}
// Rewind, so the core is not lost when it looks the body for the request.
req.Body.Position = 0;
return bodyStr;
}
答案 5 :(得分:1)
yonisha提供的解决方案很干净,但在.Net Core 2.0中对我不起作用。如果您有一个JSON正文,则此方法有效:
public IActionResult MyAction ([FromBody] PayloadObject payloadObject)
{
//create a dictionary to store the json string
var customDataDict = new Dictionary<string, string>();
//convert the object to a json string
string activationRequestJson = JsonConvert.SerializeObject(
new
{
payloadObject = payloadObject
});
customDataDict.Add("body", activationRequestJson);
//Track this event, with the json string, in Application Insights
telemetryClient.TrackEvent("MyAction", customDataDict);
return Ok();
}
答案 6 :(得分:0)
对不起,@ yonisha的解决方案在.NET 4.7中似乎不起作用。 Application Insights部分工作正常,但实际上没有简单的方法可以在.NET 4.7中的遥测初始化程序中获取请求主体。 .NET 4.7使用GetBufferlessInputStream()来获取流,并且此流是“一次读取”。一个潜在的代码是这样的:
private static void LogRequestBody(ISupportProperties requestTelemetry)
{
var requestStream = HttpContext.Current?.Request?.GetBufferlessInputStream();
if (requestStream?.Length > 0)
using (var reader = new StreamReader(requestStream))
{
string body = reader.ReadToEnd();
requestTelemetry.Properties["body"] = body.Substring(0, Math.Min(body.Length, 8192));
}
}
但GetBufferlessInputStream()的返回已被消耗,并且不支持搜索。因此,身体将永远是一个空字符串。
答案 7 :(得分:0)
我从来没有得到@ yonisha的回答,所以我使用了DelegatingHandler
代替:
public class MessageTracingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Trace the request
await TraceRequest(request);
// Execute the request
var response = await base.SendAsync(request, cancellationToken);
// Trace the response
await TraceResponse(response);
return response;
}
private async Task TraceRequest(HttpRequestMessage request)
{
try
{
var requestTelemetry = HttpContext.Current?.GetRequestTelemetry();
var requestTraceInfo = request.Content != null ? await request.Content.ReadAsByteArrayAsync() : null;
var body = requestTraceInfo.ToString();
if (!string.IsNullOrWhiteSpace(body) && requestTelemetry != null)
{
requestTelemetry.Properties.Add("Request Body", body);
}
}
catch (Exception exception)
{
// Log exception
}
}
private async Task TraceResponse(HttpResponseMessage response)
{
try
{
var requestTelemetry = HttpContext.Current?.GetRequestTelemetry();
var responseTraceInfo = response.Content != null ? await response.Content.ReadAsByteArrayAsync() : null;
var body = responseTraceInfo.ToString();
if (!string.IsNullOrWhiteSpace(body) && requestTelemetry != null)
{
requestTelemetry.Properties.Add("Response Body", body);
}
}
catch (Exception exception)
{
// Log exception
}
}
}
.GetRequestTelemetry()
是Microsoft.ApplicationInsights.Web的扩展方法。
答案 8 :(得分:0)
几天前,我收到了类似的要求,即在应用程序见解中记录请求正文,并从负载中过滤掉敏感的输入用户数据。所以分享我的解决方案。以下解决方案是针对ASP.NET Core 2.0 Web API开发的。
ActionFilterAttribute
我使用了public class MyNewRequirement : AuthorizationHandler<MyRequirement>, IAuthorizationRequirement
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var mvcContext = context.Resource as AuthorizationFilterContext;
//required service
var _mvcOptions = mvcContext.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
var parameterBinder = mvcContext.HttpContext.RequestServices.GetRequiredService<ParameterBinder>();
var _modelBinderFactory = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelBinderFactory>();
var _modelMetadataProvider = mvcContext.HttpContext.RequestServices.GetRequiredService<IModelMetadataProvider>();
var controllerContext = new ControllerContext(mvcContext);
controllerContext.ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_mvcOptions.ValueProviderFactories.ToArray());
var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext);
var parameters = controllerContext.ActionDescriptor.Parameters;
var parameterBindingInfo = GetParameterBindingInfo(
_modelBinderFactory,
_modelMetadataProvider,
controllerContext.ActionDescriptor,
_mvcOptions);
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
var bindingInfo = parameterBindingInfo[i];
var modelMetadata = bindingInfo.ModelMetadata;
if (!modelMetadata.IsBindingAllowed)
{
continue;
}
var model = await parameterBinder.BindModelAsync(
controllerContext,
bindingInfo.ModelBinder,
valueProvider,
parameter,
modelMetadata,
value: null);
}
}
private static BinderItem[] GetParameterBindingInfo(
IModelBinderFactory modelBinderFactory,
IModelMetadataProvider modelMetadataProvider,
ControllerActionDescriptor actionDescriptor,
MvcOptions mvcOptions)
{
var parameters = actionDescriptor.Parameters;
if (parameters.Count == 0)
{
return null;
}
var parameterBindingInfo = new BinderItem[parameters.Count];
for (var i = 0; i < parameters.Count; i++)
{
var parameter = parameters[i];
ModelMetadata metadata;
if (mvcOptions.AllowValidatingTopLevelNodes &&
modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase &&
parameter is ControllerParameterDescriptor controllerParameterDescriptor)
{
// The default model metadata provider derives from ModelMetadataProvider
// and can therefore supply information about attributes applied to parameters.
metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo);
}
else
{
// For backward compatibility, if there's a custom model metadata provider that
// only implements the older IModelMetadataProvider interface, access the more
// limited metadata information it supplies. In this scenario, validation attributes
// are not supported on parameters.
metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType);
}
var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext
{
BindingInfo = parameter.BindingInfo,
Metadata = metadata,
CacheToken = parameter,
});
parameterBindingInfo[i] = new BinderItem(binder, metadata);
}
return parameterBindingInfo;
}
private struct BinderItem
{
public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata)
{
ModelBinder = modelBinder;
ModelMetadata = modelMetadata;
}
public IModelBinder ModelBinder { get; }
public ModelMetadata ModelMetadata { get; }
}
}
命名空间中的ActionFilterAttribute
,它通过Microsoft.AspNetCore.Mvc.Filters
提供了模型,以便通过反射可以提取那些标记为敏感的属性。
ActionArgument
“ LogActionFilterAttribute”作为过滤器注入到MVC管道中。
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();
}
}
NoLogAttribute
在以上代码中,使用 services.AddMvc(options =>
{
options.Filters.Add<LogActionFilterAttribute>();
});
属性,该属性应应用于模型/模型的属性或方法参数以指示不应记录该值。
NoLogAttribute
NoPIILogContractResolver
此外,public class NoLogAttribute : Attribute
{
}
在序列化过程中用于NoPIILogContractResolver
JsonSerializerSettings
PIITelemetryInitializer
要注入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)
{
}
}
对象,我必须使用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);
}
}
}
}
注册为
PIITelemetryInitializer
测试功能
以下代码演示了上述代码的用法
创建一个控制器
services.AddSingleton<ITelemetryInitializer, PIITelemetryInitializer>();
[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)
{
}
}
模型定义为
User
因此,当Swagger工具调用API时
jsonBody已登录到请求中,没有敏感数据。所有敏感数据都将替换为“ PII Data”字符串文字。
答案 9 :(得分:0)
我可以使用@yonisha方法在Application Insights中记录请求消息正文,但不能记录响应消息正文。我对记录响应消息正文感兴趣。我已经使用@yonisha方法记录了发布,放置,删除请求消息的正文。
当我尝试访问TelemetryInitializer中的响应正文时,我不断收到一条异常消息,并显示一条错误消息,指出“流不可读。当我进行更多研究时,我发现AzureInitializer作为HttpModule(ApplicationInsightsWebTracking)的一部分运行,因此,它获取控制响应对象的时间。
我从@Oskar答案中得到了一个主意。为什么没有委托处理程序并记录响应,因为没有在消息处理程序阶段放置响应对象。消息处理程序是Web API生命周期的一部分,即类似于HTTP模块,但仅限于Web API。幸运的是,当我开发并测试了此想法时,它可以工作,我使用消息处理程序将响应记录在请求消息中,并在AzureInitializer(HTTP模块的执行晚于消息处理程序)处检索到。这是示例代码。
public class AzureRequestResponseInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry != null && HttpContext.Current != null && HttpContext.Current.Request != null)
{
if ((HttpContext.Current.Request.HttpMethod == HttpMethod.Post.ToString()
|| HttpContext.Current.Request.HttpMethod == HttpMethod.Put.ToString()) &&
HttpContext.Current.Request.Url.AbsoluteUri.Contains("api"))
using (var reader = new StreamReader(HttpContext.Current.Request.InputStream))
{
HttpContext.Current.Request.InputStream.Position = 0;
string requestBody = reader.ReadToEnd();
if (requestTelemetry.Properties.Keys.Contains("requestbody"))
{
requestTelemetry.Properties["requestbody"] = requestBody;
}
else
{
requestTelemetry.Properties.Add("requestbody", requestBody);
}
}
else if (HttpContext.Current.Request.HttpMethod == HttpMethod.Get.ToString()
&& HttpContext.Current.Response.ContentType.Contains("application/json"))
{
var netHttpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
if (netHttpRequestMessage.Properties.Keys.Contains("responsejson"))
{
var responseJson = netHttpRequestMessage.Properties["responsejson"].ToString();
if (requestTelemetry.Properties.Keys.Contains("responsebody"))
{
requestTelemetry.Properties["responsebody"] = responseJson;
}
else
{
requestTelemetry.Properties.Add("responsebody", responseJson);
}
}
}
}
}
}
config.MessageHandlers.Add(new LoggingHandler());
public class LoggingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
StoreResponse(response);
return response;
});
}
private void StoreResponse(HttpResponseMessage response)
{
var request = response.RequestMessage;
(response.Content ?? new StringContent("")).ReadAsStringAsync().ContinueWith(x =>
{
var ctx = request.Properties["MS_HttpContext"] as HttpContextWrapper;
if (request.Properties.ContainsKey("responseJson"))
{
request.Properties["responsejson"] = x.Result;
}
else
{
request.Properties.Add("responsejson", x.Result);
}
});
}
}