Skip to content
This repository was archived by the owner on Dec 19, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 75 additions & 19 deletions Application/EdFi.Ods.AdminApi/Features/RequestLoggingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,45 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Net;
using System.Text.Json;
using EdFi.Common.Utils.Extensions;
using EdFi.Ods.AdminApi.Common.Infrastructure.ErrorHandling;
using FluentValidation;
using System.Net;
using System.Text.Json;
using log4net;

namespace EdFi.Ods.AdminApi.Features;

public class RequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private static readonly ILog _logger = LogManager.GetLogger(typeof(RequestLoggingMiddleware));

public RequestLoggingMiddleware(RequestDelegate next)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
}

public async Task Invoke(HttpContext context, ILogger<RequestLoggingMiddleware> logger)
public async Task Invoke(HttpContext context)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This did not use dependency injection, but that's OK in this case, since we're not unit testing logging.

{
try
{
if (context.Request.Path.StartsWithSegments(new PathString("/.well-known")))
{
// Requests to the OpenId Connect ".well-known" endpoint are too chatty for informational logging, but could be useful in debug logging.
logger.LogDebug(JsonSerializer.Serialize(new { path = context.Request.Path.Value, traceId = context.TraceIdentifier }));
_logger.Debug(
JsonSerializer.Serialize(
new { path = context.Request.Path.Value, traceId = context.TraceIdentifier }
)
);
}
else
{
logger.LogInformation(JsonSerializer.Serialize(new { path = context.Request.Path.Value, traceId = context.TraceIdentifier }));
_logger.Info(
JsonSerializer.Serialize(
new { path = context.Request.Path.Value, traceId = context.TraceIdentifier }
)
);
}

// Check if this is a token endpoint request and intercept the response
Expand Down Expand Up @@ -89,7 +99,17 @@ public async Task Invoke(HttpContext context, ILogger<RequestLoggingMiddleware>
// Check if response has already started or stream is closed
if (response.HasStarted)
{
logger.LogError(ex, JsonSerializer.Serialize(new { message = "Cannot write to response, response has already started", error = new { ex.Message, ex.StackTrace }, traceId = context.TraceIdentifier }));
_logger.Error(
JsonSerializer.Serialize(
new
{
message = "Cannot write to response, response has already started",
error = new { ex.Message, ex.StackTrace },
traceId = context.TraceIdentifier
}
),
ex
);
return;
}

Expand All @@ -114,37 +134,73 @@ public async Task Invoke(HttpContext context, ILogger<RequestLoggingMiddleware>
});

#pragma warning disable S6667 // Logging in a catch clause should pass the caught exception as a parameter.
logger.LogDebug(JsonSerializer.Serialize(new { message = validationResponse, traceId = context.TraceIdentifier }));
_logger.Debug(
JsonSerializer.Serialize(
new { message = validationResponse, traceId = context.TraceIdentifier }
)
);
#pragma warning restore S6667 // Logging in a catch clause should pass the caught exception as a parameter.

response.StatusCode = (int)HttpStatusCode.BadRequest;
await response.WriteAsync(JsonSerializer.Serialize(validationResponse));
break;

case INotFoundException notFoundException:
var notFoundResponse = new
{
title = notFoundException.Message,
};
logger.LogDebug(JsonSerializer.Serialize(new { message = notFoundResponse, traceId = context.TraceIdentifier }));
var notFoundResponse = new { title = notFoundException.Message, };
_logger.Debug(
JsonSerializer.Serialize(
new { message = notFoundResponse, traceId = context.TraceIdentifier }
)
);

response.StatusCode = (int)HttpStatusCode.NotFound;
await response.WriteAsync(JsonSerializer.Serialize(notFoundResponse));
break;

case IAdminApiException adminApiException:
var message = adminApiException.StatusCode.HasValue && !string.IsNullOrWhiteSpace(adminApiException.Message)
? adminApiException.Message
: "The server encountered an unexpected condition that prevented it from fulfilling the request.";
logger.LogError(JsonSerializer.Serialize(new { message = "An uncaught error has occurred", error = new { ex.Message, ex.StackTrace }, traceId = context.TraceIdentifier }));
response.StatusCode = adminApiException.StatusCode.HasValue ? (int)adminApiException.StatusCode : 500;
var message =
adminApiException.StatusCode.HasValue
&& !string.IsNullOrWhiteSpace(adminApiException.Message)
? adminApiException.Message
: "The server encountered an unexpected condition that prevented it from fulfilling the request.";
_logger.Error(
JsonSerializer.Serialize(
new
{
message = "An uncaught error has occurred",
error = new { ex.Message, ex.StackTrace },
traceId = context.TraceIdentifier
}
),
ex
);
response.StatusCode = adminApiException.StatusCode.HasValue
? (int)adminApiException.StatusCode
: 500;
await response.WriteAsync(JsonSerializer.Serialize(new { message = message }));
break;

default:
logger.LogError(JsonSerializer.Serialize(new { message = "An uncaught error has occurred", error = new { ex.Message, ex.StackTrace }, traceId = context.TraceIdentifier }));
_logger.Error(
JsonSerializer.Serialize(
new
{
message = "An uncaught error has occurred",
error = new { ex.Message, ex.StackTrace },
traceId = context.TraceIdentifier
}
),
ex
);
response.StatusCode = (int)HttpStatusCode.InternalServerError;
await response.WriteAsync(JsonSerializer.Serialize(new { message = "The server encountered an unexpected condition that prevented it from fulfilling the request." }));
await response.WriteAsync(
JsonSerializer.Serialize(
new
{
message = "The server encountered an unexpected condition that prevented it from fulfilling the request."
}
)
);
break;
}
}
Expand Down
13 changes: 13 additions & 0 deletions Application/EdFi.Ods.AdminApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@
using EdFi.Ods.AdminApi.Features;
using EdFi.Ods.AdminApi.Infrastructure;
using log4net;
using log4net.Config;

var builder = WebApplication.CreateBuilder(args);

// Initialize log4net early so we can use it in Program.cs
var log4netConfigFileName = builder.Configuration.GetValue<string>("Log4NetCore:Log4NetConfigFileName");
if (!string.IsNullOrEmpty(log4netConfigFileName))
{
var log4netConfigPath = Path.Combine(AppContext.BaseDirectory, log4netConfigFileName);
if (File.Exists(log4netConfigPath))
{
var log4netConfig = new FileInfo(log4netConfigPath);
XmlConfigurator.Configure(LogManager.GetRepository(), log4netConfig);
}
}

// logging
var _logger = LogManager.GetLogger("Program");
_logger.Info("Starting Admin API");
Expand Down
2 changes: 1 addition & 1 deletion Application/EdFi.Ods.AdminApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api/",
"Log4NetCore": {
"Log4NetConfigFileName": "log4net\\log4net.config"
"Log4NetConfigFileName": "log4net/log4net.config"
},
"Logging": {
"LogLevel": {
Expand Down
6 changes: 3 additions & 3 deletions Application/EdFi.Ods.AdminApi/log4net/log4net.config
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="FileAppender" type="log4net.Appender.RollingFileAppender">
<file value="${ProgramData}\Ed-Fi-ODS-AdminAPI\Log.txt" />
<file value="logs/Ed-Fi-ODS-AdminAPI-Log.txt" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is truly a better default option.

<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
Expand All @@ -18,7 +18,7 @@
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="TraceAppender" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="FileAppender" />
</root>
</log4net>
Loading