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

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

@ -1,4 +1,7 @@
using Flurl;
using Flurl.Http;
using Serilog;
using TrashLib.Extensions;
namespace TrashLib.Config
{
@ -6,18 +9,21 @@ namespace TrashLib.Config
{
private readonly string _apiKey;
private readonly string _baseUrl;
private readonly ILogger _log;
public ServerInfo(string baseUrl, string apiKey)
public ServerInfo(string baseUrl, string apiKey, ILogger log)
{
_baseUrl = baseUrl;
_apiKey = apiKey;
_log = log;
}
public string BuildUrl()
public IFlurlRequest BuildRequest()
{
return _baseUrl
.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.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Newtonsoft.Json.Linq;
using TrashLib.Config;
@ -17,18 +16,16 @@ namespace TrashLib.Radarr.CustomFormat.Api
_serverInfo = serverInfo;
}
private string BaseUrl => _serverInfo.BuildUrl();
public async Task<List<JObject>> GetCustomFormats()
{
return await BaseUrl
return await BuildRequest()
.AppendPathSegment("customformat")
.GetJsonAsync<List<JObject>>();
}
public async Task CreateCustomFormat(ProcessedCustomFormatData cf)
{
var response = await BaseUrl
var response = await BuildRequest()
.AppendPathSegment("customformat")
.PostJsonAsync(cf.Json)
.ReceiveJson<JObject>();
@ -41,7 +38,7 @@ namespace TrashLib.Radarr.CustomFormat.Api
public async Task UpdateCustomFormat(ProcessedCustomFormatData cf)
{
await BaseUrl
await BuildRequest()
.AppendPathSegment($"customformat/{cf.GetCustomFormatId()}")
.PutJsonAsync(cf.Json)
.ReceiveJson<JObject>();
@ -49,9 +46,11 @@ namespace TrashLib.Radarr.CustomFormat.Api
public async Task DeleteCustomFormat(int customFormatId)
{
await BaseUrl
await BuildRequest()
.AppendPathSegment($"customformat/{customFormatId}")
.DeleteAsync();
}
private IFlurlRequest BuildRequest() => _serverInfo.BuildRequest();
}
}

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

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

@ -1,5 +1,6 @@
using Autofac;
using Autofac.Extras.AggregateService;
using Serilog;
using TrashLib.Config;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat;
@ -27,7 +28,8 @@ namespace TrashLib.Radarr
builder.Register(c =>
{
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>();

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

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

Loading…
Cancel
Save