diff --git a/Directory.Packages.props b/Directory.Packages.props
index b987d47c..20d604e4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs b/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs
index 19247afc..9bb4831a 100644
--- a/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs
+++ b/src/Recyclarr.ServarrApi/ApiServicesAutofacModule.cs
@@ -2,6 +2,7 @@ using Autofac;
using Flurl.Http.Configuration;
using Recyclarr.ServarrApi.CustomFormat;
using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.ServarrApi.QualityDefinition;
using Recyclarr.ServarrApi.QualityProfile;
@@ -26,5 +27,11 @@ public class ApiServicesAutofacModule : Module
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterType().As();
+
+ builder.RegisterTypes(
+ typeof(FlurlAfterCallHandler),
+ typeof(FlurlBeforeCallHandler),
+ typeof(FlurlRedirectHandler))
+ .As();
}
}
diff --git a/src/Recyclarr.ServarrApi/CustomFormat/CustomFormatApiService.cs b/src/Recyclarr.ServarrApi/CustomFormat/CustomFormatApiService.cs
index 3fb26cd6..7db5681d 100644
--- a/src/Recyclarr.ServarrApi/CustomFormat/CustomFormatApiService.cs
+++ b/src/Recyclarr.ServarrApi/CustomFormat/CustomFormatApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.ServarrApi.CustomFormat;
diff --git a/src/Recyclarr.ServarrApi/Http/FlurlLogging.cs b/src/Recyclarr.ServarrApi/Http/FlurlLogging.cs
index 42abd18a..82073fb7 100644
--- a/src/Recyclarr.ServarrApi/Http/FlurlLogging.cs
+++ b/src/Recyclarr.ServarrApi/Http/FlurlLogging.cs
@@ -1,49 +1,12 @@
using System.Text.Json;
using Flurl;
-using Flurl.Http.Configuration;
using Serilog;
namespace Recyclarr.ServarrApi.Http;
public static class FlurlLogging
{
- public static void SetupLogging(FlurlHttpSettings settings, ILogger log, Func? urlInterceptor = null)
- {
- urlInterceptor ??= SanitizeUrl;
-
- settings.BeforeCall = call =>
- {
- var url = urlInterceptor(call.Request.Url.Clone());
- log.Debug("HTTP Request: {Method} {Url}", call.HttpRequestMessage.Method, url);
- LogBody(log, url, "Request", call.HttpRequestMessage.Method, call.RequestBody);
- };
-
- settings.AfterCallAsync = async call =>
- {
- var statusCode = call.Response?.StatusCode.ToString() ?? "(No response)";
- var url = urlInterceptor(call.Request.Url.Clone());
- log.Debug("HTTP Response: {Status} {Method} {Url}", statusCode, call.HttpRequestMessage.Method, url);
-
- var content = call.Response?.ResponseMessage.Content;
- if (content is not null)
- {
- LogBody(log, url, "Response", call.HttpRequestMessage.Method, await content.ReadAsStringAsync());
- }
- };
-
- settings.OnRedirect = call =>
- {
- log.Warning("HTTP Redirect received; this indicates a problem with your URL and/or reverse proxy: {Url}",
- urlInterceptor(call.Redirect.Url));
-
- // Must follow redirect because we want an exception to be thrown eventually. If it is set to false, HTTP
- // communication stops and existing methods will return nothing / null. This messes with Observable
- // pipelines (which normally either expect a response object or an exception)
- call.Redirect.Follow = true;
- };
- }
-
- private static void LogBody(ILogger log, Url url, string direction, HttpMethod method, string? body)
+ public static void LogBody(ILogger log, Url url, string direction, HttpMethod method, string? body)
{
if (string.IsNullOrEmpty(body))
{
diff --git a/src/Recyclarr.ServarrApi/Http/FlurlSpecificEventHandler.cs b/src/Recyclarr.ServarrApi/Http/FlurlSpecificEventHandler.cs
new file mode 100644
index 00000000..b07ac24f
--- /dev/null
+++ b/src/Recyclarr.ServarrApi/Http/FlurlSpecificEventHandler.cs
@@ -0,0 +1,11 @@
+using System.Diagnostics.CodeAnalysis;
+using Flurl.Http;
+
+namespace Recyclarr.ServarrApi.Http;
+
+[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification =
+ "Naming convention borrowed from and determined by Flurl")]
+public abstract class FlurlSpecificEventHandler : FlurlEventHandler
+{
+ public abstract FlurlEventType EventType { get; }
+}
diff --git a/src/Recyclarr.ServarrApi/Http/IServarrRequestBuilder.cs b/src/Recyclarr.ServarrApi/Http/Servarr/IServarrRequestBuilder.cs
similarity index 79%
rename from src/Recyclarr.ServarrApi/Http/IServarrRequestBuilder.cs
rename to src/Recyclarr.ServarrApi/Http/Servarr/IServarrRequestBuilder.cs
index 928981ff..1a3d3f1b 100644
--- a/src/Recyclarr.ServarrApi/Http/IServarrRequestBuilder.cs
+++ b/src/Recyclarr.ServarrApi/Http/Servarr/IServarrRequestBuilder.cs
@@ -1,7 +1,7 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-namespace Recyclarr.ServarrApi.Http;
+namespace Recyclarr.ServarrApi.Http.Servarr;
public interface IServarrRequestBuilder
{
diff --git a/src/Recyclarr.ServarrApi/Http/Servarr/ServarrFlurlEventHandlers.cs b/src/Recyclarr.ServarrApi/Http/Servarr/ServarrFlurlEventHandlers.cs
new file mode 100644
index 00000000..fa7f527b
--- /dev/null
+++ b/src/Recyclarr.ServarrApi/Http/Servarr/ServarrFlurlEventHandlers.cs
@@ -0,0 +1,55 @@
+using Flurl.Http;
+using JetBrains.Annotations;
+using Serilog;
+
+namespace Recyclarr.ServarrApi.Http.Servarr;
+
+[UsedImplicitly]
+public class FlurlBeforeCallHandler(ILogger log) : FlurlSpecificEventHandler
+{
+ public override FlurlEventType EventType => FlurlEventType.BeforeCall;
+
+ public override void Handle(FlurlEventType eventType, FlurlCall call)
+ {
+ var url = FlurlLogging.SanitizeUrl(call.Request.Url.Clone());
+ log.Debug("HTTP Request: {Method} {Url}", call.HttpRequestMessage.Method, url);
+ FlurlLogging.LogBody(log, url, "Request", call.HttpRequestMessage.Method, call.RequestBody);
+ }
+}
+
+[UsedImplicitly]
+public class FlurlAfterCallHandler(ILogger log) : FlurlSpecificEventHandler
+{
+ public override FlurlEventType EventType => FlurlEventType.AfterCall;
+
+ public override async Task HandleAsync(FlurlEventType eventType, FlurlCall call)
+ {
+ var statusCode = call.Response?.StatusCode.ToString() ?? "(No response)";
+ var url = FlurlLogging.SanitizeUrl(call.Request.Url.Clone());
+ log.Debug("HTTP Response: {Status} {Method} {Url}", statusCode, call.HttpRequestMessage.Method, url);
+
+ var content = call.Response?.ResponseMessage.Content;
+ if (content is not null)
+ {
+ FlurlLogging.LogBody(log, url, "Response", call.HttpRequestMessage.Method,
+ await content.ReadAsStringAsync());
+ }
+ }
+}
+
+[UsedImplicitly]
+public class FlurlRedirectHandler(ILogger log) : FlurlSpecificEventHandler
+{
+ public override FlurlEventType EventType => FlurlEventType.OnRedirect;
+
+ public override void Handle(FlurlEventType eventType, FlurlCall call)
+ {
+ log.Warning("HTTP Redirect received; this indicates a problem with your URL and/or reverse proxy: {Url}",
+ FlurlLogging.SanitizeUrl(call.Redirect.Url));
+
+ // Must follow redirect because we want an exception to be thrown eventually. If it is set to false, HTTP
+ // communication stops and existing methods will return nothing / null. This messes with Observable
+ // pipelines (which normally either expect a response object or an exception)
+ call.Redirect.Follow = true;
+ }
+}
diff --git a/src/Recyclarr.ServarrApi/Http/ServarrRequestBuilder.cs b/src/Recyclarr.ServarrApi/Http/Servarr/ServarrRequestBuilder.cs
similarity index 84%
rename from src/Recyclarr.ServarrApi/Http/ServarrRequestBuilder.cs
rename to src/Recyclarr.ServarrApi/Http/Servarr/ServarrRequestBuilder.cs
index c9c79499..62d5c20d 100644
--- a/src/Recyclarr.ServarrApi/Http/ServarrRequestBuilder.cs
+++ b/src/Recyclarr.ServarrApi/Http/Servarr/ServarrRequestBuilder.cs
@@ -7,12 +7,13 @@ using Recyclarr.Json;
using Recyclarr.Settings;
using Serilog;
-namespace Recyclarr.ServarrApi.Http;
+namespace Recyclarr.ServarrApi.Http.Servarr;
public class ServarrRequestBuilder(
ILogger log,
IFlurlClientCache clientCache,
- ISettingsProvider settingsProvider)
+ ISettingsProvider settingsProvider,
+ IEnumerable eventHandlers)
: IServarrRequestBuilder
{
public IFlurlRequest Request(IServiceConfiguration config, params object[] path)
@@ -30,10 +31,14 @@ public class ServarrRequestBuilder(
[SuppressMessage("Security", "CA5359:Do Not Disable Certificate Validation")]
private void Configure(IFlurlClientBuilder builder)
{
+ foreach (var handler in eventHandlers.Select(x => (x.EventType, x)))
+ {
+ builder.EventHandlers.Add(handler);
+ }
+
builder.WithSettings(settings =>
{
settings.JsonSerializer = new DefaultJsonSerializer(GlobalJsonSerializerSettings.Services);
- FlurlLogging.SetupLogging(settings, log);
});
builder.ConfigureInnerHandler(handler =>
diff --git a/src/Recyclarr.ServarrApi/MediaNaming/MediaNamingApiService.cs b/src/Recyclarr.ServarrApi/MediaNaming/MediaNamingApiService.cs
index 8dd37dba..3da9f603 100644
--- a/src/Recyclarr.ServarrApi/MediaNaming/MediaNamingApiService.cs
+++ b/src/Recyclarr.ServarrApi/MediaNaming/MediaNamingApiService.cs
@@ -1,7 +1,7 @@
using Flurl.Http;
using Recyclarr.Common;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.MediaNaming;
diff --git a/src/Recyclarr.ServarrApi/QualityDefinition/QualityDefinitionApiService.cs b/src/Recyclarr.ServarrApi/QualityDefinition/QualityDefinitionApiService.cs
index 6c8d7a43..83c5ec96 100644
--- a/src/Recyclarr.ServarrApi/QualityDefinition/QualityDefinitionApiService.cs
+++ b/src/Recyclarr.ServarrApi/QualityDefinition/QualityDefinitionApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.QualityDefinition;
diff --git a/src/Recyclarr.ServarrApi/QualityProfile/QualityProfileApiService.cs b/src/Recyclarr.ServarrApi/QualityProfile/QualityProfileApiService.cs
index 40855558..d89ba3fe 100644
--- a/src/Recyclarr.ServarrApi/QualityProfile/QualityProfileApiService.cs
+++ b/src/Recyclarr.ServarrApi/QualityProfile/QualityProfileApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.QualityProfile;
diff --git a/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs b/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs
index da67445d..ebc468eb 100644
--- a/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs
+++ b/src/Recyclarr.ServarrApi/ReleaseProfile/ReleaseProfileApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.ReleaseProfile;
diff --git a/src/Recyclarr.ServarrApi/System/SystemApiService.cs b/src/Recyclarr.ServarrApi/System/SystemApiService.cs
index 6968bc22..ad61cf33 100644
--- a/src/Recyclarr.ServarrApi/System/SystemApiService.cs
+++ b/src/Recyclarr.ServarrApi/System/SystemApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.System;
diff --git a/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs b/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs
index 4c5a299b..24a0090f 100644
--- a/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs
+++ b/src/Recyclarr.ServarrApi/Tag/SonarrTagApiService.cs
@@ -1,6 +1,6 @@
using Flurl.Http;
using Recyclarr.Config.Models;
-using Recyclarr.ServarrApi.Http;
+using Recyclarr.ServarrApi.Http.Servarr;
namespace Recyclarr.ServarrApi.Tag;