refactor: sanitized logs for http communication

HTTP requests and responses are logged at DBG level. Host name and API
key are obfuscated for user privacy.
pull/47/head
Robert Dailey 3 years ago
parent 5c4fe0886c
commit 1772e7c9fd

@ -12,27 +12,27 @@ using Newtonsoft.Json;
using Serilog; using Serilog;
using Serilog.Core; using Serilog.Core;
using Serilog.Events; using Serilog.Events;
using TrashLib.Extensions;
using YamlDotNet.Core; using YamlDotNet.Core;
namespace Trash.Command.Helpers namespace Trash.Command.Helpers
{ {
public abstract class ServiceCommand : ICommand, IServiceCommand public abstract class ServiceCommand : ICommand, IServiceCommand
{ {
private readonly ILogger _log;
private readonly LoggingLevelSwitch _loggingLevelSwitch; private readonly LoggingLevelSwitch _loggingLevelSwitch;
private readonly ILogJanitor _logJanitor; private readonly ILogJanitor _logJanitor;
protected ServiceCommand( protected ServiceCommand(
ILogger logger, ILogger log,
LoggingLevelSwitch loggingLevelSwitch, LoggingLevelSwitch loggingLevelSwitch,
ILogJanitor logJanitor) ILogJanitor logJanitor)
{ {
_loggingLevelSwitch = loggingLevelSwitch; _loggingLevelSwitch = loggingLevelSwitch;
_logJanitor = logJanitor; _logJanitor = logJanitor;
Log = logger; _log = log;
} }
protected ILogger Log { get; }
public async ValueTask ExecuteAsync(IConsole console) public async ValueTask ExecuteAsync(IConsole console)
{ {
SetupLogging(); SetupLogging();
@ -50,13 +50,13 @@ namespace Trash.Command.Helpers
throw; throw;
} }
Log.Error("Found Unrecognized YAML Property: {ErrorMsg}", inner.Message); _log.Error("Found Unrecognized YAML Property: {ErrorMsg}", inner.Message);
Log.Error("Please remove the property quoted in the above message from your YAML file"); _log.Error("Please remove the property quoted in the above message from your YAML file");
throw new CommandException("Exiting due to invalid configuration"); throw new CommandException("Exiting due to invalid configuration");
} }
catch (Exception e) when (e is not CommandException) catch (Exception e) when (e is not CommandException)
{ {
Log.Error(e, "Unrecoverable Exception"); _log.Error(e, "Unrecoverable Exception");
ExitDueToFailure(); ExitDueToFailure();
} }
finally finally
@ -109,6 +109,7 @@ namespace Trash.Command.Helpers
}; };
settings.JsonSerializer = new NewtonsoftJsonSerializer(jsonSettings); settings.JsonSerializer = new NewtonsoftJsonSerializer(jsonSettings);
FlurlLogging.SetupLogging(settings, _log);
}); });
} }

@ -1,7 +1,9 @@
using Flurl.Http;
namespace TrashLib.Config namespace TrashLib.Config
{ {
public interface IServerInfo public interface IServerInfo
{ {
string BuildUrl(); IFlurlRequest BuildRequest();
} }
} }

@ -1,4 +1,7 @@
using Flurl; using Flurl;
using Flurl.Http;
using Serilog;
using TrashLib.Extensions;
namespace TrashLib.Config namespace TrashLib.Config
{ {
@ -6,18 +9,21 @@ namespace TrashLib.Config
{ {
private readonly string _apiKey; private readonly string _apiKey;
private readonly string _baseUrl; private readonly string _baseUrl;
private readonly ILogger _log;
public ServerInfo(string baseUrl, string apiKey) public ServerInfo(string baseUrl, string apiKey, ILogger log)
{ {
_baseUrl = baseUrl; _baseUrl = baseUrl;
_apiKey = apiKey; _apiKey = apiKey;
_log = log;
} }
public string BuildUrl() public IFlurlRequest BuildRequest()
{ {
return _baseUrl return _baseUrl
.AppendPathSegment("api/v3") .AppendPathSegment("api/v3")
.SetQueryParams(new {apikey = _apiKey}); .SetQueryParams(new {apikey = _apiKey})
.SanitizedLogging(_log);
} }
} }
} }

@ -0,0 +1,36 @@
using System;
using Flurl;
using Flurl.Http;
using Serilog;
namespace TrashLib.Extensions
{
public static class FlurlExtensions
{
public static IFlurlRequest SanitizedLogging(this Uri url, ILogger log)
=> new FlurlRequest(url).SanitizedLogging(log);
public static IFlurlRequest SanitizedLogging(this Url url, ILogger log)
=> new FlurlRequest(url).SanitizedLogging(log);
public static IFlurlRequest SanitizedLogging(this string url, ILogger log)
=> new FlurlRequest(url).SanitizedLogging(log);
public static IFlurlRequest SanitizedLogging(this IFlurlRequest request, ILogger log)
{
return request.ConfigureRequest(settings => FlurlLogging.SetupLogging(settings, log, SanitizeUrl));
}
private static Url SanitizeUrl(Url url)
{
// Replace hostname and API key for user privacy
url.Host = "hostname";
if (url.QueryParams.Contains("apikey"))
{
url.QueryParams.AddOrReplace("apikey", "SNIP");
}
return url;
}
}
}

@ -0,0 +1,28 @@
using System;
using Flurl;
using Flurl.Http.Configuration;
using Serilog;
namespace TrashLib.Extensions
{
public static class FlurlLogging
{
public static void SetupLogging(FlurlHttpSettings settings, ILogger log, Func<Url, Url>? urlInterceptor = null)
{
urlInterceptor ??= url => url;
settings.BeforeCall = call =>
{
var url = urlInterceptor(call.Request.Url.Clone());
log.Debug("HTTP Request to {Url}", url);
};
settings.AfterCall = call =>
{
var statusCode = call.Response?.StatusCode.ToString() ?? "(No response)";
var url = urlInterceptor(call.Request.Url.Clone());
log.Debug("HTTP Response {Status} from {Url}", statusCode, url);
};
}
}
}

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flurl;
using Flurl.Http; using Flurl.Http;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using TrashLib.Config; using TrashLib.Config;
@ -17,18 +16,16 @@ namespace TrashLib.Radarr.CustomFormat.Api
_serverInfo = serverInfo; _serverInfo = serverInfo;
} }
private string BaseUrl => _serverInfo.BuildUrl();
public async Task<List<JObject>> GetCustomFormats() public async Task<List<JObject>> GetCustomFormats()
{ {
return await BaseUrl return await BuildRequest()
.AppendPathSegment("customformat") .AppendPathSegment("customformat")
.GetJsonAsync<List<JObject>>(); .GetJsonAsync<List<JObject>>();
} }
public async Task CreateCustomFormat(ProcessedCustomFormatData cf) public async Task CreateCustomFormat(ProcessedCustomFormatData cf)
{ {
var response = await BaseUrl var response = await BuildRequest()
.AppendPathSegment("customformat") .AppendPathSegment("customformat")
.PostJsonAsync(cf.Json) .PostJsonAsync(cf.Json)
.ReceiveJson<JObject>(); .ReceiveJson<JObject>();
@ -41,7 +38,7 @@ namespace TrashLib.Radarr.CustomFormat.Api
public async Task UpdateCustomFormat(ProcessedCustomFormatData cf) public async Task UpdateCustomFormat(ProcessedCustomFormatData cf)
{ {
await BaseUrl await BuildRequest()
.AppendPathSegment($"customformat/{cf.GetCustomFormatId()}") .AppendPathSegment($"customformat/{cf.GetCustomFormatId()}")
.PutJsonAsync(cf.Json) .PutJsonAsync(cf.Json)
.ReceiveJson<JObject>(); .ReceiveJson<JObject>();
@ -49,9 +46,11 @@ namespace TrashLib.Radarr.CustomFormat.Api
public async Task DeleteCustomFormat(int customFormatId) public async Task DeleteCustomFormat(int customFormatId)
{ {
await BaseUrl await BuildRequest()
.AppendPathSegment($"customformat/{customFormatId}") .AppendPathSegment($"customformat/{customFormatId}")
.DeleteAsync(); .DeleteAsync();
} }
private IFlurlRequest BuildRequest() => _serverInfo.BuildRequest();
} }
} }

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flurl;
using Flurl.Http; using Flurl.Http;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using TrashLib.Config; using TrashLib.Config;
@ -16,21 +15,21 @@ namespace TrashLib.Radarr.CustomFormat.Api
_serverInfo = serverInfo; _serverInfo = serverInfo;
} }
private string BaseUrl => _serverInfo.BuildUrl();
public async Task<List<JObject>> GetQualityProfiles() public async Task<List<JObject>> GetQualityProfiles()
{ {
return await BaseUrl return await BuildRequest()
.AppendPathSegment("qualityprofile") .AppendPathSegment("qualityprofile")
.GetJsonAsync<List<JObject>>(); .GetJsonAsync<List<JObject>>();
} }
public async Task<JObject> UpdateQualityProfile(JObject profileJson, int id) public async Task<JObject> UpdateQualityProfile(JObject profileJson, int id)
{ {
return await BaseUrl return await BuildRequest()
.AppendPathSegment($"qualityprofile/{id}") .AppendPathSegment($"qualityprofile/{id}")
.PutJsonAsync(profileJson) .PutJsonAsync(profileJson)
.ReceiveJson<JObject>(); .ReceiveJson<JObject>();
} }
private IFlurlRequest BuildRequest() => _serverInfo.BuildRequest();
} }
} }

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flurl;
using Flurl.Http; using Flurl.Http;
using TrashLib.Config; using TrashLib.Config;
using TrashLib.Radarr.QualityDefinition.Api.Objects; using TrashLib.Radarr.QualityDefinition.Api.Objects;
@ -16,11 +15,9 @@ namespace TrashLib.Radarr.QualityDefinition.Api
_serverInfo = serverInfo; _serverInfo = serverInfo;
} }
private string BaseUrl => _serverInfo.BuildUrl();
public async Task<List<RadarrQualityDefinitionItem>> GetQualityDefinition() public async Task<List<RadarrQualityDefinitionItem>> GetQualityDefinition()
{ {
return await BaseUrl return await BuildRequest()
.AppendPathSegment("qualitydefinition") .AppendPathSegment("qualitydefinition")
.GetJsonAsync<List<RadarrQualityDefinitionItem>>(); .GetJsonAsync<List<RadarrQualityDefinitionItem>>();
} }
@ -28,10 +25,12 @@ namespace TrashLib.Radarr.QualityDefinition.Api
public async Task<IList<RadarrQualityDefinitionItem>> UpdateQualityDefinition( public async Task<IList<RadarrQualityDefinitionItem>> UpdateQualityDefinition(
IList<RadarrQualityDefinitionItem> newQuality) IList<RadarrQualityDefinitionItem> newQuality)
{ {
return await BaseUrl return await BuildRequest()
.AppendPathSegment("qualityDefinition/update") .AppendPathSegment("qualityDefinition/update")
.PutJsonAsync(newQuality) .PutJsonAsync(newQuality)
.ReceiveJson<List<RadarrQualityDefinitionItem>>(); .ReceiveJson<List<RadarrQualityDefinitionItem>>();
} }
private IFlurlRequest BuildRequest() => _serverInfo.BuildRequest();
} }
} }

@ -1,5 +1,6 @@
using Autofac; using Autofac;
using Autofac.Extras.AggregateService; using Autofac.Extras.AggregateService;
using Serilog;
using TrashLib.Config; using TrashLib.Config;
using TrashLib.Radarr.Config; using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat; using TrashLib.Radarr.CustomFormat;
@ -27,7 +28,8 @@ namespace TrashLib.Radarr
builder.Register(c => builder.Register(c =>
{ {
var config = c.Resolve<IConfigurationProvider>().ActiveConfiguration; var config = c.Resolve<IConfigurationProvider>().ActiveConfiguration;
return new ServerInfo(config.BaseUrl, config.ApiKey); var log = c.Resolve<ILogger>();
return new ServerInfo(config.BaseUrl, config.ApiKey, log);
}) })
.As<IServerInfo>(); .As<IServerInfo>();

@ -1,23 +1,29 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flurl;
using Flurl.Http; using Flurl.Http;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Serilog;
using TrashLib.Config; using TrashLib.Config;
using TrashLib.Extensions;
using TrashLib.Sonarr.Api.Objects; using TrashLib.Sonarr.Api.Objects;
namespace TrashLib.Sonarr.Api namespace TrashLib.Sonarr.Api
{ {
public class SonarrApi : ISonarrApi public class SonarrApi : ISonarrApi
{ {
private readonly ILogger _log;
private readonly ISonarrReleaseProfileCompatibilityHandler _profileHandler; private readonly ISonarrReleaseProfileCompatibilityHandler _profileHandler;
private readonly IServerInfo _serverInfo; private readonly IServerInfo _serverInfo;
public SonarrApi(IServerInfo serverInfo, ISonarrReleaseProfileCompatibilityHandler profileHandler) public SonarrApi(
IServerInfo serverInfo,
ISonarrReleaseProfileCompatibilityHandler profileHandler,
ILogger log)
{ {
_serverInfo = serverInfo; _serverInfo = serverInfo;
_profileHandler = profileHandler; _profileHandler = profileHandler;
_log = log;
} }
public async Task<IList<SonarrTag>> GetTags() public async Task<IList<SonarrTag>> GetTags()
@ -79,6 +85,6 @@ namespace TrashLib.Sonarr.Api
.ReceiveJson<List<SonarrQualityDefinitionItem>>(); .ReceiveJson<List<SonarrQualityDefinitionItem>>();
} }
private string BaseUrl() => _serverInfo.BuildUrl(); private IFlurlRequest BaseUrl() => _serverInfo.BuildRequest().SanitizedLogging(_log);
} }
} }

@ -1,7 +1,6 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Threading.Tasks; using System.Reactive.Threading.Tasks;
using Flurl;
using Flurl.Http; using Flurl.Http;
using TrashLib.Config; using TrashLib.Config;
@ -13,7 +12,7 @@ namespace TrashLib.Sonarr
public SonarrCompatibility(IServerInfo serverInfo) public SonarrCompatibility(IServerInfo serverInfo)
{ {
var task = serverInfo.BuildUrl() var task = serverInfo.BuildRequest()
.AppendPathSegment("system/status") .AppendPathSegment("system/status")
.GetJsonAsync(); .GetJsonAsync();

Loading…
Cancel
Save