API Request

 

 

Need of API Request/Response Logger

We often need log of application data that may include the sequence of method calls or events, errors occurs, user actions step by step that application executes. This type of data indeed needs when we don’t have any clue or data through which we can reproduce the critical error – which is occurred in production environment. By logging request and response in Web Api is helpful in debugging and tracing and becomes easy in detecting problems.

Similar like authentication, exception logging, filtering request – which is function that affects the entire application that should be centralized. The logging of request/response in web API is similar like to handle it centralized.

How we can log every API request and response:

There are many frameworks available to log unexpected errors like SeriLog, ELMAH etc., Similar like we can create our own “Message Handled” which will register once in web api to log each and every api request/response data.

Building our custom logger for Web Api:

Create a new Web API project in Visual Studio and save it with your desired name. We will be taking advantage of a custom delegating handler here to intercept the calls to the Web API.

Let’s first create the class which will store all information from our request and responses, e.g. request url, request headers, request timestamp, request content type, request body, response status code, response body, response timestamp, response header etc.

public class ApiLoggerModel
    {
        public long LoggerId { get; set; }                  // The (database) ID for the API log entry.
        public string Application { get; set; }             // The application that made the request.
        public string ApplicaionUser { get; set; }          // The user that made the request.
        public string Machine { get; set; }                 // The machine that made the request.
        public string RequestIpAddress { get; set; }        // The IP address that made the request.
        public string RequestContentType { get; set; }      // The request content type.
        public string RequestContentBody { get; set; }      // The request content body.
        public string RequestUri { get; set; }              // The request URI.
        public string RequestMethod { get; set; }           // The request method (GET, POST, PUT etc).
        public string RequestHeaders { get; set; }          // The request headers.
        public DateTime? RequestTimestamp { get; set; }     // The request timestamp.
        public string ResponseContentType { get; set; }     // The response content type.
        public string responseContentBody { get; set; }     // The response content body.
        public int? ResponseStatusCode { get; set; }        // The response status code.
        public string responseHeaders { get; set; }         // The response headers.
        public DateTime? ResponseTimestamp { get; set; }    // The response timestamp.
    }

 

Now let’s implement custom class called CustomLogHandler which is essentially a message handler that extends the DelegatingHanlder class which has sendAsync method which we can override to log our each API request as well response.

public class CustomLogHandler : DelegatingHandler
    { 

protected ove rride async Task<httpresponsemessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var apiLogEntry = CreateApiLogEntryWithRequestData(request);
            var response = (dynamic)null;
            await Task.Run(() =>
            {
                if (request.Content != null)
                {
                    request.Content.ReadAsStringAsync()
                        .ContinueWith(task =>
                        {
                            apiLogEntry.RequestContentBody = task.Result;
                        }, cancellationToken);
                }
            });

            await base.SendAsync(request, cancellationToken)
                   .ContinueWith(task =>
                   {
                       response = task.Result;
                       Task.Run(() =>
                       {
                           apiLogEntry = CreateApiLogEntryWithResponseData(response, apiLogEntry);
                           ILoggerStorage apiLoggerStorage = factory.GetLoggerStorage(LoggerStorageOptions);
                           SendToLog (apiLogEntry);
                       }, cancellationToken);
                   });
            return response;
        }
private async Task<bool> SendToLog(ApiLoggerModel apiLogEntry)
        {
            // TODO: Write code here to store the ApiLoggerModel instance to a pre-configured log store...
            return true;
        }
}</bool></httpresponsemessage>

 

The method CreateApiLogEntryWithRequestData and CreateApiLogEntryWithReponseData used to assign all meta data related to API request as well response to ApiLoggerModel properties as well serialized the request/response header data.(See the code below).

private ApiLoggerModel CreateApiLogEntryWithRequestData(HttpRequestMessage request)
        {
            var context = ((HttpContextBase)request.Properties["MS_HttpContext"]);
            string RequestHeaders = SerializeHeaders(request.Headers);
            return new ApiLoggerModel
            {
                ApplicaionUser = context.User.Identity.Name,
                Machine = Environment.MachineName,
                RequestContentType = context.Request.ContentType,
                RequestIpAddress = context.Request.UserHostAddress,
                RequestMethod = request.Method.Method,
                RequestTimestamp = DateTime.Now,
                RequestHeaders = RequestHeaders,
                RequestUri = request.RequestUri.ToString()
            };
        }
private ApiLoggerModel CreateApiLogEntryWithResponseData(dynamic response, ApiLoggerModel apiLogEntry)
        {

            apiLogEntry.ResponseStatusCode = (int)response.StatusCode;
            apiLogEntry.ResponseTimestamp = DateTime.Now;

            if (response.Content != null)
            {
                apiLogEntry.responseContentBody = response.Content.ReadAsStringAsync().Result;
                apiLogEntry.ResponseContentType = response.Content.Headers.ContentType.MediaType;
                apiLogEntry.responseHeaders = SerializeHeaders(response.Content.Headers);
            }
            return apiLogEntry;
        }

        private string SerializeHeaders(HttpHeaders headers)
        {
            var dict = new Dictionary<string, string="">();
            foreach (var item in headers.ToList())
            {
                if (item.Value != null)
                {
                    var header = String.Empty;
                    foreach (var value in item.Value)
                    {
                        header += value + " ";
                    }
                    // Trim the trailing space and add item to the dictionary
                    header = header.TrimEnd(" ".ToCharArray());
                    dict.Add(item.Key, header);
                }
            }
            return JsonConvert.SerializeObject(dict, Formatting.Indented);
        }
</string,>

 

Note that you need to write the necessary code to store the ApiLoggerModel instance shown in SendToLog method to a pre-configured log target, i.e. file or database. I have preferred to write either on file or sql db or mongoDB.

Building our custom logger for Web Api:

To register this CustomMessageHandler class, you can take the advantage of the Application_Start() event in the Global.asax.cs file. The following code snippet demonstrates how you can register the custom handler.

protected void Application_Start()
        {
            // Your as usual code here
GlobalConfiguration.Configuration.MessageHandlers.Add(new CustomLogHandler());
        }

 

Full source code on github is here which has logger facility to log either on text file or Sql DB or MongoDB with just changes in web.config file.
https://github.com/shaileshsakaria/ApiLogger

Mr. Shailesh Sakaria