From 86758ee4070810c4abcac84414538e1477c96bbd Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 09:51:13 -0400 Subject: [PATCH 01/14] Add logging option model --- .../Utilities/Logger/RequestResponseLoggerOptionModel.cs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Gordon360/Utilities/Logger/RequestResponseLoggerOptionModel.cs diff --git a/Gordon360/Utilities/Logger/RequestResponseLoggerOptionModel.cs b/Gordon360/Utilities/Logger/RequestResponseLoggerOptionModel.cs new file mode 100644 index 000000000..1ddfe4420 --- /dev/null +++ b/Gordon360/Utilities/Logger/RequestResponseLoggerOptionModel.cs @@ -0,0 +1,9 @@ +namespace Gordon360.Utilities.Logger +{ + public class RequestResponseLoggerOptionModel + { + public bool IsEnabled { get; set; } + public string Name { get; set; } + public string DateTimeFormat { get; set; } + } +} From 64d921cfcf3abe3562771f26daf294408c3333bf Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 09:51:46 -0400 Subject: [PATCH 02/14] Add request response log type --- .../Logger/RequestResponseLogModel.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Gordon360/Utilities/Logger/RequestResponseLogModel.cs diff --git a/Gordon360/Utilities/Logger/RequestResponseLogModel.cs b/Gordon360/Utilities/Logger/RequestResponseLogModel.cs new file mode 100644 index 000000000..b08a64dcb --- /dev/null +++ b/Gordon360/Utilities/Logger/RequestResponseLogModel.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Gordon360.Utilities.Logger +{ + public class RequestResponseLogModel + { + public string LogId { get; set; } /*Guid.NewGuid().ToString()*/ + public string Node { get; set; } /*project name*/ + public string ClientIp { get; set; } + public string TraceId { get; set; } /*HttpContext TraceIdentifier*/ + + + public DateTime? RequestDateTimeUtc { get; set; } + public DateTime? RequestDateTimeUtcActionLevel { get; set; } + public string RequestPath { get; set; } + public string RequestQuery { get; set; } + public List> RequestQueries { get; set; } + public string RequestMethod { get; set; } + public string RequestScheme { get; set; } + public string RequestHost { get; set; } + public Dictionary RequestHeaders { get; set; } + public string RequestBody { get; set; } + public string RequestContentType { get; set; } + + + public DateTime? ResponseDateTimeUtc { get; set; } + public DateTime? ResponseDateTimeUtcActionLevel { get; set; } + public string ResponseStatus { get; set; } + public Dictionary ResponseHeaders { get; set; } + // public string ResponseBody { get; set; } + public string ResponseContentType { get; set; } + + + public bool? IsExceptionActionLevel { get; set; } + public string ExceptionMessage { get; set; } + public string ExceptionStackTrace { get; set; } + + public RequestResponseLogModel() + { + LogId = Guid.NewGuid().ToString(); + } + } +} From 371504cf58e3a2cc2023b7d7a8a4c9f5a36d07ee Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 09:54:41 -0400 Subject: [PATCH 03/14] Add factory middleware initialization --- Gordon360/Program.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Gordon360/Program.cs b/Gordon360/Program.cs index 41baa575c..d146e47b8 100644 --- a/Gordon360/Program.cs +++ b/Gordon360/Program.cs @@ -16,6 +16,8 @@ using Microsoft.OpenApi.Models; using System.Collections.Generic; using System; +using Gordon360.Utilities.Logger; +using Gordon360.Extensions; var builder = WebApplication.CreateBuilder(args); @@ -75,7 +77,7 @@ corsBuilder.WithOrigins(builder.Configuration.GetValue("AllowedOrigin")).AllowAnyMethod().AllowAnyHeader(); })); -builder.Services.AddDbContext(options => +builder.Services.AddDbContextFactory(options => options.UseSqlServer(builder.Configuration.GetConnectionString("CCT")) ).AddDbContext(options => options.UseSqlServer(builder.Configuration.GetConnectionString("MyGordon")) @@ -106,6 +108,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); +/* Logging Options */ +builder.Services.AddOptions().Bind +(builder.Configuration.GetSection("RequestResponseLogger")).ValidateDataAnnotations(); + +builder.Services.AddTransient(); + builder.Services.AddMemoryCache(); var app = builder.Build(); @@ -136,4 +144,6 @@ app.MapControllers(); +app.UseFactoryActivatedMiddleware(); + app.Run(); From 8052eec732bfad9c2d58a8b81cf860ee6e2d1574 Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 09:54:59 -0400 Subject: [PATCH 04/14] Add middleware extensions for factory logger middleware --- Gordon360/Extensions/MiddlewareExtensions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 Gordon360/Extensions/MiddlewareExtensions.cs diff --git a/Gordon360/Extensions/MiddlewareExtensions.cs b/Gordon360/Extensions/MiddlewareExtensions.cs new file mode 100644 index 000000000..53496130d --- /dev/null +++ b/Gordon360/Extensions/MiddlewareExtensions.cs @@ -0,0 +1,14 @@ + +using Gordon360.Utilities.Logger; +using Microsoft.AspNetCore.Builder; + +namespace Gordon360.Extensions +{ + public static class MiddlewareExtensions + { + public static IApplicationBuilder UseFactoryActivatedMiddleware( + this IApplicationBuilder app) + => app.UseMiddleware(); + } + +} From 2323bef5e3870de48aeea33f85e55d57609487fb Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 09:55:26 -0400 Subject: [PATCH 05/14] Add factory middleware --- .../Utilities/Logger/LoggerMiddleware.cs | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 Gordon360/Utilities/Logger/LoggerMiddleware.cs diff --git a/Gordon360/Utilities/Logger/LoggerMiddleware.cs b/Gordon360/Utilities/Logger/LoggerMiddleware.cs new file mode 100644 index 000000000..a12313740 --- /dev/null +++ b/Gordon360/Utilities/Logger/LoggerMiddleware.cs @@ -0,0 +1,154 @@ +using Gordon360.Models.CCT.Context; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Gordon360.Utilities.Logger +{ + public class RequestResponseLoggerMiddleware : IMiddleware + { + private readonly RequestResponseLoggerOptionModel _options; + private readonly CCTContext _context; + public RequestResponseLoggerMiddleware + (IOptions options, CCTContext context) + { + _options = options.Value; + _context = context; + } + + public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next/*, + IRequestResponseLogModelCreator logCreator*/) + { + RequestResponseLogModel log = new RequestResponseLogModel(); + // Middleware is enabled only when the + // EnableRequestResponseLogging config value is set. + if (_options == null || !_options.IsEnabled) + { + await next(httpContext); + return; + } + log.RequestDateTimeUtc = DateTime.UtcNow; + HttpRequest request = httpContext.Request; + + /*log*/ + log.LogId = Guid.NewGuid().ToString(); + log.TraceId = httpContext.TraceIdentifier; + var ip = request.HttpContext.Connection.RemoteIpAddress; + log.ClientIp = ip == null ? null : ip.ToString(); + log.Node = _options.Name; + + /*request*/ + log.RequestMethod = request.Method; + log.RequestPath = request.Path; + log.RequestQuery = request.QueryString.ToString(); + log.RequestQueries = FormatQueries(request.QueryString.ToString()); + log.RequestHeaders = FormatHeaders(request.Headers); + log.RequestBody = await ReadBodyFromRequest(request); + log.RequestScheme = request.Scheme; + log.RequestHost = request.Host.ToString(); + log.RequestContentType = request.ContentType; + + // Temporarily replace the HttpResponseStream, + // which is a write-only stream, with a MemoryStream to capture + // its value in-flight. + HttpResponse response = httpContext.Response; + var originalResponseBody = response.Body; + using var newResponseBody = new MemoryStream(); + response.Body = newResponseBody; + + // Call the next middleware in the pipeline + try + { + await next(httpContext); + } + catch (Exception exception) + { + /*exception: but was not managed at app.UseExceptionHandler() + or by any middleware*/ + LogError(log, exception); + } + + newResponseBody.Seek(0, SeekOrigin.Begin); + var responseBodyText = + await new StreamReader(response.Body).ReadToEndAsync(); + + newResponseBody.Seek(0, SeekOrigin.Begin); + await newResponseBody.CopyToAsync(originalResponseBody); + + log.ResponseContentType = response.ContentType; + log.ResponseStatus = response.StatusCode.ToString(); + log.ResponseHeaders = FormatHeaders(response.Headers); + // Reponse body is far too expensive to be holding onto. + // log.ResponseBody = responseBodyText; + log.ResponseDateTimeUtc = DateTime.UtcNow; + + + /*exception: but was managed at app.UseExceptionHandler() + or by any middleware*/ + var contextFeature = + httpContext.Features.Get(); + if (contextFeature != null && contextFeature.Error != null) + { + Exception exception = contextFeature.Error; + LogError(log, exception); + } + var i = _context.ParticipantStatus; + } + + private void LogError(RequestResponseLogModel log, Exception exception) + { + log.ExceptionMessage = exception.Message; + log.ExceptionStackTrace = exception.StackTrace; + } + + private Dictionary FormatHeaders(IHeaderDictionary headers) + { + Dictionary pairs = new Dictionary(); + foreach (var header in headers) + { + pairs.Add(header.Key, header.Value); + } + return pairs; + } + + private List> FormatQueries(string queryString) + { + List> pairs = + new List>(); + string key, value; + foreach (var query in queryString.TrimStart('?').Split("&")) + { + var items = query.Split("="); + key = items.Count() >= 1 ? items[0] : string.Empty; + value = items.Count() >= 2 ? items[1] : string.Empty; + if (!String.IsNullOrEmpty(key)) + { + pairs.Add(new KeyValuePair(key, value)); + } + } + return pairs; + } + + private async Task ReadBodyFromRequest(HttpRequest request) + { + // Ensure the request's body can be read multiple times + // (for the next middlewares in the pipeline). + request.EnableBuffering(); + using var streamReader = new StreamReader(request.Body, leaveOpen: true); + var requestBody = await streamReader.ReadToEndAsync(); + // Reset the request's body stream position for + // next middleware in the pipeline. + request.Body.Position = 0; + return requestBody; + } + } +} \ No newline at end of file From b59712da81196f13c866a8375809371f596b472e Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 10:58:18 -0400 Subject: [PATCH 06/14] Add core logging middleware --- Gordon360/Utilities/Logger/LoggerMiddleware.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Gordon360/Utilities/Logger/LoggerMiddleware.cs b/Gordon360/Utilities/Logger/LoggerMiddleware.cs index a12313740..6b763babc 100644 --- a/Gordon360/Utilities/Logger/LoggerMiddleware.cs +++ b/Gordon360/Utilities/Logger/LoggerMiddleware.cs @@ -25,8 +25,7 @@ public RequestResponseLoggerMiddleware _context = context; } - public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next/*, - IRequestResponseLogModelCreator logCreator*/) + public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { RequestResponseLogModel log = new RequestResponseLogModel(); // Middleware is enabled only when the @@ -101,7 +100,7 @@ or by any middleware*/ Exception exception = contextFeature.Error; LogError(log, exception); } - var i = _context.ParticipantStatus; + } private void LogError(RequestResponseLogModel log, Exception exception) From 128309b4576470d0ea80b87b7a88cb581acdd6e8 Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 11:34:03 -0400 Subject: [PATCH 07/14] Add logging sql model --- Gordon360/Models/CCT/Context/CCTContext.cs | 10 ++-- .../Models/CCT/Context/efpt.CCT.config.json | 6 ++- .../Models/CCT/dbo/RequestResponseLog.cs | 46 +++++++++++++++++++ 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 Gordon360/Models/CCT/dbo/RequestResponseLog.cs diff --git a/Gordon360/Models/CCT/Context/CCTContext.cs b/Gordon360/Models/CCT/Context/CCTContext.cs index 83d831692..bc59f0c44 100644 --- a/Gordon360/Models/CCT/Context/CCTContext.cs +++ b/Gordon360/Models/CCT/Context/CCTContext.cs @@ -80,6 +80,7 @@ public CCTContext(DbContextOptions options) public virtual DbSet Police { get; set; } public virtual DbSet PrivType { get; set; } public virtual DbSet REQUEST { get; set; } + public virtual DbSet RequestResponseLog { get; set; } public virtual DbSet RequestView { get; set; } public virtual DbSet RoleType { get; set; } public virtual DbSet RoomAssign { get; set; } @@ -399,8 +400,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.PART_CDE).IsFixedLength(); entity.Property(e => e.SESS_CDE).IsFixedLength(); - - entity.Property(e => e.USER_NAME).IsFixedLength(); }); modelBuilder.Entity(entity => @@ -509,9 +508,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { - entity.HasKey(e => e.Username) - .HasName("PK__Particip__536C85E53B50E910"); - entity.Property(e => e.ID).ValueGeneratedOnAdd(); }); @@ -525,6 +521,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasOne(d => d.ParticipantUsernameNavigation) .WithMany(p => p.ParticipantActivity) .HasForeignKey(d => d.ParticipantUsername) + .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK_ParticipantActivity_Participant"); entity.HasOne(d => d.PrivType) @@ -540,7 +537,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany(p => p.ParticipantNotification) .HasForeignKey(d => d.ParticipantUsername) .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("FK_PartipantNotification_Participant"); + .HasConstraintName("FK_ParticipantNotification_Participant"); }); modelBuilder.Entity(entity => @@ -563,6 +560,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasOne(d => d.ParticipantUsernameNavigation) .WithMany(p => p.ParticipantTeam) .HasForeignKey(d => d.ParticipantUsername) + .OnDelete(DeleteBehavior.ClientSetNull) .HasConstraintName("FK_ParticipantTeam_Participant"); entity.HasOne(d => d.RoleType) diff --git a/Gordon360/Models/CCT/Context/efpt.CCT.config.json b/Gordon360/Models/CCT/Context/efpt.CCT.config.json index 78dc7de30..2b3cf6a9c 100644 --- a/Gordon360/Models/CCT/Context/efpt.CCT.config.json +++ b/Gordon360/Models/CCT/Context/efpt.CCT.config.json @@ -94,6 +94,10 @@ "Name": "[dbo].[REQUEST]", "ObjectType": 0 }, + { + "Name": "[dbo].[RequestResponseLog]", + "ObjectType": 0 + }, { "Name": "[dbo].[Rooms]", "ObjectType": 0 @@ -799,7 +803,7 @@ "ObjectType": 1 } ], - "UiHint": "SQLTrain1.CCT", + "UiHint": "sqltrain1.CCT.dbo", "UseBoolPropertiesWithoutDefaultSql": false, "UseDatabaseNames": true, "UseDbContextSplitting": false, diff --git a/Gordon360/Models/CCT/dbo/RequestResponseLog.cs b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs new file mode 100644 index 000000000..3ec76f495 --- /dev/null +++ b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs @@ -0,0 +1,46 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Gordon360.Models.CCT +{ + [Table("RequestResponseLog", Schema = "dbo")] + public partial class RequestResponseLog + { + [Key] + [StringLength(36)] + [Unicode(false)] + public string LogID { get; set; } + [Required] + [StringLength(15)] + [Unicode(false)] + public string ClientIP { get; set; } + [Column(TypeName = "datetime")] + public DateTime RequestDate { get; set; } + [Required] + [StringLength(510)] + [Unicode(false)] + public string UserAgent { get; set; } + [Required] + [StringLength(63)] + [Unicode(false)] + public string RequestHost { get; set; } + [Required] + [StringLength(7)] + [Unicode(false)] + public string RequestMethod { get; set; } + [Required] + [StringLength(100)] + [Unicode(false)] + public string RequestPath { get; set; } + [StringLength(510)] + [Unicode(false)] + public string RequestQuery { get; set; } + public int ResponseStatus { get; set; } + public int? ResponseContentLength { get; set; } + } +} \ No newline at end of file From 433bed2fd87d005e9639d1ce4fb31ffac5fcfad6 Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 11:39:37 -0400 Subject: [PATCH 08/14] Update column name --- Gordon360/Models/CCT/dbo/RequestResponseLog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gordon360/Models/CCT/dbo/RequestResponseLog.cs b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs index 3ec76f495..c874bf877 100644 --- a/Gordon360/Models/CCT/dbo/RequestResponseLog.cs +++ b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs @@ -20,7 +20,7 @@ public partial class RequestResponseLog [Unicode(false)] public string ClientIP { get; set; } [Column(TypeName = "datetime")] - public DateTime RequestDate { get; set; } + public DateTime RequestDateTime { get; set; } [Required] [StringLength(510)] [Unicode(false)] From 834f86f75cd9a94cace90367f0cc156733e5673d Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 11:51:55 -0400 Subject: [PATCH 09/14] Add request body to model --- Gordon360/Models/CCT/dbo/RequestResponseLog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gordon360/Models/CCT/dbo/RequestResponseLog.cs b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs index c874bf877..288372ca6 100644 --- a/Gordon360/Models/CCT/dbo/RequestResponseLog.cs +++ b/Gordon360/Models/CCT/dbo/RequestResponseLog.cs @@ -40,6 +40,8 @@ public partial class RequestResponseLog [StringLength(510)] [Unicode(false)] public string RequestQuery { get; set; } + [Unicode(false)] + public string RequestBody { get; set; } public int ResponseStatus { get; set; } public int? ResponseContentLength { get; set; } } From d9fd6cb0bc9b3d1bd3335a7f21d4ef34a87c7444 Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 12:09:44 -0400 Subject: [PATCH 10/14] Remove now defunct vm --- .../Logger/RequestResponseLogModel.cs | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 Gordon360/Utilities/Logger/RequestResponseLogModel.cs diff --git a/Gordon360/Utilities/Logger/RequestResponseLogModel.cs b/Gordon360/Utilities/Logger/RequestResponseLogModel.cs deleted file mode 100644 index b08a64dcb..000000000 --- a/Gordon360/Utilities/Logger/RequestResponseLogModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Gordon360.Utilities.Logger -{ - public class RequestResponseLogModel - { - public string LogId { get; set; } /*Guid.NewGuid().ToString()*/ - public string Node { get; set; } /*project name*/ - public string ClientIp { get; set; } - public string TraceId { get; set; } /*HttpContext TraceIdentifier*/ - - - public DateTime? RequestDateTimeUtc { get; set; } - public DateTime? RequestDateTimeUtcActionLevel { get; set; } - public string RequestPath { get; set; } - public string RequestQuery { get; set; } - public List> RequestQueries { get; set; } - public string RequestMethod { get; set; } - public string RequestScheme { get; set; } - public string RequestHost { get; set; } - public Dictionary RequestHeaders { get; set; } - public string RequestBody { get; set; } - public string RequestContentType { get; set; } - - - public DateTime? ResponseDateTimeUtc { get; set; } - public DateTime? ResponseDateTimeUtcActionLevel { get; set; } - public string ResponseStatus { get; set; } - public Dictionary ResponseHeaders { get; set; } - // public string ResponseBody { get; set; } - public string ResponseContentType { get; set; } - - - public bool? IsExceptionActionLevel { get; set; } - public string ExceptionMessage { get; set; } - public string ExceptionStackTrace { get; set; } - - public RequestResponseLogModel() - { - LogId = Guid.NewGuid().ToString(); - } - } -} From ce2d8b1ce4c62c85a512d29b491f2c626b72022c Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 12:28:31 -0400 Subject: [PATCH 11/14] Simplify object type and maintained response --- .../Utilities/Logger/LoggerMiddleware.cs | 83 +++++-------------- 1 file changed, 23 insertions(+), 60 deletions(-) diff --git a/Gordon360/Utilities/Logger/LoggerMiddleware.cs b/Gordon360/Utilities/Logger/LoggerMiddleware.cs index 6b763babc..5f10262d7 100644 --- a/Gordon360/Utilities/Logger/LoggerMiddleware.cs +++ b/Gordon360/Utilities/Logger/LoggerMiddleware.cs @@ -1,4 +1,5 @@ -using Gordon360.Models.CCT.Context; +using Gordon360.Models.CCT; +using Gordon360.Models.CCT.Context; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.Data.SqlClient; @@ -27,7 +28,7 @@ public RequestResponseLoggerMiddleware public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { - RequestResponseLogModel log = new RequestResponseLogModel(); + RequestResponseLog log = new RequestResponseLog(); // Middleware is enabled only when the // EnableRequestResponseLogging config value is set. if (_options == null || !_options.IsEnabled) @@ -35,26 +36,22 @@ public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) await next(httpContext); return; } - log.RequestDateTimeUtc = DateTime.UtcNow; + log.RequestDateTime = DateTime.UtcNow; HttpRequest request = httpContext.Request; /*log*/ - log.LogId = Guid.NewGuid().ToString(); - log.TraceId = httpContext.TraceIdentifier; + log.LogID = Guid.NewGuid().ToString(); var ip = request.HttpContext.Connection.RemoteIpAddress; - log.ClientIp = ip == null ? null : ip.ToString(); - log.Node = _options.Name; + log.ClientIP = ip == null ? "" : ip.ToString(); + request.HttpContext.Connection. /*request*/ log.RequestMethod = request.Method; log.RequestPath = request.Path; log.RequestQuery = request.QueryString.ToString(); - log.RequestQueries = FormatQueries(request.QueryString.ToString()); - log.RequestHeaders = FormatHeaders(request.Headers); + log.UserAgent = GetUserAgent(request.Headers); log.RequestBody = await ReadBodyFromRequest(request); - log.RequestScheme = request.Scheme; log.RequestHost = request.Host.ToString(); - log.RequestContentType = request.ContentType; // Temporarily replace the HttpResponseStream, // which is a write-only stream, with a MemoryStream to capture @@ -69,11 +66,9 @@ public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { await next(httpContext); } - catch (Exception exception) + catch { - /*exception: but was not managed at app.UseExceptionHandler() - or by any middleware*/ - LogError(log, exception); + } newResponseBody.Seek(0, SeekOrigin.Begin); @@ -83,58 +78,26 @@ or by any middleware*/ newResponseBody.Seek(0, SeekOrigin.Begin); await newResponseBody.CopyToAsync(originalResponseBody); - log.ResponseContentType = response.ContentType; - log.ResponseStatus = response.StatusCode.ToString(); - log.ResponseHeaders = FormatHeaders(response.Headers); - // Reponse body is far too expensive to be holding onto. - // log.ResponseBody = responseBodyText; - log.ResponseDateTimeUtc = DateTime.UtcNow; - - - /*exception: but was managed at app.UseExceptionHandler() - or by any middleware*/ - var contextFeature = - httpContext.Features.Get(); - if (contextFeature != null && contextFeature.Error != null) - { - Exception exception = contextFeature.Error; - LogError(log, exception); - } - + log.ResponseStatus = response.StatusCode; + log.ResponseContentLength = GetResponseContentLength(response.Headers); + _context.RequestResponseLog.Add(log); + _context.SaveChanges(); } - private void LogError(RequestResponseLogModel log, Exception exception) + private string GetUserAgent(IHeaderDictionary headers) { - log.ExceptionMessage = exception.Message; - log.ExceptionStackTrace = exception.StackTrace; + foreach (var header in headers) + if(header.Key == "User-Agent") + return header.Value; + return ""; } - private Dictionary FormatHeaders(IHeaderDictionary headers) + private int GetResponseContentLength(IHeaderDictionary headers) { - Dictionary pairs = new Dictionary(); foreach (var header in headers) - { - pairs.Add(header.Key, header.Value); - } - return pairs; - } - - private List> FormatQueries(string queryString) - { - List> pairs = - new List>(); - string key, value; - foreach (var query in queryString.TrimStart('?').Split("&")) - { - var items = query.Split("="); - key = items.Count() >= 1 ? items[0] : string.Empty; - value = items.Count() >= 2 ? items[1] : string.Empty; - if (!String.IsNullOrEmpty(key)) - { - pairs.Add(new KeyValuePair(key, value)); - } - } - return pairs; + if (header.Key == "Content-Length") + return Int32.Parse(header.Value); + return 0; } private async Task ReadBodyFromRequest(HttpRequest request) From 57f2632e181681cf4cc1338829daafbec1ec0d4c Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 12:43:16 -0400 Subject: [PATCH 12/14] Add documentation --- .../Utilities/Logger/LoggerMiddleware.cs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/Gordon360/Utilities/Logger/LoggerMiddleware.cs b/Gordon360/Utilities/Logger/LoggerMiddleware.cs index 5f10262d7..9be4f439d 100644 --- a/Gordon360/Utilities/Logger/LoggerMiddleware.cs +++ b/Gordon360/Utilities/Logger/LoggerMiddleware.cs @@ -5,6 +5,7 @@ using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -26,6 +27,12 @@ public RequestResponseLoggerMiddleware _context = context; } + /// + /// Invoke Async is called on all HTTP requests and are intercepted by httpContext pipelining + /// + /// + /// + /// public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) { RequestResponseLog log = new RequestResponseLog(); @@ -43,13 +50,12 @@ public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) log.LogID = Guid.NewGuid().ToString(); var ip = request.HttpContext.Connection.RemoteIpAddress; log.ClientIP = ip == null ? "" : ip.ToString(); - request.HttpContext.Connection. /*request*/ log.RequestMethod = request.Method; log.RequestPath = request.Path; log.RequestQuery = request.QueryString.ToString(); - log.UserAgent = GetUserAgent(request.Headers); + log.UserAgent = request.Headers.UserAgent; log.RequestBody = await ReadBodyFromRequest(request); log.RequestHost = request.Host.ToString(); @@ -84,19 +90,27 @@ public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) _context.SaveChanges(); } + /// + /// Picks User-Agent out of given headers, defaults to empty string + /// + /// + /// private string GetUserAgent(IHeaderDictionary headers) { - foreach (var header in headers) - if(header.Key == "User-Agent") - return header.Value; + if (headers.UserAgent is StringValues UA) + return UA; return ""; } + /// + /// Returns length of response content, defaults to 0 + /// + /// + /// private int GetResponseContentLength(IHeaderDictionary headers) { - foreach (var header in headers) - if (header.Key == "Content-Length") - return Int32.Parse(header.Value); + if (headers.ContentLength is long contentLenth) + return (int)contentLenth; return 0; } From 01a336c67a8de6ffa1e3a5ca9ac4f7f0ae887a92 Mon Sep 17 00:00:00 2001 From: amos-cha Date: Thu, 13 Jul 2023 12:44:23 -0400 Subject: [PATCH 13/14] Remove unused helper method --- Gordon360/Utilities/Logger/LoggerMiddleware.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Gordon360/Utilities/Logger/LoggerMiddleware.cs b/Gordon360/Utilities/Logger/LoggerMiddleware.cs index 9be4f439d..226345762 100644 --- a/Gordon360/Utilities/Logger/LoggerMiddleware.cs +++ b/Gordon360/Utilities/Logger/LoggerMiddleware.cs @@ -90,18 +90,6 @@ public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) _context.SaveChanges(); } - /// - /// Picks User-Agent out of given headers, defaults to empty string - /// - /// - /// - private string GetUserAgent(IHeaderDictionary headers) - { - if (headers.UserAgent is StringValues UA) - return UA; - return ""; - } - /// /// Returns length of response content, defaults to 0 /// From 140a75ac81167598bed2e8d9fd6940fb28d5215b Mon Sep 17 00:00:00 2001 From: Amos Cha <78386128+amos-cha@users.noreply.github.com> Date: Thu, 20 Jul 2023 20:06:22 -0400 Subject: [PATCH 14/14] Update Gordon360/Extensions/MiddlewareExtensions.cs Co-authored-by: Russ Tuck --- Gordon360/Extensions/MiddlewareExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gordon360/Extensions/MiddlewareExtensions.cs b/Gordon360/Extensions/MiddlewareExtensions.cs index 53496130d..1cb32a614 100644 --- a/Gordon360/Extensions/MiddlewareExtensions.cs +++ b/Gordon360/Extensions/MiddlewareExtensions.cs @@ -1,7 +1,8 @@  using Gordon360.Utilities.Logger; using Microsoft.AspNetCore.Builder; - +// See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/extensibility?view=aspnetcore-6.0 +// for background on how this works. namespace Gordon360.Extensions { public static class MiddlewareExtensions