Request and Response logging in asp.net core api

In this tip you will learn how to log request and response in api. This method is used to trouble shoot most of the issues by just retrieving the log information saved in database.
Read the following articles before. you will understand this article. Because we will use these both in this article.


Create table (apilog) and stored procedures in your db


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CREATE TABLE [dbo].[APILog](
	[Id] [bigint] IDENTITY(1,1) NOT NULL,
	[CreatedTime] [datetime] NULL,
	[Type] [nvarchar](50) NULL,
	[URL] [nvarchar](1000) NULL,
	[Body] [nvarchar](max) NULL,
	[QueryString] [nvarchar](1000) NULL,
	[Response] [nvarchar](max) NULL,
	[IsMobile] [bit] NULL,
 CONSTRAINT [PK_APILog] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CREATE PROCEDURE [dbo].[spAPILogCreate]  
(    
 @pType nvarchar(50) = NULL,  
 @pURL nvarchar(1000) = NULL,  
 @pBody nvarchar(max)  = NULL,  
 @pQueryString nvarchar(1000)  = NULL,  
 @pOutput int output  
)  
AS  
BEGIN  
   
    
 INSERT INTO APILog WITH(ROWLOCK)   
 (  
  CreatedTime, [Type],URL,Body, QueryString  
 )  
 VALUES  
 (  
  GETDATE(), @pType, @pURL, @pBody, @pQueryString  
 )  
 Set @pOutput = SCOPE_IDENTITY()  
 --SELECT  CAST( SCOPE_IDENTITY() AS INT)  
END

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
CREATE PROCEDURE [dbo].[spAPILogUpdate]  
(    
 @pId int = 0,  
 @pResponse nvarchar(max) = NULL  
    
)  
AS  
BEGIN  
 --  
 Update APILog WITH(ROWLOCK) SET Response = @pResponse where Id = @pId  
END
GO

Add Services folder to your project add two classes to this folder (RequestResponseLoggingBL.cs, RequestResponseLoggingDAL) RequestResponseLoggingDAL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using MyCoreProject.Models;
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace MyCoreProject.Services
{
    public class RequestResponseLoggingDAL
    {
        private readonly MyDbContext db = new MyDbContext();
        public int AddLog(string reqType, string reqURL, string reqBody = null, string reqQueryString = null)
        {
            int returnVal = -1;
            try
            {

                var sqlCommand = db.LoadStoredProc("spAPILogCreate")
                    .WithSqlParam("@pType", reqType)
                    .WithSqlParam("@pURL", reqURL)
                    .WithSqlParam("@pBody", reqBody)
                    .WithSqlParam("@pQueryString", reqQueryString);           
                DbParameter ouputParam = sqlCommand.CreateParameter();
                ouputParam.DbType = DbType.Int32;
                ouputParam.Direction = ParameterDirection.Output;
                ouputParam.ParameterName = "@pOutput";
              
                sqlCommand.Parameters.Add(ouputParam);             
                int result = sqlCommand.ExecuteStoredProc();
                returnVal = int.Parse(ouputParam.Value.ToString());
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return returnVal;
        }

        public async Task<int> UpdateLog(int requestId, string responseText)
        {
            int returnVal;

            var sqlCommand = db.LoadStoredProc("spAPILogUpdate")
                .WithSqlParam("@pId", requestId)
                .WithSqlParam("@pResponse", responseText);
            try
            {
               
               
                if (sqlCommand.Connection.State == System.Data.ConnectionState.Closed)
                    sqlCommand.Connection.Open();
                //
                await sqlCommand.ExecuteNonQueryAsync();
                //
                returnVal = 1;
            }
            catch
            {
                throw;
            }
            finally
            {
                sqlCommand.Connection.Close();
            }

            return returnVal;
        }
    }
}

RequestResponseLoggingBL
using MyCoreProject.Middleware;
using Microsoft.AspNetCore.Builder;
using System.Threading.Tasks;

namespace MyCoreProject.Services
{
    public class RequestResponseLoggingBL
    {
        private readonly RequestResponseLoggingDAL _requestResponseLogging;
        public RequestResponseLoggingBL()
        {
            _requestResponseLogging = new RequestResponseLoggingDAL();
        }
        public int CreateLog(string reqType, string reqURL, string reqBody = null, string queryString = null)
        {
            return _requestResponseLogging.AddLog(reqType, reqURL, reqBody, queryString);
        }

        public async Task<int> UpdateLog(int reqId, string response)
        {
            return await _requestResponseLogging.UpdateLog(reqId, response);
        }
    }

   
}

Add folder Middleware to your project and add two classes (RequestResponseLoggingMiddleware.cs, RequestResponseLoggingMiddlewareExtensions.cs)
RequestResponseLoggingMiddleware.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
using MyCoreProject.Services;

namespace MyCoreProject.Middleware
{
    public class RequestResponseLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;
        private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
        private readonly RequestResponseLoggingBL _requestResponseLogging;

        public RequestResponseLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<RequestResponseLoggingMiddleware>();
            _recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
            _requestResponseLogging = new RequestResponseLoggingBL();
        }

        public async Task Invoke(HttpContext context)
        {
            int result = await LogRequest(context);
            await LogResponse(context, result);
        }

        private async Task<int> LogRequest(HttpContext context)
        {
            context.Request.EnableBuffering();

            await using var requestStream = _recyclableMemoryStreamManager.GetStream();
            
            await context.Request.Body.CopyToAsync(requestStream);
            var requestBody = ReadStreamInChunks(requestStream);
            
            _logger.LogInformation($"Http Request Information:{Environment.NewLine}" +
                                   $"Schema:{context.Request.Scheme} " +
                                   $"Host: {context.Request.Host} " +
                                   $"Path: {context.Request.Path} " +
                                   $"QueryString: {context.Request.QueryString} " +
                                   $"Request Body: {requestBody}");
            int requestId = -1;
            if (context.Request.Path.Value.Contains("coreapi"))
            {
                if (!string.IsNullOrEmpty(context.Request.Path) && !string.IsNullOrWhiteSpace(context.Request.Path))
                {
                    requestId = _requestResponseLogging.CreateLog(context.Request.Method, $"{context.Request.Host}{context.Request.Path}",
                                        requestBody, context.Request.QueryString.ToString());
                }
            }
            //
            context.Request.Body.Position = 0;
            return requestId;
        }

        private async Task LogResponse(HttpContext context, int requestId)
        {
            var originalBodyStream = context.Response.Body;

            await using var responseBody = _recyclableMemoryStreamManager.GetStream();
            context.Response.Body = responseBody;

            await _next(context);

            context.Response.Body.Seek(0, SeekOrigin.Begin);
            var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
            context.Response.Body.Seek(0, SeekOrigin.Begin);

            _logger.LogInformation($"Http Response Information:{Environment.NewLine}" +
                                   $"Schema:{context.Request.Scheme} " +
                                   $"Host: {context.Request.Host} " +
                                   $"Path: {context.Request.Path} " +
                                   $"QueryString: {context.Request.QueryString} " +
                                   $"Response Body: {text}");
            // update log.
            if(requestId != -1)
            {
                await _requestResponseLogging.UpdateLog(requestId, text);
            }
            //
            await responseBody.CopyToAsync(originalBodyStream);
        }

        private static string ReadStreamInChunks(Stream stream)
        {
            const int readChunkBufferLength = 4096;

            stream.Seek(0, SeekOrigin.Begin);

            using var textWriter = new StringWriter();
            using var reader = new StreamReader(stream);

            var readChunk = new char[readChunkBufferLength];
            int readChunkLength;

            do
            {
                readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength);
                textWriter.Write(readChunk, 0, readChunkLength);
            } while (readChunkLength > 0);

            return textWriter.ToString();
        }
    }
}

RequestResponseLoggingMiddlewareExtensions.cs
using Microsoft.AspNetCore.Builder;

namespace MyCoreProject.Middleware
{
    public static class RequestResponseLoggingMiddlewareExtensions
    {
        public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestResponseLoggingMiddleware>();
        }
    }
}


startup.cs (configure method)
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();
            app.UseRequestResponseLogging();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");

                endpoints.MapControllerRoute(
                   name: "defaultCoreapi",                 
                   pattern: "coreapi/{controller=Home}/{action=Index}/{id?}");
            });
        }

Post a Comment