NetCore_NetFramework

Request Response Logger .NET6

.NET6 adalah versi terbaru dari .NET yang dirilis pada November 2021. Tidak hanya itu, .NET 6 selain versi Framework yang jauh lebih baik dibandingkan dengan pendahulunya, tetapi juga memperkenalkan beberapa fitur paling keren yang pernah saya lihat di beberapa platform dan bahasa paling populer.

Tetapi pada artikel ini saya tidak akan menjelaskan

Tetapi pada artikel ini saya tidak akan menjelaskan lebih jauh mengenai .NET6 itu sendiri, melainkan ingin berbagi salah satu Fitur yang dapat dibuat oleh NET6 terutama pada aplikasi API.

Pada aplikasi API, tentunya hal yang paling penting adalah Request dan Response, tetapi selain itu, terkadang kita juga membutuhkan sebuah Logger untuk mencatat Request dan Response pada API kita.

Bagi Anda yang sering membuat API, tentunya sudah mengetahui bahwa Logger dapat ditambahkan pada masing-masing EndPoint atau secara keseluruhan pada level Middleware. Kali ini kita akan membuat Logger pada Level Middleware menggunakan NET6.

Source Code Asli nya saya dapatkan di StackOverflow, karena kebetulan pada saat itu saya memiliki kendala dimana request pada HttpContext tidak dapat dibaca saat menggunakan Delegate Invoke, ternyata di .NET6 context.Request.EnableRewind() / HttpRequest.EnableRewind() sudah tidak ada, melainkan menggunakan context.Request.EnableBuffering();

NET6 Request Response Logger Source Code

Pertama kita buat class AuditLog yang berfungsi sebagai penampungan object.

				
					public class AuditLog
    {
        public string HttpMethod { get; set; }
        public string RemoteHost { get; set; }
        public string QueryString { get; set; }
        public string HttpURL { get; set; }
        public string LocalAddress { get; set; }
        public string Headers { get; set; }
        public string Form { get; set; }
        public string ResponseBody { get; set; }
        public int ResponseStatusCode { get; set; }
        public int Port { get; set; }
        public DateTime RequestTime { get; set; }
        public DateTime ResponseTime { get; set; }

        public void save()
        {
            return;
        }

    }
				
			

Kemudian kita buat Class WSRequestData sebagai penampungan data Request.

				
					public class WSRequestData
    {
        public string header { get; set; }
        public string query { get; set; }
        public string body { get; set; }
    }
				
			

Selanjutnya kita juga buat 1 Class untuk menampung Response

				
					public class WSResponseData
    {
        public string statusCode { get; set; }
        public string body { get; set; }
    }
				
			

Setelah semua step diatas, selanjutnya kita buat Middleware / class utama.

				
					public class AccessLogMiddleware
    {
        private readonly RequestDelegate _next;
        private const int ReadChunkBufferLength = 4096;
        private const string ContentEncodingHeader = "Content-Encoding";
        private const string ContentEncodingGzip = "gzip";
        private const string ContentEncodingDeflate = "deflate";
        public AccessLogMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                EndpointMetadataCollection endpointMetaData = context.Features.Get<IEndpointFeature>()?.Endpoint.Metadata;

                context.Request.EnableBuffering();
                var auditLog = await LogRequest(context);
                await LogResponse(context, auditLog);
            }
            catch (UnauthorizedAccessException)
            {
                throw;
            }
            catch (Exception ex)
            {
                //Custom exception logging here
            }
        }

        public async Task<AuditLog> LogRequest(HttpContext context)
        {
            IHttpRequestFeature features = context.Features.Get<IHttpRequestFeature>();
            // string url = $"{features.Scheme}://{context.Request.Host.Value}{features.RawTarget}";
            string url = $"{features.Path}";

            IFormCollection form = null;
            string formString = string.Empty;

            if (context.Request.HasFormContentType)
            {
                form = context.Request.Form;
            }
            else
            {
                formString = await new StreamReader(context.Request.Body).ReadToEndAsync();
                var injectedRequestStream = new MemoryStream();
                byte[] bytesToWrite = Encoding.UTF8.GetBytes(formString);
                injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
                injectedRequestStream.Seek(0, SeekOrigin.Begin);
                context.Request.Body = injectedRequestStream;
            }

            System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("en-GB");

            return new AuditLog()
            {
                RemoteHost = context.Connection.RemoteIpAddress.ToString(),
                HttpURL = url,
                HttpMethod = context.Request.Method,
                Port = context.Request.Host.Port.HasValue ? context.Request.Host.Port.Value : 80,
                LocalAddress = context.Connection.LocalIpAddress.ToString(),
                QueryString = $"{context.Request.QueryString}",
                Headers = Newtonsoft.Json.JsonConvert.SerializeObject(context.Request.Headers, Formatting.None),
                Form = form != null ? Newtonsoft.Json.JsonConvert.SerializeObject(form) : formString,
                RequestTime = DateTime.Parse(DateTime.Now.ToString("FORMAT TANGGAL"), cultureinfo)
            };
        }

        public async Task LogResponse(HttpContext context, AuditLog auditLog)
        {


            if (auditLog == null)
            {
                await _next(context);
                return;
            }

            if (context.Request.Path.HasValue)
            {
				// Swagger page won't log
                if (!context.Request.Path.Value.ToString().Contains("/api/"))
                {
                    await _next(context);
                    return;
                }
				// Swagger page won't log, you can delete
                if (context.Request.Path.Value.ToString().StartsWith("/docs/api/"))
                {
                    await _next(context);
                    return;
                }
            }

            Stream originalBody = context.Response.Body;

            try
            {
                using (var memStream = new MemoryStream())
                {
                    context.Response.Body = memStream;

                    await _next(context);

                    memStream.Position = 0;
                    string responseBody = new StreamReader(memStream).ReadToEnd();

                    auditLog.ResponseStatusCode = context.Response.StatusCode;
                    auditLog.ResponseBody = responseBody;


                    memStream.Position = 0;
                    await memStream.CopyToAsync(originalBody);

                    System.Globalization.CultureInfo cultureinfo = new System.Globalization.CultureInfo("en-GB");
                    auditLog.ResponseTime = DateTime.Parse(DateTime.Now.ToString("Your Date Format"), cultureinfo);
                    WSRequestData requestData = new WSRequestData
                    {
                        header = auditLog.Headers,
                        query = auditLog.QueryString,
                        body = auditLog.Form
                    };

                    WSResponseData responseData = new WSResponseData();
                    responseData.statusCode = auditLog.ResponseStatusCode.ToString();
                    responseData.body = auditLog.ResponseBody;

                    this.save(auditLog, requestData, responseData);

                }
            }
            catch
            {
                auditLog?.save();
                throw;
            }
            finally
            {
                context.Response.Body = originalBody;
            }
        }

        private void save(AuditLog auditLog, WSRequestData request, WSResponseData response)
        {
            string ReferenceNo = $"{auditLog.HttpMethod}";
            if (!string.IsNullOrEmpty(auditLog.RemoteHost))
            {
                if (auditLog.RemoteHost.Equals("::1") || auditLog.RemoteHost.Equals("127.0.0.1"))
                {
                    ReferenceNo = $"127.0.0.1";
                }
                else
                {
                    ReferenceNo = $"{auditLog.RemoteHost}:{auditLog.Port}";
                }
            }

            string _form = string.IsNullOrEmpty(auditLog.Form) ? null : auditLog.Form;
            string _queryString = string.IsNullOrEmpty(auditLog.QueryString) ? null : auditLog.QueryString;
            string _requestData = string.Empty;
            if (!string.IsNullOrEmpty(_form) && !string.IsNullOrEmpty(_queryString))
            {
                _requestData = $"QREQ: {_queryString} , JSONREQ: {_form}";
            }
            else
            {
                if (!string.IsNullOrEmpty(_form) && string.IsNullOrEmpty(_queryString))
                {
                    _requestData = _form;
                }

                if (string.IsNullOrEmpty(_form) && !string.IsNullOrEmpty(_queryString))
                {
                    _requestData = _queryString;
                }
            }
			
			// Save to Database ... 
            LogRequest logRequest = new LogRequest();
            logRequest.RemoteIPServerPort = ReferenceNo;
            logRequest.Path = $"{auditLog.HttpMethod} {auditLog.HttpURL}";
            logRequest.RequestData = _requestData; //, // Newtonsoft.Json.JsonConvert.SerializeObject(request);
            // logRequest.ResponseData = Newtonsoft.Json.JsonConvert.SerializeObject(response);
            logRequest.ResponseData = response.body;
            logRequest.RequestTime = auditLog.RequestTime;
            logRequest.ResponseTime = auditLog.ResponseTime;
			
			// do something with logRequest
        }
    }
				
			

Sebelum dapat digunakan, panggil class Middleware pada file startup.cs. Jika timbul pertanyaan kenapa NET6 masih menggunakan file startup.cs, karena project ini merupakan hasil upgrade dari NET5, sehingga program.cs dan startup.cs masih terpisah.

				
					public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider, IHttpContextAccessor accessor) {
        app.UseMiddleware<AccessLogMiddleware>();
    }
				
			
Source: https://stackoverflow.com/questions/71082172/net-6-custom-middleware-logging-mixing-responses-and-requests/71082173
Author: https://stackoverflow.com/users/2286743/marius

About suhendrayputra

Avatar of suhendrayputra
Konsultan IT Bandung, Freelance web developer Kota Bandung. Jasa Pembuatan Website dan Aplikasi Sistem Informasi. #java #aspnet #wordPress

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *