Saltar al contenido

Ver el cuerpo de la solicitud POST en Application Insights

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

ingrese la descripción de la imagen aquí

El jsonBody se registra en Solicitud sin datos confidenciales. Todos los datos confidenciales se reemplazan por el literal de cadena ‘PII Data’.

ingrese la descripción de la imagen aquí

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *