From 18bb77a0e1b162b2e6fd89ce7aa769b81eba2e5b Mon Sep 17 00:00:00 2001 From: Oleg Shevchenko Date: Thu, 11 Aug 2022 18:25:37 +0300 Subject: [PATCH 1/2] Implement gRPC destructurer --- Serilog.Exceptions.sln | 7 +++ .../Destructurers/RpcExceptionDestructurer.cs | 43 ++++++++++++++ .../Properties/AssemblyInfo.cs | 3 + .../Serilog.Exceptions.Grpc.csproj | 21 +++++++ .../RpcExceptionDestructurerTest.cs | 59 +++++++++++++++++++ .../Serilog.Exceptions.Test.csproj | 1 + 6 files changed, 134 insertions(+) create mode 100644 Source/Serilog.Exceptions.Grpc/Destructurers/RpcExceptionDestructurer.cs create mode 100644 Source/Serilog.Exceptions.Grpc/Properties/AssemblyInfo.cs create mode 100644 Source/Serilog.Exceptions.Grpc/Serilog.Exceptions.Grpc.csproj create mode 100644 Tests/Serilog.Exceptions.Test/Destructurers/RpcExceptionDestructurerTest.cs diff --git a/Serilog.Exceptions.sln b/Serilog.Exceptions.sln index 1f65dd8d..67031453 100644 --- a/Serilog.Exceptions.sln +++ b/Serilog.Exceptions.sln @@ -92,6 +92,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.MsSqlSer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Refit", "Source\Serilog.Exceptions.Refit\Serilog.Exceptions.Refit.csproj", "{0EABF22F-F070-4F8D-B165-DD4C4AB62820}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Grpc", "Source\Serilog.Exceptions.Grpc\Serilog.Exceptions.Grpc.csproj", "{62CD1306-225F-4FE4-8C2C-45D457E84D36}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -130,6 +132,10 @@ Global {0EABF22F-F070-4F8D-B165-DD4C4AB62820}.Debug|Any CPU.Build.0 = Debug|Any CPU {0EABF22F-F070-4F8D-B165-DD4C4AB62820}.Release|Any CPU.ActiveCfg = Release|Any CPU {0EABF22F-F070-4F8D-B165-DD4C4AB62820}.Release|Any CPU.Build.0 = Release|Any CPU + {62CD1306-225F-4FE4-8C2C-45D457E84D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62CD1306-225F-4FE4-8C2C-45D457E84D36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62CD1306-225F-4FE4-8C2C-45D457E84D36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62CD1306-225F-4FE4-8C2C-45D457E84D36}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -147,6 +153,7 @@ Global {4F089B23-3121-4935-B24E-7A9A497BD9FE} = {2C245036-D7F6-4F7C-9BB6-5AFBCCE480F7} {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} {0EABF22F-F070-4F8D-B165-DD4C4AB62820} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} + {62CD1306-225F-4FE4-8C2C-45D457E84D36} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BE74AFAC-AC6F-4B80-860F-15C22BEE1A38} diff --git a/Source/Serilog.Exceptions.Grpc/Destructurers/RpcExceptionDestructurer.cs b/Source/Serilog.Exceptions.Grpc/Destructurers/RpcExceptionDestructurer.cs new file mode 100644 index 00000000..5da17602 --- /dev/null +++ b/Source/Serilog.Exceptions.Grpc/Destructurers/RpcExceptionDestructurer.cs @@ -0,0 +1,43 @@ +namespace Serilog.Exceptions.Grpc.Destructurers; + +using System; +using System.Collections.Generic; +using global::Grpc.Core; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Destructurers; + +/// +/// A destructurer for . +/// +/// +public class RpcExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] { typeof(RpcException) }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + + var rpcException = (RpcException)exception; + +#pragma warning disable CA1062 // Validate arguments of public methods + propertiesBag.AddProperty(nameof(RpcException.Status.StatusCode), rpcException.Status.StatusCode); + propertiesBag.AddProperty(nameof(RpcException.Status.Detail), rpcException.Status.Detail); + + foreach (var trailer in rpcException.Trailers) + { + if (trailer.IsBinary) + { + continue; + } + + propertiesBag.AddProperty($"{nameof(RpcException.Trailers)}.{trailer.Key}", trailer.Value); + } +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Source/Serilog.Exceptions.Grpc/Properties/AssemblyInfo.cs b/Source/Serilog.Exceptions.Grpc/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..0270020d --- /dev/null +++ b/Source/Serilog.Exceptions.Grpc/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: CLSCompliant(true)] diff --git a/Source/Serilog.Exceptions.Grpc/Serilog.Exceptions.Grpc.csproj b/Source/Serilog.Exceptions.Grpc/Serilog.Exceptions.Grpc.csproj new file mode 100644 index 00000000..38cfee30 --- /dev/null +++ b/Source/Serilog.Exceptions.Grpc/Serilog.Exceptions.Grpc.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.1;netstandard2.0;netstandard1.5;net462 + + + + Serilog Exceptions gRPC + Log exception details and custom properties that are not output in Exception.ToString(). Contains custom destructurers for gRPC exceptions. + Serilog;Exception;Log;Logging;Detail;Details;gRPC + + + + + + + + + + + diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/RpcExceptionDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/RpcExceptionDestructurerTest.cs new file mode 100644 index 00000000..c87aa737 --- /dev/null +++ b/Tests/Serilog.Exceptions.Test/Destructurers/RpcExceptionDestructurerTest.cs @@ -0,0 +1,59 @@ +namespace Serilog.Exceptions.Test.Destructurers; + +using System; +using System.Net; +using global::Grpc.Core; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Grpc.Destructurers; +using Xunit; +using static LogJsonOutputUtils; + +public class RpcExceptionDestructurerTest +{ + [Fact] + public void RpcException_StatusCodeIsLoggedAsProperty() + { + var options = new DestructuringOptionsBuilder().WithDestructurers(new[] { new RpcExceptionDestructurer() }); + var rpcException = new RpcException(new Status(StatusCode.Aborted, string.Empty)); + + Test_LoggedExceptionContainsProperty(rpcException, nameof(RpcException.Status.StatusCode), nameof(StatusCode.Aborted), options); + } + + [Fact] + public void RpcException_StatusDetailIsLoggedAsProperty() + { + var options = new DestructuringOptionsBuilder().WithDestructurers(new[] { new RpcExceptionDestructurer() }); + var testDetail = "details"; + var rpcException = new RpcException(new Status(StatusCode.Aborted, testDetail)); + + Test_LoggedExceptionContainsProperty(rpcException, nameof(RpcException.Status.Detail), testDetail, options); + } + + [Fact] + public void RpcException_TrailersAreLoggedAsProperty() + { + var options = new DestructuringOptionsBuilder().WithDestructurers(new[] { new RpcExceptionDestructurer() }); + const string stringTrailerKey1 = "key1"; + const string stringTrailerValue1 = "stringTrailerValue1"; + const string stringTrailerKey2 = "key2"; + const string stringTrailerValue2 = "stringTrailerValue2"; + var metadata = new Metadata { { stringTrailerKey1, stringTrailerValue1 }, { stringTrailerKey2, stringTrailerValue2 } }; + + var rpcException = new RpcException(new Status(StatusCode.Aborted, string.Empty), metadata); + + Test_LoggedExceptionContainsProperty(rpcException, $"{nameof(RpcException.Trailers)}.{stringTrailerKey1}", stringTrailerValue1, options); + Test_LoggedExceptionContainsProperty(rpcException, $"{nameof(RpcException.Trailers)}.{stringTrailerKey2}", stringTrailerValue2, options); + } + + [Fact] + public void RpcException_BinaryTrailersAreNotLoggedAsProperty() + { + var options = new DestructuringOptionsBuilder().WithDestructurers(new[] { new RpcExceptionDestructurer() }); + const string stringTrailerKey1 = "key-bin"; + var metadata = new Metadata { { stringTrailerKey1, new byte[] { 1 } } }; + + var rpcException = new RpcException(new Status(StatusCode.Aborted, string.Empty), metadata); + + Test_LoggedExceptionDoesNotContainProperty(rpcException, $"{nameof(RpcException.Trailers)}.{stringTrailerKey1}", options); + } +} diff --git a/Tests/Serilog.Exceptions.Test/Serilog.Exceptions.Test.csproj b/Tests/Serilog.Exceptions.Test/Serilog.Exceptions.Test.csproj index 0c7dd32a..c0dc01cc 100644 --- a/Tests/Serilog.Exceptions.Test/Serilog.Exceptions.Test.csproj +++ b/Tests/Serilog.Exceptions.Test/Serilog.Exceptions.Test.csproj @@ -11,6 +11,7 @@ + From 1376859788edcce00c40213289179aa8cd599060 Mon Sep 17 00:00:00 2001 From: Oleg Shevchenko Date: Mon, 15 Aug 2022 12:08:30 +0300 Subject: [PATCH 2/2] Add information about Serilog.Exceptions.Grpc to readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 44aa47ec..2413a5bb 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,24 @@ In addition, the `ApiException.Content` property can be logged with the followin Be careful with this option as the HTTP body could be very large and/or contain sensitive information. +### Serilog.Exceptions.Grpc + +[![Serilog.Exceptions.Grpc NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.Grpc.svg)](https://www.nuget.org/packages/Serilog.Exceptions.Grpc/) +[![Serilog.Exceptions.Grpc NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.Grpc)](https://www.nuget.org/packages/Serilog.Exceptions.Grpc) + +Add the [Serilog.Exceptions.Grpc](https://www.nuget.org/packages/Serilog.Exceptions.Grpc/) NuGet package to your project to avoid the reflection based destructurer for `RpcException` when using [Grpc.Net.Client](https://www.nuget.org/packages/Grpc.Net.Client/): + +``` +Install-Package Serilog.Exceptions.Grpc +``` + +Add the `RpcExceptionDestructurer` during setup: +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new RpcExceptionDestructurer() })) +``` + ## Custom Exception Destructurers You may want to add support for destructuring your own exceptions without relying on reflection. To do this, create your own destructuring class implementing `ExceptionDestructurer` (You can take a look at [this](https://github.com/RehanSaeed/Serilog.Exceptions/blob/main/Source/Serilog.Exceptions/Destructurers/ArgumentExceptionDestructurer.cs) for `ArgumentException`), then simply add it like so: