Skip to content

chore: Exception logging to include inner exceptions #681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -57,39 +57,50 @@
/// <param name="options">The JsonSerializer options.</param>
public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
{
var exceptionType = value.GetType();
var properties = ExceptionPropertyExtractor.ExtractProperties(value);
void WriteException(Utf8JsonWriter w, Exception ex)
{
var exceptionType = ex.GetType();
var properties = ExceptionPropertyExtractor.ExtractProperties(ex);

if (options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull)
properties = properties.Where(prop => prop.Value != null);
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull)
properties = properties.Where(prop => prop.Value != null);

Check warning on line 66 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs#L66

Added line #L66 was not covered by tests

var props = properties.ToArray();
if (props.Length == 0)
return;
var props = properties.ToArray();
if (props.Length == 0)
return;

Check warning on line 70 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs#L70

Added line #L70 was not covered by tests

writer.WriteStartObject();
writer.WriteString(ApplyPropertyNamingPolicy("Type", options), exceptionType.FullName);

foreach (var prop in props)
{
switch (prop.Value)
w.WriteStartObject();
w.WriteString(ApplyPropertyNamingPolicy("Type", options), exceptionType.FullName);

foreach (var prop in props)
{
case IntPtr intPtr:
writer.WriteNumber(ApplyPropertyNamingPolicy(prop.Key, options), intPtr.ToInt64());
break;
case UIntPtr uIntPtr:
writer.WriteNumber(ApplyPropertyNamingPolicy(prop.Key, options), uIntPtr.ToUInt64());
break;
case Type propType:
writer.WriteString(ApplyPropertyNamingPolicy(prop.Key, options), propType.FullName);
break;
case string propString:
writer.WriteString(ApplyPropertyNamingPolicy(prop.Key, options), propString);
break;
switch (prop.Value)
{
case IntPtr intPtr:
w.WriteNumber(ApplyPropertyNamingPolicy(prop.Key, options), intPtr.ToInt64());
break;

Check warning on line 81 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs#L80-L81

Added lines #L80 - L81 were not covered by tests
case UIntPtr uIntPtr:
w.WriteNumber(ApplyPropertyNamingPolicy(prop.Key, options), uIntPtr.ToUInt64());
break;

Check warning on line 84 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs#L83-L84

Added lines #L83 - L84 were not covered by tests
case Type propType:
w.WriteString(ApplyPropertyNamingPolicy(prop.Key, options), propType.FullName);
break;

Check warning on line 87 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ExceptionConverter.cs#L86-L87

Added lines #L86 - L87 were not covered by tests
case string propString:
w.WriteString(ApplyPropertyNamingPolicy(prop.Key, options), propString);
break;
}
}

if (ex.InnerException != null)
{
w.WritePropertyName(ApplyPropertyNamingPolicy("InnerException", options));
WriteException(w, ex.InnerException);
}

w.WriteEndObject();
}

writer.WriteEndObject();
WriteException(writer, value);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ private static IEnumerable<KeyValuePair<string, object>> GetBaseExceptionPropert
yield return new KeyValuePair<string, object>(nameof(Exception.Message), ex.Message);
yield return new KeyValuePair<string, object>(nameof(Exception.Source), ex.Source);
yield return new KeyValuePair<string, object>(nameof(Exception.StackTrace), ex.StackTrace);
yield return new KeyValuePair<string, object>(nameof(Exception.InnerException), ex.InnerException);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,90 @@ public void Log_WhenException_LogsExceptionDetails()
s.Contains("\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()")
));
}

[Fact]
public void Log_Inner_Exception()
{
// Arrange
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var error = new InvalidOperationException("Parent exception message",
new ArgumentNullException(nameof(service), "Very important inner exception message"));
var logLevel = LogLevel.Information;
var randomSampleRate = 0.5;

var configurations = Substitute.For<IPowertoolsConfigurations>();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());

var systemWrapper = Substitute.For<ISystemWrapper>();
systemWrapper.GetRandom().Returns(randomSampleRate);

var loggerConfiguration = new LoggerConfiguration
{
Service = null,
MinimumLevel = LogLevel.None
};

var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
var logger = provider.CreateLogger(loggerName);

logger.LogError(
error,
"Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}",
12345);

// Assert
systemWrapper.Received(1).LogLine(Arg.Is<string>(s =>
s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" +
error.Message + "\"")
));

systemWrapper.Received(1).LogLine(Arg.Is<string>(s =>
s.Contains("\"level\":\"Error\",\"service\":\"" + service+ "\",\"name\":\"" + loggerName + "\",\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"Very important inner exception message (Parameter 'service')\"}}}")
));
}

[Fact]
public void Log_Nested_Inner_Exception()
{
// Arrange
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var error = new InvalidOperationException("Parent exception message",
new ArgumentNullException(nameof(service),
new Exception("Very important nested inner exception message")));

var logLevel = LogLevel.Information;
var randomSampleRate = 0.5;

var configurations = Substitute.For<IPowertoolsConfigurations>();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());

var systemWrapper = Substitute.For<ISystemWrapper>();
systemWrapper.GetRandom().Returns(randomSampleRate);

var loggerConfiguration = new LoggerConfiguration
{
Service = null,
MinimumLevel = LogLevel.None
};

var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
var logger = provider.CreateLogger(loggerName);


logger.LogError(
error,
"Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}",
12345);

// Assert
systemWrapper.Received(1).LogLine(Arg.Is<string>(s =>
s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"service\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Very important nested inner exception message\"}}}}")
));
}

[Fact]
public void Log_WhenNestedException_LogsExceptionDetails()
Expand Down
Loading