Solución:
Simplemente puede implementar su propio inicializador de telemetría:
Por ejemplo, a continuación, una implementación que extrae la carga útil y la agrega como una dimensión personalizada de la telemetría de solicitud:
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);
}
}
}
}
Luego agréguelo a la configuración ya sea por archivo de configuración o por código:
TelemetryConfiguration.Active.TelemetryInitializers.Add(new RequestBodyInitializer());
Luego, consúltelo en Analytics:
requests | limit 1 | project customDimensions.body
La solución proporcionada por @yonisha es, en mi opinión, la más limpia disponible. Sin embargo, todavía necesita obtener su HttpContext
allí y para eso necesitas más código. También he insertado algunos comentarios que se basan o se toman de los ejemplos de código anteriores. Es importante restablecer la posición de su solicitud, de lo contrario perderá sus datos.
Esta es mi solución que he probado y me da el 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);
}
}
}
Entonces también asegúrese de agregar esto a su Inicio -> ConfigureServices
services.AddSingleton<ITelemetryInitializer, RequestBodyInitializer>();
EDITAR:
Si también desea obtener el cuerpo de la respuesta, me pareció útil crear una pieza de middleware (.NET Core, no estoy seguro de Framework). Al principio, tomé el enfoque anterior en el que registras una respuesta y una solicitud, pero la mayoría de las veces las quieres juntas:
public async Task Invoke(HttpContext context)
{
var reqBody = await this.GetRequestBodyForTelemetry(context.Request);
var respBody = await this.GetResponseBodyForTelemetry(context);
this.SendDataToTelemetryLog(reqBody, respBody, context);
}
Esto espera tanto una solicitud como una respuesta. GetRequestBodyForTelemetry
es casi idéntico al código del inicializador de telemetría, excepto que usa Task
. Para el cuerpo de respuesta, he usado el código a continuación, también excluí un 204 ya que eso conduce a un 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;
}
}
Hace unos días, obtuve un requisito similar para registrar el cuerpo de la solicitud en la información de la aplicación con filtrar datos de usuario de entrada sensibles de la carga útil. Así que comparto mi solución. La siguiente solución está desarrollada para ASP.NET Core 2.0 Web API.
ActionFilterAttribute
he usado ActionFilterAttribute
de (Microsoft.AspNetCore.Mvc.Filters
espacio de nombres) que proporciona el modelo a través de ActionArgument
de modo que por reflexión se puedan extraer aquellas propiedades que están marcadas como sensibles.
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();
}
}
El ‘LogActionFilterAttribute’ se inyecta en la canalización MVC como Filter.
services.AddMvc(options =>
{
options.Filters.Add<LogActionFilterAttribute>();
});
NoLogAttribute
En el código anterior, NoLogAttribute
Se utiliza un atributo que debe aplicarse en las propiedades del modelo / modelo o en el parámetro del método para indicar que el valor no debe registrarse.
public class NoLogAttribute : Attribute
{
}
NoPIILogContractResolver
También, NoPIILogContractResolver
se utiliza en JsonSerializerSettings
durante el proceso de serialización
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)
{
}
}
PIITelemetryInitializer
Para inyectar el RequestTelemetry
objeto, tengo que usar ITelemetryInitializer
así que eso RequestTelemetry
se puede recuperar en LogActionFilterAttribute
clase.
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);
}
}
}
}
los PIITelemetryInitializer
está registrado como
services.AddSingleton<ITelemetryInitializer, PIITelemetryInitializer>();
Característica de prueba
El siguiente código demuestra el uso del código anterior
Creó un controlador
[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)
{
}
}
Dónde User
El modelo se define como
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; }
}
Entonces, cuando la herramienta Swagger invoca la API
El jsonBody se registra en Solicitud sin datos confidenciales. Todos los datos confidenciales se reemplazan por el literal de cadena ‘PII Data’.