- Centralize sanitization into a custom Serilog enricher - More log sites pass the exception object in - Console output now consistently only prints the mssage, but not the stack trace. - File output always outputs the stack trace. Additionally, there are fixes to several SonarLint issues.pull/259/head
parent
925d54368a
commit
cb56ab5737
@ -1,18 +0,0 @@
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Cli.Logging;
|
||||
|
||||
public class ExceptionMessageEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
var msg = logEvent.Exception?.Message;
|
||||
if (string.IsNullOrEmpty(msg))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ExceptionMessage", msg));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System.Text;
|
||||
using Recyclarr.Http;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Recyclarr.Cli.Logging;
|
||||
|
||||
public class FlurlExceptionSanitizingEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (logEvent.Exception is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sanitizedMessage = Sanitize.ExceptionMessage(logEvent.Exception);
|
||||
|
||||
// Use a builder to handle whether to use a newline character for the full exception message without checking if
|
||||
// the sanitized message is null or whitespace more than once.
|
||||
var fullBuilder = new StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(sanitizedMessage))
|
||||
{
|
||||
MakeProperty("SanitizedExceptionMessage", sanitizedMessage);
|
||||
fullBuilder.Append($"{sanitizedMessage}\n");
|
||||
}
|
||||
|
||||
// ReSharper disable once InvertIf
|
||||
if (logEvent.Exception.StackTrace is not null)
|
||||
{
|
||||
fullBuilder.Append(logEvent.Exception.StackTrace);
|
||||
MakeProperty("SanitizedExceptionFull", fullBuilder.ToString());
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void MakeProperty(string propertyName, object value)
|
||||
{
|
||||
logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty(propertyName, value));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public class FileExistsException(string attemptedPath) : Exception
|
||||
public class FileExistsException(string attemptedPath) : Exception($"File already exists: {attemptedPath}")
|
||||
{
|
||||
public string AttemptedPath { get; } = attemptedPath;
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
namespace Recyclarr.Common.Extensions;
|
||||
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
public static string FullMessage(this Exception ex)
|
||||
{
|
||||
if (ex is AggregateException aex)
|
||||
{
|
||||
return aex.InnerExceptions.Aggregate("[ ", (total, next) => $"{total}[{next.FullMessage()}] ") + "]";
|
||||
}
|
||||
|
||||
var msg = ex.Message.Replace(", see inner exception.", "").Trim();
|
||||
var innerMsg = ex.InnerException?.FullMessage();
|
||||
if (innerMsg != null && !innerMsg.ContainsIgnoreCase(msg) && !msg.ContainsIgnoreCase(innerMsg))
|
||||
{
|
||||
msg = $"{msg} [ {innerMsg} ]";
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
namespace Recyclarr.Config.Parsing.ErrorHandling;
|
||||
|
||||
public class NoConfigurationFilesException : Exception;
|
||||
public class NoConfigurationFilesException() : Exception("No configuration files found");
|
||||
|
@ -1,23 +1,36 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Flurl.Http;
|
||||
using Flurl;
|
||||
using Recyclarr.Common.Extensions;
|
||||
|
||||
namespace Recyclarr.Http;
|
||||
|
||||
public static partial class FlurlExtensions
|
||||
public static partial class Sanitize
|
||||
{
|
||||
public static string SanitizedExceptionMessage(this FlurlHttpException exception)
|
||||
public static string Message(string message)
|
||||
{
|
||||
// Replace full URLs
|
||||
var result = UrlRegex().Replace(exception.Message, Sanitize);
|
||||
var result = UrlRegex().Replace(message, SanitizeMatch);
|
||||
|
||||
// There are sometimes parenthetical parts of the message that contain the host but are not
|
||||
// detected as true URLs. Just strip those out completely.
|
||||
return HostRegex().Replace(result, "");
|
||||
}
|
||||
|
||||
private static string Sanitize(Match match)
|
||||
public static string ExceptionMessage(Exception exception)
|
||||
{
|
||||
return FlurlLogging.SanitizeUrl(match.Value).ToString() ?? match.Value;
|
||||
return Message(exception.FullMessage());
|
||||
}
|
||||
|
||||
public static Url Url(Url url)
|
||||
{
|
||||
// Replace hostname for user privacy
|
||||
url.Host = "REDACTED";
|
||||
return url;
|
||||
}
|
||||
|
||||
private static string SanitizeMatch(Match match)
|
||||
{
|
||||
return Url(match.Value).ToString() ?? match.Value;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\([-a-zA-Z0-9@:%._+~#=]{1,256}(?::[0-9]+)?\)")]
|
@ -1,11 +1,8 @@
|
||||
namespace Recyclarr.VersionControl;
|
||||
|
||||
public class GitCmdException(int exitCode, string error) : Exception("Git command failed with a non-zero exit code")
|
||||
public class GitCmdException(int exitCode, string errorMessage) : Exception(errorMessage)
|
||||
{
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
public string Error { get; } = error;
|
||||
public int ExitCode { get; } = exitCode;
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Global
|
||||
}
|
||||
|
||||
public class InvalidGitRepoException(string? message) : Exception(message);
|
||||
|
@ -1,31 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NodeDeserializers;
|
||||
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace Recyclarr.Yaml.YamlDotNet;
|
||||
|
||||
public static class YamlDotNetExtensions
|
||||
{
|
||||
public static T? DeserializeType<T>(this IDeserializer deserializer, string data)
|
||||
where T : class
|
||||
{
|
||||
var extractor = deserializer.Deserialize<RootExtractor<T>>(data);
|
||||
return extractor.RootObject;
|
||||
}
|
||||
|
||||
public static DeserializerBuilder WithRequiredPropertyValidation(this DeserializerBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.WithNodeDeserializer(inner => new ValidatingDeserializer(inner),
|
||||
s => s.InsteadOf<ObjectNodeDeserializer>());
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
|
||||
private sealed class RootExtractor<T>
|
||||
where T : class
|
||||
{
|
||||
public T? RootObject { get; }
|
||||
}
|
||||
}
|
Loading…
Reference in new issue