From faeb49357952d3a4da28cf079f93c32a37af0c24 Mon Sep 17 00:00:00 2001 From: Muhammad Rehan Saeed Date: Tue, 28 Jun 2022 14:50:20 +0100 Subject: [PATCH] Renormalise git files --- .editorconfig | 928 ++++++------ .gitignore | 708 ++++----- ...rk.DestructuringBenchmark-report-github.md | 36 +- ...enchmark.DestructuringBenchmark-report.csv | 10 +- ...nchmark.DestructuringBenchmark-report.html | 70 +- .../BenchmarkException.cs | 70 +- .../BenchmarkExceptionDestructurer.cs | 72 +- .../DestructuringBenchmark.cs | 146 +- .../ExceptionPropertiesBag.cs | 118 +- .../Serilog.Exceptions.Benchmark/Point.cs | 28 +- .../Serilog.Exceptions.Benchmark/Program.cs | 16 +- .../Serilog.Exceptions.Benchmark.csproj | 58 +- README.md | 548 +++---- Serilog.Exceptions.sln | 314 ++-- .../Destructurers/SqlExceptionDestructurer.cs | 76 +- .../Serilog.Exceptions.SqlServer.csproj | 42 +- .../Core/DestructuringOptionsBuilder.cs | 370 ++--- .../Core/ExceptionEnricher.cs | 212 +-- .../Core/ExceptionPropertiesBag.cs | 224 +-- .../Core/IDestructuringOptions.cs | 86 +- .../Core/IExceptionPropertiesBag.cs | 62 +- .../AggregateExceptionDestructurer.cs | 62 +- .../ArgumentExceptionDestructurer.cs | 64 +- ...ArgumentOutOfRangeExceptionDestructurer.cs | 62 +- .../Destructurers/ExceptionDestructurer.cs | 572 ++++---- .../Destructurers/IExceptionDestructurer.cs | 64 +- .../OperationCanceledExceptionDestructurer.cs | 94 +- .../ReflectionBasedDestructurer.cs | 756 +++++----- ...ReflectionTypeLoadExceptionDestructurer.cs | 108 +- .../TaskCanceledExceptionDestructurer.cs | 146 +- .../CompositeExceptionPropertyFilter.cs | 124 +- .../Filters/IExceptionPropertyFilter.cs | 38 +- .../IgnorePropertyByNameExceptionFilter.cs | 86 +- ...LoggerEnrichmentConfigurationExtensions.cs | 126 +- .../ExceptionDestructurerTest.cs | 476 +++--- .../ReflectionBasedDestructurerTest.cs | 1284 ++++++++--------- .../TaskCanceledExceptionDestructurerTest.cs | 196 +-- .../ExceptionFinderTool.csproj | 26 +- appveyor.yml | 106 +- azure-pipelines.yml | 206 +-- build.cake | 176 +-- 41 files changed, 4483 insertions(+), 4483 deletions(-) diff --git a/.editorconfig b/.editorconfig index a51fac03..51c2eaa4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,464 +1,464 @@ -# Version: 4.0.0 (Using https://semver.org/) -# Updated: 2021-10-12 -# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. -# See https://github.com/RehanSaeed/EditorConfig for updates to this file. -# See http://EditorConfig.org for more information about .editorconfig files. - -########################################## -# Common Settings -########################################## - -# This file is the top-most EditorConfig file -root = true - -# All Files -[*] -charset = utf-8 -indent_style = space -indent_size = 4 -insert_final_newline = true -trim_trailing_whitespace = true - -########################################## -# File Extension Settings -########################################## - -# Visual Studio Solution Files -[*.sln] -indent_style = tab - -# Visual Studio XML Project Files -[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] -indent_size = 2 - -# XML Configuration Files -[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] -indent_size = 2 - -# JSON Files -[*.{json,json5,webmanifest}] -indent_size = 2 - -# YAML Files -[*.{yml,yaml}] -indent_size = 2 - -# Markdown Files -[*.md] -trim_trailing_whitespace = false - -# Web Files -[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}] -indent_size = 2 - -# Batch Files -[*.{cmd,bat}] -end_of_line = crlf - -# Bash Files -[*.sh] -end_of_line = lf - -# Makefiles -[Makefile] -indent_style = tab - -########################################## -# Default .NET Code Style Severities -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope -########################################## - -[*.{cs,csx,cake,vb,vbx}] -# Default Severity for all .NET Code Style rules below -dotnet_analyzer_diagnostic.severity = warning - -########################################## -# Language Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules -########################################## - -# .NET Style Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules -[*.{cs,csx,cake,vb,vbx}] -# "this." and "Me." qualifiers -dotnet_style_qualification_for_field = true:warning -dotnet_style_qualification_for_property = true:warning -dotnet_style_qualification_for_method = true:warning -dotnet_style_qualification_for_event = true:warning -# Language keywords instead of framework type names for type references -dotnet_style_predefined_type_for_locals_parameters_members = true:warning -dotnet_style_predefined_type_for_member_access = true:warning -# Modifier preferences -dotnet_style_require_accessibility_modifiers = always:warning -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning -visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning -dotnet_style_readonly_field = true:warning -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning -# Expression-level preferences -dotnet_style_object_initializer = true:warning -dotnet_style_collection_initializer = true:warning -dotnet_style_explicit_tuple_names = true:warning -dotnet_style_prefer_inferred_tuple_names = true:warning -dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning -dotnet_style_prefer_auto_properties = true:warning -dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion -dotnet_diagnostic.IDE0045.severity = suggestion -dotnet_style_prefer_conditional_expression_over_return = false:suggestion -dotnet_diagnostic.IDE0046.severity = suggestion -dotnet_style_prefer_compound_assignment = true:warning -dotnet_style_prefer_simplified_interpolation = true:warning -dotnet_style_prefer_simplified_boolean_expressions = true:warning -# Null-checking preferences -dotnet_style_coalesce_expression = true:warning -dotnet_style_null_propagation = true:warning -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning -# File header preferences -# file_header_template = \n© PROJECT-AUTHOR\n -# If you use StyleCop, you'll need to disable SA1636: File header copyright text should match. -# dotnet_diagnostic.SA1636.severity = none -# Undocumented -dotnet_style_operator_placement_when_wrapping = end_of_line:warning -csharp_style_prefer_null_check_over_type_check = true:warning -dotnet_style_namespace_match_folder = true:suggestion -dotnet_diagnostic.IDE0130.severity = suggestion - -# C# Style Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules -[*.{cs,csx,cake}] -# 'var' preferences -csharp_style_var_for_built_in_types = true:warning -csharp_style_var_when_type_is_apparent = true:warning -csharp_style_var_elsewhere = true:warning -# Expression-bodied members -csharp_style_expression_bodied_methods = true:warning -csharp_style_expression_bodied_constructors = true:warning -csharp_style_expression_bodied_operators = true:warning -csharp_style_expression_bodied_properties = true:warning -csharp_style_expression_bodied_indexers = true:warning -csharp_style_expression_bodied_accessors = true:warning -csharp_style_expression_bodied_lambdas = true:warning -csharp_style_expression_bodied_local_functions = true:warning -# Pattern matching preferences -csharp_style_pattern_matching_over_is_with_cast_check = true:warning -csharp_style_pattern_matching_over_as_with_null_check = true:warning -csharp_style_prefer_switch_expression = true:warning -csharp_style_prefer_pattern_matching = true:warning -csharp_style_prefer_not_pattern = true:warning -# Expression-level preferences -csharp_style_inlined_variable_declaration = true:warning -csharp_prefer_simple_default_expression = true:warning -csharp_style_pattern_local_over_anonymous_function = true:warning -csharp_style_deconstructed_variable_declaration = true:warning -csharp_style_prefer_index_operator = true:warning -csharp_style_prefer_range_operator = true:warning -csharp_style_implicit_object_creation_when_type_is_apparent = true:warning -# "Null" checking preferences -csharp_style_throw_expression = true:warning -csharp_style_conditional_delegate_call = true:warning -# Code block preferences -csharp_prefer_braces = true:warning -csharp_prefer_simple_using_statement = true:suggestion -dotnet_diagnostic.IDE0063.severity = suggestion -# 'using' directive preferences -csharp_using_directive_placement = inside_namespace:warning -# Modifier preferences -csharp_prefer_static_local_function = true:warning -# Undocumented -csharp_style_namespace_declarations = file_scoped:warning - -########################################## -# Unnecessary Code Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules -########################################## - -# .NET Unnecessary code rules -[*.{cs,csx,cake,vb,vbx}] -dotnet_code_quality_unused_parameters = all:warning -dotnet_remove_unnecessary_suppression_exclusions = none:warning - -# C# Unnecessary code rules -[*.{cs,csx,cake}] -csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion -dotnet_diagnostic.IDE0058.severity = suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -dotnet_diagnostic.IDE0059.severity = suggestion - -########################################## -# Formatting Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules -########################################## - -# .NET formatting rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules -[*.{cs,csx,cake,vb,vbx}] -# Organize using directives -dotnet_sort_system_directives_first = true -dotnet_separate_import_directive_groups = false - -# C# formatting rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules -[*.{cs,csx,cake}] -# Newline options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true -csharp_new_line_before_catch = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_between_query_expression_clauses = true -# Indentation options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options -csharp_indent_case_contents = true -csharp_indent_switch_labels = true -csharp_indent_labels = no_change -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents_when_block = false -# Spacing options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options -csharp_space_after_cast = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_between_parentheses = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_around_binary_operators = before_and_after -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_after_comma = true -csharp_space_before_comma = false -csharp_space_after_dot = false -csharp_space_before_dot = false -csharp_space_after_semicolon_in_for_statement = true -csharp_space_before_semicolon_in_for_statement = false -csharp_space_around_declaration_statements = false -csharp_space_before_open_square_brackets = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_square_brackets = false -# Wrap options -# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options -csharp_preserve_single_line_statements = false -csharp_preserve_single_line_blocks = true - -########################################## -# .NET Naming Rules -# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules -########################################## - -[*.{cs,csx,cake,vb,vbx}] - -########################################## -# Styles -########################################## - -# camel_case_style - Define the camelCase style -dotnet_naming_style.camel_case_style.capitalization = camel_case -# pascal_case_style - Define the PascalCase style -dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# first_upper_style - The first character must start with an upper-case character -dotnet_naming_style.first_upper_style.capitalization = first_word_upper -# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' -dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case -dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I -# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' -dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case -dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T -# disallowed_style - Anything that has this style applied is marked as disallowed -dotnet_naming_style.disallowed_style.capitalization = pascal_case -dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ -dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ -# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file -dotnet_naming_style.internal_error_style.capitalization = pascal_case -dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ -dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ - -########################################## -# .NET Design Guideline Field Naming Rules -# Naming rules for fields follow the .NET Framework design guidelines -# https://docs.microsoft.com/dotnet/standard/design-guidelines/index -########################################## - -# All public/protected/protected_internal constant fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning - -# All public/protected/protected_internal static readonly fields must be PascalCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No other public/protected/protected_internal fields are allowed -# https://docs.microsoft.com/dotnet/standard/design-guidelines/field -dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal -dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style -dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error - -########################################## -# StyleCop Field Naming Rules -# Naming rules for fields follow the StyleCop analyzers -# This does not override any rules using disallowed_style above -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers -########################################## - -# All constant fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const -dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning - -# All static readonly fields must be PascalCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private -dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly -dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style -dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning - -# No non-private instance fields are allowed -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected -dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style -dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error - -# Private fields must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md -dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private -dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning - -# Local variables must be camelCase -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md -dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local -dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style -dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent - -# This rule should never fire. However, it's included for at least two purposes: -# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. -# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * -dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style -dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error - - -########################################## -# Other Naming Rules -########################################## - -# All of the following must be PascalCase: -# - Namespaces -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Classes and Enumerations -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md -# - Delegates -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types -# - Constructors, Properties, Events, Methods -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members -dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property -dotnet_naming_rule.element_rule.symbols = element_group -dotnet_naming_rule.element_rule.style = pascal_case_style -dotnet_naming_rule.element_rule.severity = warning - -# Interfaces use PascalCase and are prefixed with uppercase 'I' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.interface_group.applicable_kinds = interface -dotnet_naming_rule.interface_rule.symbols = interface_group -dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style -dotnet_naming_rule.interface_rule.severity = warning - -# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' -# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces -dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter -dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group -dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style -dotnet_naming_rule.type_parameter_rule.severity = warning - -# Function parameters use camelCase -# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters -dotnet_naming_symbols.parameters_group.applicable_kinds = parameter -dotnet_naming_rule.parameters_rule.symbols = parameters_group -dotnet_naming_rule.parameters_rule.style = camel_case_style -dotnet_naming_rule.parameters_rule.severity = warning - -########################################## -# StyleCop -########################################## - -[*] -# SA0001: XML comment analysis is disabled due to project configuration -# Justification: Comments turned off -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0001.md -dotnet_diagnostic.SA0001.severity = none - -[*.cs] -# SA1633: A C# code file is missing a standard file header. -# Justification: Comments turned off -# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md -dotnet_diagnostic.SA1633.severity = none - -########################################## -# License -########################################## -# The following applies as to the .editorconfig file ONLY, and is -# included below for reference, per the requirements of the license -# corresponding to this .editorconfig file. -# See: https://github.com/RehanSaeed/EditorConfig -# -# MIT License -# -# Copyright (c) 2017-2019 Muhammad Rehan Saeed -# Copyright (c) 2019 Henry Gabryjelski -# -# Permission is hereby granted, free of charge, to any -# person obtaining a copy of this software and associated -# documentation files (the "Software"), to deal in the -# Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, -# sublicense, and/or sell copies of the Software, and to permit -# persons to whom the Software is furnished to do so, subject -# to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -########################################## +# Version: 4.0.0 (Using https://semver.org/) +# Updated: 2021-10-12 +# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. +# See https://github.com/RehanSaeed/EditorConfig for updates to this file. +# See http://EditorConfig.org for more information about .editorconfig files. + +########################################## +# Common Settings +########################################## + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +########################################## +# File Extension Settings +########################################## + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,pcss,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + +# Makefiles +[Makefile] +indent_style = tab + +########################################## +# Default .NET Code Style Severities +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope +########################################## + +[*.{cs,csx,cake,vb,vbx}] +# Default Severity for all .NET Code Style rules below +dotnet_analyzer_diagnostic.severity = warning + +########################################## +# Language Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules +########################################## + +# .NET Style Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules +[*.{cs,csx,cake,vb,vbx}] +# "this." and "Me." qualifiers +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_property = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_event = true:warning +# Language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Modifier preferences +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning +dotnet_style_readonly_field = true:warning +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +# Null-checking preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +# File header preferences +# file_header_template = \n© PROJECT-AUTHOR\n +# If you use StyleCop, you'll need to disable SA1636: File header copyright text should match. +# dotnet_diagnostic.SA1636.severity = none +# Undocumented +dotnet_style_operator_placement_when_wrapping = end_of_line:warning +csharp_style_prefer_null_check_over_type_check = true:warning +dotnet_style_namespace_match_folder = true:suggestion +dotnet_diagnostic.IDE0130.severity = suggestion + +# C# Style Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules +[*.{cs,csx,cake}] +# 'var' preferences +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +# Expression-bodied members +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_not_pattern = true:warning +# Expression-level preferences +csharp_style_inlined_variable_declaration = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +# "Null" checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Code block preferences +csharp_prefer_braces = true:warning +csharp_prefer_simple_using_statement = true:suggestion +dotnet_diagnostic.IDE0063.severity = suggestion +# 'using' directive preferences +csharp_using_directive_placement = inside_namespace:warning +# Modifier preferences +csharp_prefer_static_local_function = true:warning +# Undocumented +csharp_style_namespace_declarations = file_scoped:warning + +########################################## +# Unnecessary Code Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules +########################################## + +# .NET Unnecessary code rules +[*.{cs,csx,cake,vb,vbx}] +dotnet_code_quality_unused_parameters = all:warning +dotnet_remove_unnecessary_suppression_exclusions = none:warning + +# C# Unnecessary code rules +[*.{cs,csx,cake}] +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +dotnet_diagnostic.IDE0058.severity = suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +dotnet_diagnostic.IDE0059.severity = suggestion + +########################################## +# Formatting Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules +########################################## + +# .NET formatting rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules +[*.{cs,csx,cake,vb,vbx}] +# Organize using directives +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# C# formatting rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules +[*.{cs,csx,cake}] +# Newline options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false +# Spacing options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# Wrap options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +########################################## +# .NET Naming Rules +# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules +########################################## + +[*.{cs,csx,cake,vb,vbx}] + +########################################## +# Styles +########################################## + +# camel_case_style - Define the camelCase style +dotnet_naming_style.camel_case_style.capitalization = camel_case +# pascal_case_style - Define the PascalCase style +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# first_upper_style - The first character must start with an upper-case character +dotnet_naming_style.first_upper_style.capitalization = first_word_upper +# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +# disallowed_style - Anything that has this style applied is marked as disallowed +dotnet_naming_style.disallowed_style.capitalization = pascal_case +dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ +dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ +# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file +dotnet_naming_style.internal_error_style.capitalization = pascal_case +dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ +dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ + +########################################## +# .NET Design Guideline Field Naming Rules +# Naming rules for fields follow the .NET Framework design guidelines +# https://docs.microsoft.com/dotnet/standard/design-guidelines/index +########################################## + +# All public/protected/protected_internal constant fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning + +# All public/protected/protected_internal static readonly fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No other public/protected/protected_internal fields are allowed +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error + +########################################## +# StyleCop Field Naming Rules +# Naming rules for fields follow the StyleCop analyzers +# This does not override any rules using disallowed_style above +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning + +# All static readonly fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error + +# Private fields must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private +dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent + +# This rule should never fire. However, it's included for at least two purposes: +# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. +# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error + + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +# - Namespaces +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Classes and Enumerations +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Delegates +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types +# - Constructors, Properties, Events, Methods +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +########################################## +# StyleCop +########################################## + +[*] +# SA0001: XML comment analysis is disabled due to project configuration +# Justification: Comments turned off +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA0001.md +dotnet_diagnostic.SA0001.severity = none + +[*.cs] +# SA1633: A C# code file is missing a standard file header. +# Justification: Comments turned off +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md +dotnet_diagnostic.SA1633.severity = none + +########################################## +# License +########################################## +# The following applies as to the .editorconfig file ONLY, and is +# included below for reference, per the requirements of the license +# corresponding to this .editorconfig file. +# See: https://github.com/RehanSaeed/EditorConfig +# +# MIT License +# +# Copyright (c) 2017-2019 Muhammad Rehan Saeed +# Copyright (c) 2019 Henry Gabryjelski +# +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject +# to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +########################################## diff --git a/.gitignore b/.gitignore index 5057603a..45fb4ea3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,354 +1,354 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -Artefacts/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +Artefacts/ diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report-github.md b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report-github.md index 9471c33a..48e6da95 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report-github.md +++ b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report-github.md @@ -1,18 +1,18 @@ -``` ini - -BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 1 [1607, Anniversary Update] (10.0.14393.1944) -Intel Core i5-6300U CPU 2.40GHz (Skylake), 1 CPU, 4 logical cores and 2 physical cores -Frequency=2437506 Hz, Resolution=410.2554 ns, Timer=TSC -.NET Core SDK=2.1.4 - [Host] : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT - Clr : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2117.0 - Core : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT - - -``` -| Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Allocated | -|----------------------- |----- |-------- |-----------:|-----------:|-----------:|-------:|----------:| -| ReflectionDestructurer | Clr | Clr | 9.966 us | 0.1979 us | 0.3196 us | 2.4719 | 3.81 KB | -| CustomDestructurer | Clr | Clr | 7.567 us | 0.0839 us | 0.0701 us | 2.2278 | 3.44 KB | -| ReflectionDestructurer | Core | Core | 850.053 us | 7.5538 us | 7.0658 us | 9.7656 | 16.11 KB | -| CustomDestructurer | Core | Core | 868.570 us | 17.1272 us | 17.5884 us | 9.7656 | 15.74 KB | +``` ini + +BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 1 [1607, Anniversary Update] (10.0.14393.1944) +Intel Core i5-6300U CPU 2.40GHz (Skylake), 1 CPU, 4 logical cores and 2 physical cores +Frequency=2437506 Hz, Resolution=410.2554 ns, Timer=TSC +.NET Core SDK=2.1.4 + [Host] : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT + Clr : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2117.0 + Core : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT + + +``` +| Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Allocated | +|----------------------- |----- |-------- |-----------:|-----------:|-----------:|-------:|----------:| +| ReflectionDestructurer | Clr | Clr | 9.966 us | 0.1979 us | 0.3196 us | 2.4719 | 3.81 KB | +| CustomDestructurer | Clr | Clr | 7.567 us | 0.0839 us | 0.0701 us | 2.2278 | 3.44 KB | +| ReflectionDestructurer | Core | Core | 850.053 us | 7.5538 us | 7.0658 us | 9.7656 | 16.11 KB | +| CustomDestructurer | Core | Core | 868.570 us | 17.1272 us | 17.5884 us | 9.7656 | 15.74 KB | diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.csv b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.csv index 29190fe0..8f03c7fc 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.csv +++ b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.csv @@ -1,5 +1,5 @@ -Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;RemoveOutliers;Affinity;Jit;Platform;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;HeapAffinitizeMask;HeapCount;NoAffinitize;RetainVm;Server;Arguments;BuildConfiguration;Clock;EngineFactory;EnvironmentVariables;Toolchain;IsBaseline;InvocationCount;IterationTime;LaunchCount;RunStrategy;TargetCount;UnrollFactor;WarmupCount;Mean;Error;StdDev;Gen 0;Allocated -ReflectionDestructurer;Clr;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Clr;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;9.966 us;0.1979 us;0.3196 us;2.4719;3.81 KB -CustomDestructurer;Clr;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Clr;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;7.567 us;0.0839 us;0.0701 us;2.2278;3.44 KB -ReflectionDestructurer;Core;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Core;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;850.053 us;7.5538 us;7.0658 us;9.7656;16.11 KB -CustomDestructurer;Core;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Core;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;868.570 us;17.1272 us;17.5884 us;9.7656;15.74 KB +Method;Job;AnalyzeLaunchVariance;EvaluateOverhead;MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;RemoveOutliers;Affinity;Jit;Platform;Runtime;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;HeapAffinitizeMask;HeapCount;NoAffinitize;RetainVm;Server;Arguments;BuildConfiguration;Clock;EngineFactory;EnvironmentVariables;Toolchain;IsBaseline;InvocationCount;IterationTime;LaunchCount;RunStrategy;TargetCount;UnrollFactor;WarmupCount;Mean;Error;StdDev;Gen 0;Allocated +ReflectionDestructurer;Clr;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Clr;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;9.966 us;0.1979 us;0.3196 us;2.4719;3.81 KB +CustomDestructurer;Clr;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Clr;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;7.567 us;0.0839 us;0.0701 us;2.2278;3.44 KB +ReflectionDestructurer;Core;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Core;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;850.053 us;7.5538 us;7.0658 us;9.7656;16.11 KB +CustomDestructurer;Core;False;Default;Default;Default;Default;Default;Default;15;RyuJit;X64;Core;False;True;False;True;Default;Default;False;False;False;Default;Default;Default;Default;Default;Default;Default;1;Default;Default;Default;Default;16;Default;868.570 us;17.1272 us;17.5884 us;9.7656;15.74 KB diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.html b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.html index 75f895b5..aa5c5d71 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.html +++ b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkDotNet.Artifacts/results/Serilog.Exceptions.Benchmark.DestructuringBenchmark-report.html @@ -1,35 +1,35 @@ - - - - -DestructuringBenchmark - - - - -

-BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 1 [1607, Anniversary Update] (10.0.14393.1944)
-Intel Core i5-6300U CPU 2.40GHz (Skylake), 1 CPU, 4 logical cores and 2 physical cores
-Frequency=2437506 Hz, Resolution=410.2554 ns, Timer=TSC
-.NET Core SDK=2.1.4
-  [Host] : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
-  Clr    : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2117.0
-  Core   : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
-
-
- - - - - - - - -
MethodJobRuntimeMeanErrorStdDevGen 0Allocated
ReflectionDestructurerClrClr9.966 us0.1979 us0.3196 us2.47193.81 KB
CustomDestructurerClrClr7.567 us0.0839 us0.0701 us2.22783.44 KB
ReflectionDestructurerCoreCore850.053 us7.5538 us7.0658 us9.765616.11 KB
CustomDestructurerCoreCore868.570 us17.1272 us17.5884 us9.765615.74 KB
- - + + + + +DestructuringBenchmark + + + + +

+BenchmarkDotNet=v0.10.12, OS=Windows 10 Redstone 1 [1607, Anniversary Update] (10.0.14393.1944)
+Intel Core i5-6300U CPU 2.40GHz (Skylake), 1 CPU, 4 logical cores and 2 physical cores
+Frequency=2437506 Hz, Resolution=410.2554 ns, Timer=TSC
+.NET Core SDK=2.1.4
+  [Host] : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
+  Clr    : .NET Framework 4.6.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2117.0
+  Core   : .NET Core 2.0.5 (Framework 4.6.26020.03), 64bit RyuJIT
+
+
+ + + + + + + + +
MethodJobRuntimeMeanErrorStdDevGen 0Allocated
ReflectionDestructurerClrClr9.966 us0.1979 us0.3196 us2.47193.81 KB
CustomDestructurerClrClr7.567 us0.0839 us0.0701 us2.22783.44 KB
ReflectionDestructurerCoreCore850.053 us7.5538 us7.0658 us9.765616.11 KB
CustomDestructurerCoreCore868.570 us17.1272 us17.5884 us9.765615.74 KB
+ + diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkException.cs b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkException.cs index 41348798..482e3ef1 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkException.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkException.cs @@ -1,35 +1,35 @@ -namespace Serilog.Exceptions.Benchmark; - -using System; -using System.Runtime.Serialization; - -[Serializable] -public class BenchmarkException : Exception -{ - public BenchmarkException() - { - } - - public BenchmarkException(string message) - : base(message) - { - } - - public BenchmarkException(string message, Exception inner) - : base(message, inner) - { - } - - protected BenchmarkException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } - - public string? ParamString { get; set; } - - public int ParamInt { get; set; } - - public Point? Point { get; set; } -} +namespace Serilog.Exceptions.Benchmark; + +using System; +using System.Runtime.Serialization; + +[Serializable] +public class BenchmarkException : Exception +{ + public BenchmarkException() + { + } + + public BenchmarkException(string message) + : base(message) + { + } + + public BenchmarkException(string message, Exception inner) + : base(message, inner) + { + } + + protected BenchmarkException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } + + public string? ParamString { get; set; } + + public int ParamInt { get; set; } + + public Point? Point { get; set; } +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkExceptionDestructurer.cs b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkExceptionDestructurer.cs index e8ee053b..2e0cda5c 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkExceptionDestructurer.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/BenchmarkExceptionDestructurer.cs @@ -1,36 +1,36 @@ -namespace Serilog.Exceptions.Benchmark; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Destructurers; - -/// -/// A destructurer used in benchmarks. -/// -/// -public class BenchmarkExceptionDestructurer : ExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] { typeof(BenchmarkException) }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var benchmarkException = (BenchmarkException)exception; - propertiesBag.AddProperty("ParamString", benchmarkException.ParamString); - propertiesBag.AddProperty("ParamInt", benchmarkException.ParamInt); - propertiesBag.AddProperty("Point", new Dictionary - { - { "X", benchmarkException.Point?.X }, - { "Y", benchmarkException.Point?.Y }, - }); -#pragma warning restore CA1062 // Validate arguments of public methods - } -} +namespace Serilog.Exceptions.Benchmark; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Destructurers; + +/// +/// A destructurer used in benchmarks. +/// +/// +public class BenchmarkExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] { typeof(BenchmarkException) }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var benchmarkException = (BenchmarkException)exception; + propertiesBag.AddProperty("ParamString", benchmarkException.ParamString); + propertiesBag.AddProperty("ParamInt", benchmarkException.ParamInt); + propertiesBag.AddProperty("Point", new Dictionary + { + { "X", benchmarkException.Point?.X }, + { "Y", benchmarkException.Point?.Y }, + }); +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/DestructuringBenchmark.cs b/Benchmarks/Serilog.Exceptions.Benchmark/DestructuringBenchmark.cs index 50462e2e..45521bc7 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/DestructuringBenchmark.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/DestructuringBenchmark.cs @@ -1,73 +1,73 @@ -namespace Serilog.Exceptions.Benchmark; - -using System; -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Jobs; -using Serilog.Exceptions.Destructurers; - -[KeepBenchmarkFiles] -[MemoryDiagnoser] -[MinColumn] -[MaxColumn] -[HtmlExporter] -[CsvMeasurementsExporter] -[RPlotExporter] -[SimpleJob(RuntimeMoniker.Net60)] -[SimpleJob(RuntimeMoniker.Net472)] -public class DestructuringBenchmark -{ - private readonly ReflectionBasedDestructurer reflectionBasedDestructurer = new(10); - private readonly BenchmarkExceptionDestructurer benchmarkExceptionDestructurer = new(); - private BenchmarkException benchmarkException = default!; - - [GlobalSetup] - public void Setup() - { - try - { - throw new BenchmarkException() - { - ParamInt = 123, - ParamString = "some param value", - Point = new Point() { X = 666, Y = 777 }, - }; - } - catch (BenchmarkException ex) - { - this.benchmarkException = ex; - } - } - - public IReadOnlyDictionary DestructureUsingReflectionDestructurer(Exception ex) - { - var bag = new ExceptionPropertiesBag(ex); - - this.reflectionBasedDestructurer.Destructure( - ex, - bag, - _ => new Dictionary()); - - return bag.GetResultDictionary(); - } - - [Benchmark] - public IReadOnlyDictionary ReflectionDestructurer() => - this.DestructureUsingReflectionDestructurer(this.benchmarkException); - - public IReadOnlyDictionary DestructureUsingCustomDestructurer(Exception ex) - { - var bag = new ExceptionPropertiesBag(ex); - - this.benchmarkExceptionDestructurer.Destructure( - ex, - bag, - _ => new Dictionary()); - - return bag.GetResultDictionary(); - } - - [Benchmark] - public IReadOnlyDictionary CustomDestructurer() => - this.DestructureUsingCustomDestructurer(this.benchmarkException); -} +namespace Serilog.Exceptions.Benchmark; + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Serilog.Exceptions.Destructurers; + +[KeepBenchmarkFiles] +[MemoryDiagnoser] +[MinColumn] +[MaxColumn] +[HtmlExporter] +[CsvMeasurementsExporter] +[RPlotExporter] +[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net472)] +public class DestructuringBenchmark +{ + private readonly ReflectionBasedDestructurer reflectionBasedDestructurer = new(10); + private readonly BenchmarkExceptionDestructurer benchmarkExceptionDestructurer = new(); + private BenchmarkException benchmarkException = default!; + + [GlobalSetup] + public void Setup() + { + try + { + throw new BenchmarkException() + { + ParamInt = 123, + ParamString = "some param value", + Point = new Point() { X = 666, Y = 777 }, + }; + } + catch (BenchmarkException ex) + { + this.benchmarkException = ex; + } + } + + public IReadOnlyDictionary DestructureUsingReflectionDestructurer(Exception ex) + { + var bag = new ExceptionPropertiesBag(ex); + + this.reflectionBasedDestructurer.Destructure( + ex, + bag, + _ => new Dictionary()); + + return bag.GetResultDictionary(); + } + + [Benchmark] + public IReadOnlyDictionary ReflectionDestructurer() => + this.DestructureUsingReflectionDestructurer(this.benchmarkException); + + public IReadOnlyDictionary DestructureUsingCustomDestructurer(Exception ex) + { + var bag = new ExceptionPropertiesBag(ex); + + this.benchmarkExceptionDestructurer.Destructure( + ex, + bag, + _ => new Dictionary()); + + return bag.GetResultDictionary(); + } + + [Benchmark] + public IReadOnlyDictionary CustomDestructurer() => + this.DestructureUsingCustomDestructurer(this.benchmarkException); +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/ExceptionPropertiesBag.cs b/Benchmarks/Serilog.Exceptions.Benchmark/ExceptionPropertiesBag.cs index cf8e544c..b0c00888 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/ExceptionPropertiesBag.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/ExceptionPropertiesBag.cs @@ -1,59 +1,59 @@ -namespace Serilog.Exceptions.Benchmark; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Filters; - -internal class ExceptionPropertiesBag : IExceptionPropertiesBag -{ - private readonly Exception exception; - private readonly IExceptionPropertyFilter? filter; - private readonly Dictionary properties = new(); - - // We keep a note on whether the results were collected to be sure that - // after that there are no changes. This is the application of fail-fast principle. - private bool resultsCollected; - - public ExceptionPropertiesBag(Exception exception, IExceptionPropertyFilter? filter = null) - { - this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); - this.filter = filter; - } - - public IReadOnlyDictionary GetResultDictionary() - { - this.resultsCollected = true; - return this.properties; - } - - public void AddProperty(string key, object? value) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(key); -#else - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } -#endif - - if (this.resultsCollected) - { - throw new InvalidOperationException( - $"Cannot add exception property '{key}' to bag, after results were already collected"); - } - - if (this.filter is not null) - { - if (this.filter.ShouldPropertyBeFiltered(this.exception, key, value)) - { - return; - } - } - - this.properties.Add(key, value); - } - - public bool ContainsProperty(string key) => this.properties.ContainsKey(key); -} +namespace Serilog.Exceptions.Benchmark; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Filters; + +internal class ExceptionPropertiesBag : IExceptionPropertiesBag +{ + private readonly Exception exception; + private readonly IExceptionPropertyFilter? filter; + private readonly Dictionary properties = new(); + + // We keep a note on whether the results were collected to be sure that + // after that there are no changes. This is the application of fail-fast principle. + private bool resultsCollected; + + public ExceptionPropertiesBag(Exception exception, IExceptionPropertyFilter? filter = null) + { + this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); + this.filter = filter; + } + + public IReadOnlyDictionary GetResultDictionary() + { + this.resultsCollected = true; + return this.properties; + } + + public void AddProperty(string key, object? value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(key); +#else + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } +#endif + + if (this.resultsCollected) + { + throw new InvalidOperationException( + $"Cannot add exception property '{key}' to bag, after results were already collected"); + } + + if (this.filter is not null) + { + if (this.filter.ShouldPropertyBeFiltered(this.exception, key, value)) + { + return; + } + } + + this.properties.Add(key, value); + } + + public bool ContainsProperty(string key) => this.properties.ContainsKey(key); +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/Point.cs b/Benchmarks/Serilog.Exceptions.Benchmark/Point.cs index dced5c3d..0c20b200 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/Point.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/Point.cs @@ -1,14 +1,14 @@ -namespace Serilog.Exceptions.Benchmark; - -public class Point -{ - public Point() => this.Z = 3; - - public int X { get; set; } - - public int Y { get; set; } - -#pragma warning disable IDE0052 // Remove unread private members - private int Z { get; set; } -#pragma warning restore IDE0052 // Remove unread private members -} +namespace Serilog.Exceptions.Benchmark; + +public class Point +{ + public Point() => this.Z = 3; + + public int X { get; set; } + + public int Y { get; set; } + +#pragma warning disable IDE0052 // Remove unread private members + private int Z { get; set; } +#pragma warning restore IDE0052 // Remove unread private members +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/Program.cs b/Benchmarks/Serilog.Exceptions.Benchmark/Program.cs index 9168a5ae..123beb82 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/Program.cs +++ b/Benchmarks/Serilog.Exceptions.Benchmark/Program.cs @@ -1,8 +1,8 @@ -namespace Serilog.Exceptions.Benchmark; - -using BenchmarkDotNet.Running; - -public static class Program -{ - public static void Main() => BenchmarkRunner.Run(); -} +namespace Serilog.Exceptions.Benchmark; + +using BenchmarkDotNet.Running; + +public static class Program +{ + public static void Main() => BenchmarkRunner.Run(); +} diff --git a/Benchmarks/Serilog.Exceptions.Benchmark/Serilog.Exceptions.Benchmark.csproj b/Benchmarks/Serilog.Exceptions.Benchmark/Serilog.Exceptions.Benchmark.csproj index 9a024b22..5700540a 100644 --- a/Benchmarks/Serilog.Exceptions.Benchmark/Serilog.Exceptions.Benchmark.csproj +++ b/Benchmarks/Serilog.Exceptions.Benchmark/Serilog.Exceptions.Benchmark.csproj @@ -1,29 +1,29 @@ - - - - Exe - net6.0;net472 - false - - - - - - - - - - - - - - - - - - - - - - - + + + + Exe + net6.0;net472 + false + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index a9332f0e..44aa47ec 100644 --- a/README.md +++ b/README.md @@ -1,274 +1,274 @@ -![Serilog.Exceptions Banner](https://github.com/RehanSaeed/Serilog.Exceptions/blob/main/Images/Banner.png) - -[![Serilog.Exceptions NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.svg)](https://www.nuget.org/packages/Serilog.Exceptions/) [![Serilog.Exceptions package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/212043f6-5fe5-4c79-949e-162156b89894/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=212043f6-5fe5-4c79-949e-162156b89894&preferRelease=true) [![Serilog.Exceptions NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions)](https://www.nuget.org/packages/Serilog.Exceptions) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/RehanSaeedUK) [![Twitter Follow](https://img.shields.io/twitter/follow/rehansaeeduk.svg?style=social&label=Follow)](https://twitter.com/RehanSaeedUK) - -Serilog.Exceptions is an add-on to [Serilog](https://serilog.net) to log exception details and custom properties that are not output in `Exception.ToString()`. - -## What Does It Do? - -Your JSON logs will now be supplemented with detailed exception information and even custom exception properties. Here is an example of what happens when you log a `DbEntityValidationException` from EntityFramework (This exception is notorious for having deeply nested custom properties which are not included in the `.ToString()`). - -```csharp -try -{ - ... -} -catch (DbEntityValidationException exception) -{ - logger.Error(exception, "Hello World"); -} -``` - -The code above logs the following: - -```json -{ - "Timestamp": "2015-12-07T12:26:24.0557671+00:00", - "Level": "Error", - "MessageTemplate": "Hello World", - "RenderedMessage": "Hello World", - "Exception": "System.Data.Entity.Validation.DbEntityValidationException: Message", - "Properties": { - "ExceptionDetail": { - "EntityValidationErrors": [ - { - "Entry": null, - "ValidationErrors": [ - { - "PropertyName": "PropertyName", - "ErrorMessage": "PropertyName is Required.", - "Type": "System.Data.Entity.Validation.DbValidationError" - } - ], - "IsValid": false, - "Type": "System.Data.Entity.Validation.DbEntityValidationResult" - } - ], - "Message": "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.", - "Data": {}, - "InnerException": null, - "TargetSite": null, - "StackTrace": null, - "HelpLink": null, - "Source": null, - "HResult": -2146232032, - "Type": "System.Data.Entity.Validation.DbEntityValidationException" - }, - "Source": "418169ff-e65f-456e-8b0d-42a0973c3577" - } -} -``` - -## Getting Started - -Add the [Serilog.Exceptions](https://www.nuget.org/packages/Serilog.Exceptions/) NuGet package to your project using the NuGet Package Manager or run the following command in the Package Console Window: - -```powershell -dotnet add package Serilog.Exceptions -``` - -When setting up your logger, add the `WithExceptionDetails()` line like so: - -```csharp -using Serilog; -using Serilog.Exceptions; - -ILogger logger = new LoggerConfiguration() - .Enrich.WithExceptionDetails() - .WriteTo.RollingFile( - new JsonFormatter(renderMessage: true), - @"C:\logs\log-{Date}.txt") - .CreateLogger(); -``` - -Make sure that the sink's formatter outputs enriched properties. `Serilog.Sinks.Console` and many more do not do that by default. You may need to add `{Properties:j}` to your sink's format template. For example, configuration for console sink may look like that: - -```csharp -.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {Properties:j}") -``` - -### JSON `appSettings.json` configuration - -Alternatively to fluent configuration setting can be stored in application configuration using [_Serilog.Settings.Configuration_](https://github.com/serilog/serilog-settings-configuration): -```json -{ - "Serilog": { - "Using": [ "Serilog.Exceptions" ], - "Enrich": [ "WithExceptionDetails" ], - "WriteTo": [ - { "Name": "Console" } - ] - } -} -``` - -## Performance - -This library has custom code to deal with extra properties on most common exception types and only falls back to using reflection to get the extra information if the exception is not supported by Serilog.Exceptions internally. Reflection overhead is present but minimal, because all the expensive relection-based operations are done only once per exception-type. - -## Additional Destructurers - -### Serilog.Exceptions.SqlServer - -[![Serilog.Exceptions.SqlServer NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.SqlServer.svg)](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer/) [![Serilog.Exceptions.SqlServer package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/67be830c-2c0f-4df8-be30-771d817b382f/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=67be830c-2c0f-4df8-be30-771d817b382f&preferRelease=true) [![Serilog.Exceptions.SqlServer NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.SqlServer)](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer) - -Add the [Serilog.Exceptions.SqlServer](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer/) NuGet package to your project to avoid the reflection based destructurer for `SqlException` when using [System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient/): - -``` -Install-Package Serilog.Exceptions.SqlServer -``` - -Add the `SqlExceptionDestructurer` during setup: -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new SqlExceptionDestructurer() })) -``` - -### Serilog.Exceptions.MsSqlServer - -[![Serilog.Exceptions.MsSqlServer NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.MsSqlServer.svg)](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer/) -[![Serilog.Exceptions.MsSqlServer package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/dce98084-312a-4939-b879-07bc25734572/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=dce98084-312a-4939-b879-07bc25734572&preferRelease=true) [![Serilog.Exceptions.MsSqlServer NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.MsSqlServer)](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer) - -Add the [Serilog.Exceptions.MsSqlServer](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer/) NuGet package to your project to avoid the reflection based destructurer for `SqlException` when using [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient/): - -``` -Install-Package Serilog.Exceptions.MsSqlServer -``` - -Add the `SqlExceptionDestructurer` during setup: -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new SqlExceptionDestructurer() })) -``` - -### Serilog.Exceptions.EntityFrameworkCore - -[![Serilog.Exceptions.EntityFrameworkCore NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.EntityFrameworkCore.svg)](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore/) [![Serilog.Exceptions.EntityFrameworkCore package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/ee2cd6f8-4c93-4774-9398-23c49ba41928/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=ee2cd6f8-4c93-4774-9398-23c49ba41928&preferRelease=true) [![Serilog.Exceptions.EntityFrameworkCore NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.EntityFrameworkCore)](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore) - -> **WARNING**: In versions of Serilog.Exceptions older than [8.0.0](https://github.com/RehanSaeed/Serilog.Exceptions/releases/tag/8.0.0), if you are using EntityFrameworkCore with Serilog.Exceptions you must add this, otherwise in certain cases your entire database will be logged! This is because the exceptions in Entity Framework Core have properties that link to the entire database schema in them (See [#100](https://github.com/RehanSaeed/Serilog.Exceptions/issues/100), [aspnet/EntityFrameworkCore#15214](https://github.com/aspnet/EntityFrameworkCore/issues/15214)). Newer versions of Serilog.Exceptions avoids this issue by preventing the destructure of properties that implement IQueryable preventing their execution. - -Add the [Serilog.Exceptions.EntityFrameworkCore](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore/) NuGet package to your project when using EntityFrameworkCore in your project - -``` -Install-Package Serilog.Exceptions.EntityFrameworkCore -``` - -Add the `DbUpdateExceptionDestructurer` during setup: -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() })) -``` - -### Serilog.Exceptions.Refit - -[![Serilog.Exceptions.Refit NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.Refit.svg)](https://www.nuget.org/packages/Serilog.Exceptions.Refit/) -[![Serilog.Exceptions.Refit package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/dce98084-312a-4939-b879-07bc25734572/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=dce98084-312a-4939-b879-07bc25734572&preferRelease=true) [![Serilog.Exceptions.Refit NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.Refit)](https://www.nuget.org/packages/Serilog.Exceptions.Refit) - -Add the [Serilog.Exceptions.Refit](https://www.nuget.org/packages/Serilog.Exceptions.Refit/) NuGet package to your project to provide detailed logging for the `ApiException` when using [Refit](https://www.nuget.org/packages/Refit/): - -``` -Install-Package Serilog.Exceptions.Refit -``` - -Add the `ApiExceptionDestructurer` during setup: -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new ApiExceptionDestructurer() })) -``` - -Depending on your Serilog setup, common `System.Exception` properties may already be logged. To omit the logging of these properties, use the overloaded -constructor as follows: - -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new ApiExceptionDestructurer(destructureCommonExceptionProperties: false) })) -``` - -The default configuration logs the following properties of an `ApiException`: - -- `Uri` -- `StatusCode` - -In addition, the `ApiException.Content` property can be logged with the following setup: - -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new ApiExceptionDestructurer(destructureHttpContent: true) })) -``` - -Be careful with this option as the HTTP body could be very large and/or contain sensitive information. - -## 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: - -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithDestructurers(new[] { new MyCustomExceptionDestructurer() })) -``` - -If you write a destructurer that is not included in this project (even for a third party library), please contribute it. - -## Additional configuration - -You can configure some additional properties of destructuring process, by passing custom destructuring options during setup: - -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithRootName("Exception")) -``` - -Currently following options are supported: - -- `RootName`: The property name which will hold destructured exception, `ExceptionDetail` by default. -- `Filter`: The object implementing `IExceptionPropertyFilter` that will have a chance to filter properties just before they are put in destructured exception object. Go to "Filtering properties" section for details. -- `DestructuringDepth`: The maximum depth of reflection based recursive destructuring process. -- `ReflectionBasedDestructurer`: Reflection based destructurer is enabled by default, but can be disabled in case you want to have complete control over destructuring process. You will have to register destructurers for all exceptions explicitly. - -## Filtering properties - -You may want to skip some properties of all or part your exception classes without directly creating or modifying custom destructurers. Serilog.Exceptions supports this functionality using a filter. - -Most typical use case is the need to skip `StackTrace` and `TargetSite`. Serilog is already reporting them so you may want Serilog.Exceptions to skip them to save space and processing time. To do that you just need to modify a line in configuration: - -```csharp -.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder().WithFilter(someFilter)); -``` - -Filtering for other scenarios is also supported: - -- Use `WithIgnoreStackTraceAndTargetSiteExceptionFilter` if you need to filter some other set of named properties -- Implement custom `IExceptionPropertyFilter` if you need some different filtering logic -- Use `CompositeExceptionPropertyFilter` to combine multiple filters - -## Continuous Integration - -| Name | Operating System | Status | History | -| :--- | :--- | :--- | :--- | -| Azure Pipelines | Ubuntu | [![Azure Pipelines Ubuntu Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Linux)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | -| Azure Pipelines | Mac | [![Azure Pipelines Mac Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Mac)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | -| Azure Pipelines | Windows | [![Azure Pipelines Windows Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Windows)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | -| Azure Pipelines | Overall | [![Azure Pipelines Overall Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | [![Azure DevOps Build History](https://buildstats.info/azurepipelines/chart/serilog-exceptions/Serilog.Exceptions/1?branch=main&includeBuildsFromPullRequest=false)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | -| GitHub Actions | Ubuntu, Mac & Windows | [![GitHub Actions Status](https://github.com/RehanSaeed/Serilog.Exceptions/workflows/Build/badge.svg?branch=main)](https://github.com/RehanSaeed/Serilog.Exceptions/actions) | [![GitHub Actions Build History](https://buildstats.info/github/chart/RehanSaeed/Serilog.Exceptions?branch=main&includeBuildsFromPullRequest=false)](https://github.com/RehanSaeed/Serilog.Exceptions/actions) | -| AppVeyor | Ubuntu, Mac & Windows | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/7ijbthe6iig9phn6/branch/main?svg=true)](https://ci.appveyor.com/project/RehanSaeed/serilog-exceptions/branch/main) | [![AppVeyor Build History](https://buildstats.info/appveyor/chart/RehanSaeed/serilog-exceptions?branch=main&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/RehanSaeed/serilog-exceptions) | - -## Contributions and Thanks - -Please view the [contributing guide](https://github.com/RehanSaeed/Serilog.Exceptions/blob/main/.github/CONTRIBUTING.md) for more information. - -- [304NotModified](https://github.com/304NotModified) - Added Markdown syntax highlighting. -- [joelweiss](https://github.com/joelweiss) - Added Entity Framework Core destructurers. -- [krajek](https://github.com/krajek) & [JeroenDragt](https://github.com/JeroenDragt) - For adding filters to help ignore exception properties you don't want logged. -- [krajek](https://github.com/krajek) - For helping with cyclic dependencies when using the reflection destructurer. -- [mraming](https://github.com/mraming) - For logging properties that throw exceptions. -- [optical](https://github.com/optical) - For a huge VS 2017 upgrade PR. -- [Jérémie Bertrand](https://github.com/laedit) - For making Serilog.Exceptions compatible with Mono. -- [krajek](https://github.com/krajek) - For writing some much needed unit tests. +![Serilog.Exceptions Banner](https://github.com/RehanSaeed/Serilog.Exceptions/blob/main/Images/Banner.png) + +[![Serilog.Exceptions NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.svg)](https://www.nuget.org/packages/Serilog.Exceptions/) [![Serilog.Exceptions package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/212043f6-5fe5-4c79-949e-162156b89894/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=212043f6-5fe5-4c79-949e-162156b89894&preferRelease=true) [![Serilog.Exceptions NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions)](https://www.nuget.org/packages/Serilog.Exceptions) [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/RehanSaeedUK) [![Twitter Follow](https://img.shields.io/twitter/follow/rehansaeeduk.svg?style=social&label=Follow)](https://twitter.com/RehanSaeedUK) + +Serilog.Exceptions is an add-on to [Serilog](https://serilog.net) to log exception details and custom properties that are not output in `Exception.ToString()`. + +## What Does It Do? + +Your JSON logs will now be supplemented with detailed exception information and even custom exception properties. Here is an example of what happens when you log a `DbEntityValidationException` from EntityFramework (This exception is notorious for having deeply nested custom properties which are not included in the `.ToString()`). + +```csharp +try +{ + ... +} +catch (DbEntityValidationException exception) +{ + logger.Error(exception, "Hello World"); +} +``` + +The code above logs the following: + +```json +{ + "Timestamp": "2015-12-07T12:26:24.0557671+00:00", + "Level": "Error", + "MessageTemplate": "Hello World", + "RenderedMessage": "Hello World", + "Exception": "System.Data.Entity.Validation.DbEntityValidationException: Message", + "Properties": { + "ExceptionDetail": { + "EntityValidationErrors": [ + { + "Entry": null, + "ValidationErrors": [ + { + "PropertyName": "PropertyName", + "ErrorMessage": "PropertyName is Required.", + "Type": "System.Data.Entity.Validation.DbValidationError" + } + ], + "IsValid": false, + "Type": "System.Data.Entity.Validation.DbEntityValidationResult" + } + ], + "Message": "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.", + "Data": {}, + "InnerException": null, + "TargetSite": null, + "StackTrace": null, + "HelpLink": null, + "Source": null, + "HResult": -2146232032, + "Type": "System.Data.Entity.Validation.DbEntityValidationException" + }, + "Source": "418169ff-e65f-456e-8b0d-42a0973c3577" + } +} +``` + +## Getting Started + +Add the [Serilog.Exceptions](https://www.nuget.org/packages/Serilog.Exceptions/) NuGet package to your project using the NuGet Package Manager or run the following command in the Package Console Window: + +```powershell +dotnet add package Serilog.Exceptions +``` + +When setting up your logger, add the `WithExceptionDetails()` line like so: + +```csharp +using Serilog; +using Serilog.Exceptions; + +ILogger logger = new LoggerConfiguration() + .Enrich.WithExceptionDetails() + .WriteTo.RollingFile( + new JsonFormatter(renderMessage: true), + @"C:\logs\log-{Date}.txt") + .CreateLogger(); +``` + +Make sure that the sink's formatter outputs enriched properties. `Serilog.Sinks.Console` and many more do not do that by default. You may need to add `{Properties:j}` to your sink's format template. For example, configuration for console sink may look like that: + +```csharp +.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {Properties:j}") +``` + +### JSON `appSettings.json` configuration + +Alternatively to fluent configuration setting can be stored in application configuration using [_Serilog.Settings.Configuration_](https://github.com/serilog/serilog-settings-configuration): +```json +{ + "Serilog": { + "Using": [ "Serilog.Exceptions" ], + "Enrich": [ "WithExceptionDetails" ], + "WriteTo": [ + { "Name": "Console" } + ] + } +} +``` + +## Performance + +This library has custom code to deal with extra properties on most common exception types and only falls back to using reflection to get the extra information if the exception is not supported by Serilog.Exceptions internally. Reflection overhead is present but minimal, because all the expensive relection-based operations are done only once per exception-type. + +## Additional Destructurers + +### Serilog.Exceptions.SqlServer + +[![Serilog.Exceptions.SqlServer NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.SqlServer.svg)](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer/) [![Serilog.Exceptions.SqlServer package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/67be830c-2c0f-4df8-be30-771d817b382f/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=67be830c-2c0f-4df8-be30-771d817b382f&preferRelease=true) [![Serilog.Exceptions.SqlServer NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.SqlServer)](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer) + +Add the [Serilog.Exceptions.SqlServer](https://www.nuget.org/packages/Serilog.Exceptions.SqlServer/) NuGet package to your project to avoid the reflection based destructurer for `SqlException` when using [System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient/): + +``` +Install-Package Serilog.Exceptions.SqlServer +``` + +Add the `SqlExceptionDestructurer` during setup: +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new SqlExceptionDestructurer() })) +``` + +### Serilog.Exceptions.MsSqlServer + +[![Serilog.Exceptions.MsSqlServer NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.MsSqlServer.svg)](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer/) +[![Serilog.Exceptions.MsSqlServer package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/dce98084-312a-4939-b879-07bc25734572/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=dce98084-312a-4939-b879-07bc25734572&preferRelease=true) [![Serilog.Exceptions.MsSqlServer NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.MsSqlServer)](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer) + +Add the [Serilog.Exceptions.MsSqlServer](https://www.nuget.org/packages/Serilog.Exceptions.MsSqlServer/) NuGet package to your project to avoid the reflection based destructurer for `SqlException` when using [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient/): + +``` +Install-Package Serilog.Exceptions.MsSqlServer +``` + +Add the `SqlExceptionDestructurer` during setup: +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new SqlExceptionDestructurer() })) +``` + +### Serilog.Exceptions.EntityFrameworkCore + +[![Serilog.Exceptions.EntityFrameworkCore NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.EntityFrameworkCore.svg)](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore/) [![Serilog.Exceptions.EntityFrameworkCore package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/ee2cd6f8-4c93-4774-9398-23c49ba41928/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=ee2cd6f8-4c93-4774-9398-23c49ba41928&preferRelease=true) [![Serilog.Exceptions.EntityFrameworkCore NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.EntityFrameworkCore)](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore) + +> **WARNING**: In versions of Serilog.Exceptions older than [8.0.0](https://github.com/RehanSaeed/Serilog.Exceptions/releases/tag/8.0.0), if you are using EntityFrameworkCore with Serilog.Exceptions you must add this, otherwise in certain cases your entire database will be logged! This is because the exceptions in Entity Framework Core have properties that link to the entire database schema in them (See [#100](https://github.com/RehanSaeed/Serilog.Exceptions/issues/100), [aspnet/EntityFrameworkCore#15214](https://github.com/aspnet/EntityFrameworkCore/issues/15214)). Newer versions of Serilog.Exceptions avoids this issue by preventing the destructure of properties that implement IQueryable preventing their execution. + +Add the [Serilog.Exceptions.EntityFrameworkCore](https://www.nuget.org/packages/Serilog.Exceptions.EntityFrameworkCore/) NuGet package to your project when using EntityFrameworkCore in your project + +``` +Install-Package Serilog.Exceptions.EntityFrameworkCore +``` + +Add the `DbUpdateExceptionDestructurer` during setup: +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new DbUpdateExceptionDestructurer() })) +``` + +### Serilog.Exceptions.Refit + +[![Serilog.Exceptions.Refit NuGet Package](https://img.shields.io/nuget/v/Serilog.Exceptions.Refit.svg)](https://www.nuget.org/packages/Serilog.Exceptions.Refit/) +[![Serilog.Exceptions.Refit package in serilog-exceptions feed in Azure Artifacts](https://feeds.dev.azure.com/serilog-exceptions/_apis/public/Packaging/Feeds/8479813c-da6b-4677-b40d-78df8725dc9c/Packages/dce98084-312a-4939-b879-07bc25734572/Badge)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_packaging?_a=package&feed=8479813c-da6b-4677-b40d-78df8725dc9c&package=dce98084-312a-4939-b879-07bc25734572&preferRelease=true) [![Serilog.Exceptions.Refit NuGet Package Downloads](https://img.shields.io/nuget/dt/Serilog.Exceptions.Refit)](https://www.nuget.org/packages/Serilog.Exceptions.Refit) + +Add the [Serilog.Exceptions.Refit](https://www.nuget.org/packages/Serilog.Exceptions.Refit/) NuGet package to your project to provide detailed logging for the `ApiException` when using [Refit](https://www.nuget.org/packages/Refit/): + +``` +Install-Package Serilog.Exceptions.Refit +``` + +Add the `ApiExceptionDestructurer` during setup: +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new ApiExceptionDestructurer() })) +``` + +Depending on your Serilog setup, common `System.Exception` properties may already be logged. To omit the logging of these properties, use the overloaded +constructor as follows: + +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new ApiExceptionDestructurer(destructureCommonExceptionProperties: false) })) +``` + +The default configuration logs the following properties of an `ApiException`: + +- `Uri` +- `StatusCode` + +In addition, the `ApiException.Content` property can be logged with the following setup: + +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new ApiExceptionDestructurer(destructureHttpContent: true) })) +``` + +Be careful with this option as the HTTP body could be very large and/or contain sensitive information. + +## 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: + +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithDestructurers(new[] { new MyCustomExceptionDestructurer() })) +``` + +If you write a destructurer that is not included in this project (even for a third party library), please contribute it. + +## Additional configuration + +You can configure some additional properties of destructuring process, by passing custom destructuring options during setup: + +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithRootName("Exception")) +``` + +Currently following options are supported: + +- `RootName`: The property name which will hold destructured exception, `ExceptionDetail` by default. +- `Filter`: The object implementing `IExceptionPropertyFilter` that will have a chance to filter properties just before they are put in destructured exception object. Go to "Filtering properties" section for details. +- `DestructuringDepth`: The maximum depth of reflection based recursive destructuring process. +- `ReflectionBasedDestructurer`: Reflection based destructurer is enabled by default, but can be disabled in case you want to have complete control over destructuring process. You will have to register destructurers for all exceptions explicitly. + +## Filtering properties + +You may want to skip some properties of all or part your exception classes without directly creating or modifying custom destructurers. Serilog.Exceptions supports this functionality using a filter. + +Most typical use case is the need to skip `StackTrace` and `TargetSite`. Serilog is already reporting them so you may want Serilog.Exceptions to skip them to save space and processing time. To do that you just need to modify a line in configuration: + +```csharp +.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder().WithFilter(someFilter)); +``` + +Filtering for other scenarios is also supported: + +- Use `WithIgnoreStackTraceAndTargetSiteExceptionFilter` if you need to filter some other set of named properties +- Implement custom `IExceptionPropertyFilter` if you need some different filtering logic +- Use `CompositeExceptionPropertyFilter` to combine multiple filters + +## Continuous Integration + +| Name | Operating System | Status | History | +| :--- | :--- | :--- | :--- | +| Azure Pipelines | Ubuntu | [![Azure Pipelines Ubuntu Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Linux)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | +| Azure Pipelines | Mac | [![Azure Pipelines Mac Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Mac)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | +| Azure Pipelines | Windows | [![Azure Pipelines Windows Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main&stageName=Build&jobName=Build&configuration=Build%20Windows)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | +| Azure Pipelines | Overall | [![Azure Pipelines Overall Build Status](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_apis/build/status/RehanSaeed.Serilog.Exceptions?branchName=main)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | [![Azure DevOps Build History](https://buildstats.info/azurepipelines/chart/serilog-exceptions/Serilog.Exceptions/1?branch=main&includeBuildsFromPullRequest=false)](https://dev.azure.com/serilog-exceptions/Serilog.Exceptions/_build/latest?definitionId=1&branchName=main) | +| GitHub Actions | Ubuntu, Mac & Windows | [![GitHub Actions Status](https://github.com/RehanSaeed/Serilog.Exceptions/workflows/Build/badge.svg?branch=main)](https://github.com/RehanSaeed/Serilog.Exceptions/actions) | [![GitHub Actions Build History](https://buildstats.info/github/chart/RehanSaeed/Serilog.Exceptions?branch=main&includeBuildsFromPullRequest=false)](https://github.com/RehanSaeed/Serilog.Exceptions/actions) | +| AppVeyor | Ubuntu, Mac & Windows | [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/7ijbthe6iig9phn6/branch/main?svg=true)](https://ci.appveyor.com/project/RehanSaeed/serilog-exceptions/branch/main) | [![AppVeyor Build History](https://buildstats.info/appveyor/chart/RehanSaeed/serilog-exceptions?branch=main&includeBuildsFromPullRequest=false)](https://ci.appveyor.com/project/RehanSaeed/serilog-exceptions) | + +## Contributions and Thanks + +Please view the [contributing guide](https://github.com/RehanSaeed/Serilog.Exceptions/blob/main/.github/CONTRIBUTING.md) for more information. + +- [304NotModified](https://github.com/304NotModified) - Added Markdown syntax highlighting. +- [joelweiss](https://github.com/joelweiss) - Added Entity Framework Core destructurers. +- [krajek](https://github.com/krajek) & [JeroenDragt](https://github.com/JeroenDragt) - For adding filters to help ignore exception properties you don't want logged. +- [krajek](https://github.com/krajek) - For helping with cyclic dependencies when using the reflection destructurer. +- [mraming](https://github.com/mraming) - For logging properties that throw exceptions. +- [optical](https://github.com/optical) - For a huge VS 2017 upgrade PR. +- [Jérémie Bertrand](https://github.com/laedit) - For making Serilog.Exceptions compatible with Mono. +- [krajek](https://github.com/krajek) - For writing some much needed unit tests. diff --git a/Serilog.Exceptions.sln b/Serilog.Exceptions.sln index c6d4b515..1f65dd8d 100644 --- a/Serilog.Exceptions.sln +++ b/Serilog.Exceptions.sln @@ -1,157 +1,157 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31423.177 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FBEEA2-0F88-487E-99C3-5D865CBE79B6}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitattributes = .gitattributes - .gitignore = .gitignore - appveyor.yml = appveyor.yml - azure-pipelines.yml = azure-pipelines.yml - build.cake = build.cake - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - dotnet-tools.json = dotnet-tools.json - global.json = global.json - Key.snk = Key.snk - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{C5508012-7216-4ABE-AB2F-B166ED5FF94F}" - ProjectSection(SolutionItems) = preProject - Source\Directory.Build.props = Source\Directory.Build.props - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D6E871A8-2DFF-4676-BED8-046B164919B9}" - ProjectSection(SolutionItems) = preProject - Tests\.editorconfig = Tests\.editorconfig - Tests\Directory.Build.props = Tests\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions", "Source\Serilog.Exceptions\Serilog.Exceptions.csproj", "{D8034654-4A6F-4857-9637-62098FC9267A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Test", "Tests\Serilog.Exceptions.Test\Serilog.Exceptions.Test.csproj", "{7C962929-2138-4787-ABA7-94E8D3F9C20C}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{D2CAFFAF-CBF6-493A-AA61-4916FE7D586C}" - ProjectSection(SolutionItems) = preProject - LICENSE.md = LICENSE.md - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.SqlServer", "Source\Serilog.Exceptions.SqlServer\Serilog.Exceptions.SqlServer.csproj", "{F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{5CE72C42-F5C5-405B-BE8A-9AFDA7F43CB2}" - ProjectSection(SolutionItems) = preProject - Benchmarks\.editorconfig = Benchmarks\.editorconfig - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Benchmark", "Benchmarks\Serilog.Exceptions.Benchmark\Serilog.Exceptions.Benchmark.csproj", "{5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.EntityFrameworkCore", "Source\Serilog.Exceptions.EntityFrameworkCore\Serilog.Exceptions.EntityFrameworkCore.csproj", "{69C1004B-627D-4B90-8FAD-4B4069115A56}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{8B74D735-C0D8-48BA-8500-7EC6A817610B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExceptionFinderTool", "Tools\ExceptionFinderTool\ExceptionFinderTool.csproj", "{B6603A53-F254-43ED-9A03-08D95F8F3BAC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{6C126007-ECA1-4442-9ECA-C5CED430BCB8}" - ProjectSection(SolutionItems) = preProject - Images\Banner.png = Images\Banner.png - Images\Hero.png = Images\Hero.png - Images\Icon.png = Images\Icon.png - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{25698817-D59E-4F85-9B44-59FD50E9ED4B}" - ProjectSection(SolutionItems) = preProject - .github\ISSUE_TEMPLATE\BUG_REPORT.yml = .github\ISSUE_TEMPLATE\BUG_REPORT.yml - .github\ISSUE_TEMPLATE\FEATURE_REQUEST.yml = .github\ISSUE_TEMPLATE\FEATURE_REQUEST.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{C50E9892-B2DF-43A6-9E00-BA2952933FA4}" - ProjectSection(SolutionItems) = preProject - .github\CODE_OF_CONDUCT.md = .github\CODE_OF_CONDUCT.md - .github\CONTRIBUTING.md = .github\CONTRIBUTING.md - .github\FUNDING.yml = .github\FUNDING.yml - .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md - .github\SECURITY.md = .github\SECURITY.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{2C245036-D7F6-4F7C-9BB6-5AFBCCE480F7}" - ProjectSection(SolutionItems) = preProject - .github\release-drafter.yml = .github\release-drafter.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{4F089B23-3121-4935-B24E-7A9A497BD9FE}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build.yml = .github\workflows\build.yml - .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml - .github\workflows\release-drafter.yml = .github\workflows\release-drafter.yml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.MsSqlServer", "Source\Serilog.Exceptions.MsSqlServer\Serilog.Exceptions.MsSqlServer.csproj", "{0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Refit", "Source\Serilog.Exceptions.Refit\Serilog.Exceptions.Refit.csproj", "{0EABF22F-F070-4F8D-B165-DD4C4AB62820}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D8034654-4A6F-4857-9637-62098FC9267A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D8034654-4A6F-4857-9637-62098FC9267A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D8034654-4A6F-4857-9637-62098FC9267A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D8034654-4A6F-4857-9637-62098FC9267A}.Release|Any CPU.Build.0 = Release|Any CPU - {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Release|Any CPU.Build.0 = Release|Any CPU - {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Release|Any CPU.Build.0 = Release|Any CPU - {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Release|Any CPU.Build.0 = Release|Any CPU - {69C1004B-627D-4B90-8FAD-4B4069115A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {69C1004B-627D-4B90-8FAD-4B4069115A56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {69C1004B-627D-4B90-8FAD-4B4069115A56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {69C1004B-627D-4B90-8FAD-4B4069115A56}.Release|Any CPU.Build.0 = Release|Any CPU - {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Release|Any CPU.Build.0 = Release|Any CPU - {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Release|Any CPU.Build.0 = Release|Any CPU - {0EABF22F-F070-4F8D-B165-DD4C4AB62820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {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 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D8034654-4A6F-4857-9637-62098FC9267A} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} - {7C962929-2138-4787-ABA7-94E8D3F9C20C} = {D6E871A8-2DFF-4676-BED8-046B164919B9} - {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} - {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA} = {5CE72C42-F5C5-405B-BE8A-9AFDA7F43CB2} - {69C1004B-627D-4B90-8FAD-4B4069115A56} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} - {B6603A53-F254-43ED-9A03-08D95F8F3BAC} = {8B74D735-C0D8-48BA-8500-7EC6A817610B} - {25698817-D59E-4F85-9B44-59FD50E9ED4B} = {C50E9892-B2DF-43A6-9E00-BA2952933FA4} - {C50E9892-B2DF-43A6-9E00-BA2952933FA4} = {D2CAFFAF-CBF6-493A-AA61-4916FE7D586C} - {2C245036-D7F6-4F7C-9BB6-5AFBCCE480F7} = {76FBEEA2-0F88-487E-99C3-5D865CBE79B6} - {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} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BE74AFAC-AC6F-4B80-860F-15C22BEE1A38} - EndGlobalSection - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31423.177 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76FBEEA2-0F88-487E-99C3-5D865CBE79B6}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + appveyor.yml = appveyor.yml + azure-pipelines.yml = azure-pipelines.yml + build.cake = build.cake + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + dotnet-tools.json = dotnet-tools.json + global.json = global.json + Key.snk = Key.snk + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{C5508012-7216-4ABE-AB2F-B166ED5FF94F}" + ProjectSection(SolutionItems) = preProject + Source\Directory.Build.props = Source\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D6E871A8-2DFF-4676-BED8-046B164919B9}" + ProjectSection(SolutionItems) = preProject + Tests\.editorconfig = Tests\.editorconfig + Tests\Directory.Build.props = Tests\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions", "Source\Serilog.Exceptions\Serilog.Exceptions.csproj", "{D8034654-4A6F-4857-9637-62098FC9267A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Test", "Tests\Serilog.Exceptions.Test\Serilog.Exceptions.Test.csproj", "{7C962929-2138-4787-ABA7-94E8D3F9C20C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Documentation", "Documentation", "{D2CAFFAF-CBF6-493A-AA61-4916FE7D586C}" + ProjectSection(SolutionItems) = preProject + LICENSE.md = LICENSE.md + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.SqlServer", "Source\Serilog.Exceptions.SqlServer\Serilog.Exceptions.SqlServer.csproj", "{F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", "{5CE72C42-F5C5-405B-BE8A-9AFDA7F43CB2}" + ProjectSection(SolutionItems) = preProject + Benchmarks\.editorconfig = Benchmarks\.editorconfig + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Benchmark", "Benchmarks\Serilog.Exceptions.Benchmark\Serilog.Exceptions.Benchmark.csproj", "{5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.EntityFrameworkCore", "Source\Serilog.Exceptions.EntityFrameworkCore\Serilog.Exceptions.EntityFrameworkCore.csproj", "{69C1004B-627D-4B90-8FAD-4B4069115A56}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{8B74D735-C0D8-48BA-8500-7EC6A817610B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExceptionFinderTool", "Tools\ExceptionFinderTool\ExceptionFinderTool.csproj", "{B6603A53-F254-43ED-9A03-08D95F8F3BAC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{6C126007-ECA1-4442-9ECA-C5CED430BCB8}" + ProjectSection(SolutionItems) = preProject + Images\Banner.png = Images\Banner.png + Images\Hero.png = Images\Hero.png + Images\Icon.png = Images\Icon.png + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{25698817-D59E-4F85-9B44-59FD50E9ED4B}" + ProjectSection(SolutionItems) = preProject + .github\ISSUE_TEMPLATE\BUG_REPORT.yml = .github\ISSUE_TEMPLATE\BUG_REPORT.yml + .github\ISSUE_TEMPLATE\FEATURE_REQUEST.yml = .github\ISSUE_TEMPLATE\FEATURE_REQUEST.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{C50E9892-B2DF-43A6-9E00-BA2952933FA4}" + ProjectSection(SolutionItems) = preProject + .github\CODE_OF_CONDUCT.md = .github\CODE_OF_CONDUCT.md + .github\CONTRIBUTING.md = .github\CONTRIBUTING.md + .github\FUNDING.yml = .github\FUNDING.yml + .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md + .github\SECURITY.md = .github\SECURITY.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{2C245036-D7F6-4F7C-9BB6-5AFBCCE480F7}" + ProjectSection(SolutionItems) = preProject + .github\release-drafter.yml = .github\release-drafter.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{4F089B23-3121-4935-B24E-7A9A497BD9FE}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + .github\workflows\release-drafter.yml = .github\workflows\release-drafter.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.MsSqlServer", "Source\Serilog.Exceptions.MsSqlServer\Serilog.Exceptions.MsSqlServer.csproj", "{0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Exceptions.Refit", "Source\Serilog.Exceptions.Refit\Serilog.Exceptions.Refit.csproj", "{0EABF22F-F070-4F8D-B165-DD4C4AB62820}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D8034654-4A6F-4857-9637-62098FC9267A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8034654-4A6F-4857-9637-62098FC9267A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8034654-4A6F-4857-9637-62098FC9267A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8034654-4A6F-4857-9637-62098FC9267A}.Release|Any CPU.Build.0 = Release|Any CPU + {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C962929-2138-4787-ABA7-94E8D3F9C20C}.Release|Any CPU.Build.0 = Release|Any CPU + {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7}.Release|Any CPU.Build.0 = Release|Any CPU + {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA}.Release|Any CPU.Build.0 = Release|Any CPU + {69C1004B-627D-4B90-8FAD-4B4069115A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69C1004B-627D-4B90-8FAD-4B4069115A56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69C1004B-627D-4B90-8FAD-4B4069115A56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69C1004B-627D-4B90-8FAD-4B4069115A56}.Release|Any CPU.Build.0 = Release|Any CPU + {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6603A53-F254-43ED-9A03-08D95F8F3BAC}.Release|Any CPU.Build.0 = Release|Any CPU + {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A21D2AD-024B-4F3D-95F4-BAEFEEE95945}.Release|Any CPU.Build.0 = Release|Any CPU + {0EABF22F-F070-4F8D-B165-DD4C4AB62820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D8034654-4A6F-4857-9637-62098FC9267A} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} + {7C962929-2138-4787-ABA7-94E8D3F9C20C} = {D6E871A8-2DFF-4676-BED8-046B164919B9} + {F5BBB5C1-5920-40F3-8C44-01A54CC3D6B7} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} + {5A82CFFC-EC33-4B7B-A6F6-7FADC0E2DDAA} = {5CE72C42-F5C5-405B-BE8A-9AFDA7F43CB2} + {69C1004B-627D-4B90-8FAD-4B4069115A56} = {C5508012-7216-4ABE-AB2F-B166ED5FF94F} + {B6603A53-F254-43ED-9A03-08D95F8F3BAC} = {8B74D735-C0D8-48BA-8500-7EC6A817610B} + {25698817-D59E-4F85-9B44-59FD50E9ED4B} = {C50E9892-B2DF-43A6-9E00-BA2952933FA4} + {C50E9892-B2DF-43A6-9E00-BA2952933FA4} = {D2CAFFAF-CBF6-493A-AA61-4916FE7D586C} + {2C245036-D7F6-4F7C-9BB6-5AFBCCE480F7} = {76FBEEA2-0F88-487E-99C3-5D865CBE79B6} + {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} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE74AFAC-AC6F-4B80-860F-15C22BEE1A38} + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection +EndGlobal diff --git a/Source/Serilog.Exceptions.SqlServer/Destructurers/SqlExceptionDestructurer.cs b/Source/Serilog.Exceptions.SqlServer/Destructurers/SqlExceptionDestructurer.cs index f903c469..c6fee8f1 100644 --- a/Source/Serilog.Exceptions.SqlServer/Destructurers/SqlExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions.SqlServer/Destructurers/SqlExceptionDestructurer.cs @@ -1,38 +1,38 @@ -namespace Serilog.Exceptions.SqlServer.Destructurers; - -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Destructurers; - -/// -/// A destructurer for . -/// -/// -public class SqlExceptionDestructurer : ExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] { typeof(SqlException) }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var sqlException = (SqlException)exception; - propertiesBag.AddProperty(nameof(SqlException.ClientConnectionId), sqlException.ClientConnectionId); - propertiesBag.AddProperty(nameof(SqlException.Class), sqlException.Class); - propertiesBag.AddProperty(nameof(SqlException.LineNumber), sqlException.LineNumber); - propertiesBag.AddProperty(nameof(SqlException.Number), sqlException.Number); - propertiesBag.AddProperty(nameof(SqlException.Server), sqlException.Server); - propertiesBag.AddProperty(nameof(SqlException.State), sqlException.State); - propertiesBag.AddProperty(nameof(SqlException.Errors), sqlException.Errors.Cast().ToArray()); -#pragma warning restore CA1062 // Validate arguments of public methods - } -} +namespace Serilog.Exceptions.SqlServer.Destructurers; + +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Destructurers; + +/// +/// A destructurer for . +/// +/// +public class SqlExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] { typeof(SqlException) }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var sqlException = (SqlException)exception; + propertiesBag.AddProperty(nameof(SqlException.ClientConnectionId), sqlException.ClientConnectionId); + propertiesBag.AddProperty(nameof(SqlException.Class), sqlException.Class); + propertiesBag.AddProperty(nameof(SqlException.LineNumber), sqlException.LineNumber); + propertiesBag.AddProperty(nameof(SqlException.Number), sqlException.Number); + propertiesBag.AddProperty(nameof(SqlException.Server), sqlException.Server); + propertiesBag.AddProperty(nameof(SqlException.State), sqlException.State); + propertiesBag.AddProperty(nameof(SqlException.Errors), sqlException.Errors.Cast().ToArray()); +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Source/Serilog.Exceptions.SqlServer/Serilog.Exceptions.SqlServer.csproj b/Source/Serilog.Exceptions.SqlServer/Serilog.Exceptions.SqlServer.csproj index c291d56a..a0790a6d 100644 --- a/Source/Serilog.Exceptions.SqlServer/Serilog.Exceptions.SqlServer.csproj +++ b/Source/Serilog.Exceptions.SqlServer/Serilog.Exceptions.SqlServer.csproj @@ -1,21 +1,21 @@ - - - - net6.0;net5.0;netstandard2.1;netstandard2.0;netstandard1.3;net472;net461 - - - - Serilog Exceptions - Log exception details and custom properties that are not output in Exception.ToString(). Contains custom destructurers for SQL Server exceptions. - Serilog;Exception;Log;Logging;Detail;Details;SQL;Server - - - - - - - - - - - + + + + net6.0;net5.0;netstandard2.1;netstandard2.0;netstandard1.3;net472;net461 + + + + Serilog Exceptions + Log exception details and custom properties that are not output in Exception.ToString(). Contains custom destructurers for SQL Server exceptions. + Serilog;Exception;Log;Logging;Detail;Details;SQL;Server + + + + + + + + + + + diff --git a/Source/Serilog.Exceptions/Core/DestructuringOptionsBuilder.cs b/Source/Serilog.Exceptions/Core/DestructuringOptionsBuilder.cs index b0b76f57..b9f0f56e 100644 --- a/Source/Serilog.Exceptions/Core/DestructuringOptionsBuilder.cs +++ b/Source/Serilog.Exceptions/Core/DestructuringOptionsBuilder.cs @@ -1,185 +1,185 @@ -namespace Serilog.Exceptions.Core; - -using System; -using System.Collections.Generic; -using System.Globalization; -using Serilog.Events; -using Serilog.Exceptions.Destructurers; -using Serilog.Exceptions.Filters; - -/// -/// Accumulates destructuring options to be used by the . -/// -public class DestructuringOptionsBuilder : IDestructuringOptions -{ - /// - /// Default set of destructurers. Destructurers cover all of the exceptions from standard library. - /// - public static readonly IExceptionDestructurer[] DefaultDestructurers = - { - new ExceptionDestructurer(), - new ArgumentExceptionDestructurer(), - new ArgumentOutOfRangeExceptionDestructurer(), - new AggregateExceptionDestructurer(), - new RegexMatchTimeoutExceptionDestructurer(), - new ReflectionTypeLoadExceptionDestructurer(), - new OperationCanceledExceptionDestructurer(), - new TaskCanceledExceptionDestructurer(), - new SocketExceptionDestructurer(), - }; - - /// - /// Filter that ignores and Exception.TargetSite properties. - /// Usually, they can be safely ignored, because Serilog attaches them tog already. - /// - public static readonly IExceptionPropertyFilter IgnoreStackTraceAndTargetSiteExceptionFilter = - -#if NET461 || NET472 - new IgnorePropertyByNameExceptionFilter( - nameof(Exception.StackTrace), - nameof(Exception.TargetSite)); -#else - new IgnorePropertyByNameExceptionFilter( - nameof(Exception.StackTrace)); -#endif - - private readonly List destructurers = new(); - - /// - /// Gets the name of the property which value will be filled with destructured exception. - /// - public string RootName { get; private set; } = "ExceptionDetail"; - - /// - /// Gets the maximum depth of destructuring to which reflection destructurer will proceed. - /// - public int DestructuringDepth { get; private set; } = 10; - - /// - /// Gets a value indicating whether to disable the reflection based destructurer. - /// - public bool DisableReflectionBasedDestructurer { get; private set; } - - /// - /// Gets a collection of destructurers that will be used to handle exception. - /// - public IEnumerable Destructurers => this.destructurers; - - /// - /// Gets a global filter, that will be applied to every destructured property just before it is added to the result. - /// - public IExceptionPropertyFilter? Filter { get; private set; } - - /// - /// Accumulates destructurers to be used by . - /// - /// Collection of destructurers. - /// Options builder for method chaining. - public DestructuringOptionsBuilder WithDestructurers(IEnumerable destructurers) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(destructurers); -#else - if (destructurers is null) - { - throw new ArgumentNullException(nameof(destructurers)); - } -#endif - - this.destructurers.AddRange(destructurers); - return this; - } - - /// - /// Adds destructurers for a known set of exceptions from standard library. - /// - /// Options builder for method chaining. - public DestructuringOptionsBuilder WithDefaultDestructurers() => - this.WithDestructurers(DefaultDestructurers); - - /// - /// Sets a filter that will be used by . Only one filter can be set, second - /// invocation of this method throws . - /// - /// The filter. - /// Options builder for method chaining. - public DestructuringOptionsBuilder WithFilter(IExceptionPropertyFilter filter) - { - if (this.Filter is not null) - { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, Resources.FilterAlreadySet, nameof(CompositeExceptionPropertyFilter))); - } - - this.Filter = filter; - return this; - } - - /// - /// Sets a filter that will be used by . The filter ignores - /// and Exception.TargetSite properties. Only one filter can - /// be set, second invocation of this method throws . - /// - /// Options builder for method chaining. - public DestructuringOptionsBuilder WithIgnoreStackTraceAndTargetSiteExceptionFilter() => - this.WithFilter(IgnoreStackTraceAndTargetSiteExceptionFilter); - - /// - /// Sets a property name that will be used by . - /// - /// The name of root property. - /// Options builder for method chaining. - /// Name cannot be null or empty. - public DestructuringOptionsBuilder WithRootName(string rootName) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(rootName); -#else - if (rootName is null) - { - throw new ArgumentNullException(nameof(rootName)); - } -#endif - - if (rootName.Length == 0) - { - throw new ArgumentException(Resources.CannotBeEmpty, nameof(rootName)); - } - - this.RootName = rootName; - return this; - } - - /// - /// Sets a maximum destructuring depth that will reach during destructuring of - /// unknown exception type. - /// - /// Maximum depth, must be positive. - /// Options builder for method chaining. - /// Given depth must be positive. - public DestructuringOptionsBuilder WithDestructuringDepth(int destructuringDepth) - { - if (destructuringDepth <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(destructuringDepth), - destructuringDepth, - Resources.DestructuringDepthMustBeMoreThanZero); - } - - this.DestructuringDepth = destructuringDepth; - return this; - } - - /// - /// Disable reflection based destructurer. - /// You may want to disable this destructurer if you need full control - /// over the process of destructuring and want to provide all the destructurers yourself. - /// - /// Options builder for method chaining. - public DestructuringOptionsBuilder WithoutReflectionBasedDestructurer() - { - this.DisableReflectionBasedDestructurer = true; - return this; - } -} +namespace Serilog.Exceptions.Core; + +using System; +using System.Collections.Generic; +using System.Globalization; +using Serilog.Events; +using Serilog.Exceptions.Destructurers; +using Serilog.Exceptions.Filters; + +/// +/// Accumulates destructuring options to be used by the . +/// +public class DestructuringOptionsBuilder : IDestructuringOptions +{ + /// + /// Default set of destructurers. Destructurers cover all of the exceptions from standard library. + /// + public static readonly IExceptionDestructurer[] DefaultDestructurers = + { + new ExceptionDestructurer(), + new ArgumentExceptionDestructurer(), + new ArgumentOutOfRangeExceptionDestructurer(), + new AggregateExceptionDestructurer(), + new RegexMatchTimeoutExceptionDestructurer(), + new ReflectionTypeLoadExceptionDestructurer(), + new OperationCanceledExceptionDestructurer(), + new TaskCanceledExceptionDestructurer(), + new SocketExceptionDestructurer(), + }; + + /// + /// Filter that ignores and Exception.TargetSite properties. + /// Usually, they can be safely ignored, because Serilog attaches them tog already. + /// + public static readonly IExceptionPropertyFilter IgnoreStackTraceAndTargetSiteExceptionFilter = + +#if NET461 || NET472 + new IgnorePropertyByNameExceptionFilter( + nameof(Exception.StackTrace), + nameof(Exception.TargetSite)); +#else + new IgnorePropertyByNameExceptionFilter( + nameof(Exception.StackTrace)); +#endif + + private readonly List destructurers = new(); + + /// + /// Gets the name of the property which value will be filled with destructured exception. + /// + public string RootName { get; private set; } = "ExceptionDetail"; + + /// + /// Gets the maximum depth of destructuring to which reflection destructurer will proceed. + /// + public int DestructuringDepth { get; private set; } = 10; + + /// + /// Gets a value indicating whether to disable the reflection based destructurer. + /// + public bool DisableReflectionBasedDestructurer { get; private set; } + + /// + /// Gets a collection of destructurers that will be used to handle exception. + /// + public IEnumerable Destructurers => this.destructurers; + + /// + /// Gets a global filter, that will be applied to every destructured property just before it is added to the result. + /// + public IExceptionPropertyFilter? Filter { get; private set; } + + /// + /// Accumulates destructurers to be used by . + /// + /// Collection of destructurers. + /// Options builder for method chaining. + public DestructuringOptionsBuilder WithDestructurers(IEnumerable destructurers) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(destructurers); +#else + if (destructurers is null) + { + throw new ArgumentNullException(nameof(destructurers)); + } +#endif + + this.destructurers.AddRange(destructurers); + return this; + } + + /// + /// Adds destructurers for a known set of exceptions from standard library. + /// + /// Options builder for method chaining. + public DestructuringOptionsBuilder WithDefaultDestructurers() => + this.WithDestructurers(DefaultDestructurers); + + /// + /// Sets a filter that will be used by . Only one filter can be set, second + /// invocation of this method throws . + /// + /// The filter. + /// Options builder for method chaining. + public DestructuringOptionsBuilder WithFilter(IExceptionPropertyFilter filter) + { + if (this.Filter is not null) + { + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, Resources.FilterAlreadySet, nameof(CompositeExceptionPropertyFilter))); + } + + this.Filter = filter; + return this; + } + + /// + /// Sets a filter that will be used by . The filter ignores + /// and Exception.TargetSite properties. Only one filter can + /// be set, second invocation of this method throws . + /// + /// Options builder for method chaining. + public DestructuringOptionsBuilder WithIgnoreStackTraceAndTargetSiteExceptionFilter() => + this.WithFilter(IgnoreStackTraceAndTargetSiteExceptionFilter); + + /// + /// Sets a property name that will be used by . + /// + /// The name of root property. + /// Options builder for method chaining. + /// Name cannot be null or empty. + public DestructuringOptionsBuilder WithRootName(string rootName) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(rootName); +#else + if (rootName is null) + { + throw new ArgumentNullException(nameof(rootName)); + } +#endif + + if (rootName.Length == 0) + { + throw new ArgumentException(Resources.CannotBeEmpty, nameof(rootName)); + } + + this.RootName = rootName; + return this; + } + + /// + /// Sets a maximum destructuring depth that will reach during destructuring of + /// unknown exception type. + /// + /// Maximum depth, must be positive. + /// Options builder for method chaining. + /// Given depth must be positive. + public DestructuringOptionsBuilder WithDestructuringDepth(int destructuringDepth) + { + if (destructuringDepth <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(destructuringDepth), + destructuringDepth, + Resources.DestructuringDepthMustBeMoreThanZero); + } + + this.DestructuringDepth = destructuringDepth; + return this; + } + + /// + /// Disable reflection based destructurer. + /// You may want to disable this destructurer if you need full control + /// over the process of destructuring and want to provide all the destructurers yourself. + /// + /// Options builder for method chaining. + public DestructuringOptionsBuilder WithoutReflectionBasedDestructurer() + { + this.DisableReflectionBasedDestructurer = true; + return this; + } +} diff --git a/Source/Serilog.Exceptions/Core/ExceptionEnricher.cs b/Source/Serilog.Exceptions/Core/ExceptionEnricher.cs index 1452f3af..41b69ae6 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionEnricher.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionEnricher.cs @@ -1,106 +1,106 @@ -namespace Serilog.Exceptions.Core; - -using System; -using System.Collections.Generic; -using Serilog.Core; -using Serilog.Events; -using Serilog.Exceptions.Destructurers; - -/// -/// Enrich a with details about an if present. -/// -public sealed class ExceptionEnricher : ILogEventEnricher -{ - private readonly IExceptionDestructurer reflectionBasedDestructurer; - private readonly Dictionary destructurers; - private readonly IDestructuringOptions destructuringOptions; - - /// - /// Initializes a new instance of the class. - /// - public ExceptionEnricher() - : this(new DestructuringOptionsBuilder().WithDefaultDestructurers()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The destructuring options, cannot be null. - public ExceptionEnricher(IDestructuringOptions destructuringOptions) - { - this.destructuringOptions = destructuringOptions ?? throw new ArgumentNullException(nameof(destructuringOptions)); - this.reflectionBasedDestructurer = new ReflectionBasedDestructurer(destructuringOptions.DestructuringDepth); - - this.destructurers = new Dictionary(); - foreach (var destructurer in this.destructuringOptions.Destructurers) - { - foreach (var targetType in destructurer.TargetTypes) - { - this.destructurers.Add(targetType, destructurer); - } - } - } - - /// - /// Enriches with a destructured exception's properties. If the exception is not - /// present, no action is taken. - /// - /// Log event that will be enriched. - /// The property factory. - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(logEvent); - ArgumentNullException.ThrowIfNull(propertyFactory); -#else - if (logEvent is null) - { - throw new ArgumentNullException(nameof(logEvent)); - } - - if (propertyFactory is null) - { - throw new ArgumentNullException(nameof(propertyFactory)); - } -#endif - - if (logEvent.Exception is not null) - { - var dataDictionary = this.DestructureException(logEvent.Exception); - - if (dataDictionary is not null) - { - logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( - this.destructuringOptions.RootName, - dataDictionary, - true)); - } - } - } - - private IReadOnlyDictionary? DestructureException(Exception exception) - { - var exceptionType = exception.GetType(); - - if (this.destructurers.ContainsKey(exceptionType)) - { - var data = new ExceptionPropertiesBag(exception, this.destructuringOptions.Filter); - - var destructurer = this.destructurers[exceptionType]; - destructurer.Destructure(exception, data, this.DestructureException); - - return data.GetResultDictionary(); - } - else if (!this.destructuringOptions.DisableReflectionBasedDestructurer) - { - var data = new ExceptionPropertiesBag(exception, this.destructuringOptions.Filter); - - this.reflectionBasedDestructurer.Destructure(exception, data, this.DestructureException); - - return data.GetResultDictionary(); - } - - return null; - } -} +namespace Serilog.Exceptions.Core; + +using System; +using System.Collections.Generic; +using Serilog.Core; +using Serilog.Events; +using Serilog.Exceptions.Destructurers; + +/// +/// Enrich a with details about an if present. +/// +public sealed class ExceptionEnricher : ILogEventEnricher +{ + private readonly IExceptionDestructurer reflectionBasedDestructurer; + private readonly Dictionary destructurers; + private readonly IDestructuringOptions destructuringOptions; + + /// + /// Initializes a new instance of the class. + /// + public ExceptionEnricher() + : this(new DestructuringOptionsBuilder().WithDefaultDestructurers()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The destructuring options, cannot be null. + public ExceptionEnricher(IDestructuringOptions destructuringOptions) + { + this.destructuringOptions = destructuringOptions ?? throw new ArgumentNullException(nameof(destructuringOptions)); + this.reflectionBasedDestructurer = new ReflectionBasedDestructurer(destructuringOptions.DestructuringDepth); + + this.destructurers = new Dictionary(); + foreach (var destructurer in this.destructuringOptions.Destructurers) + { + foreach (var targetType in destructurer.TargetTypes) + { + this.destructurers.Add(targetType, destructurer); + } + } + } + + /// + /// Enriches with a destructured exception's properties. If the exception is not + /// present, no action is taken. + /// + /// Log event that will be enriched. + /// The property factory. + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(logEvent); + ArgumentNullException.ThrowIfNull(propertyFactory); +#else + if (logEvent is null) + { + throw new ArgumentNullException(nameof(logEvent)); + } + + if (propertyFactory is null) + { + throw new ArgumentNullException(nameof(propertyFactory)); + } +#endif + + if (logEvent.Exception is not null) + { + var dataDictionary = this.DestructureException(logEvent.Exception); + + if (dataDictionary is not null) + { + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty( + this.destructuringOptions.RootName, + dataDictionary, + true)); + } + } + } + + private IReadOnlyDictionary? DestructureException(Exception exception) + { + var exceptionType = exception.GetType(); + + if (this.destructurers.ContainsKey(exceptionType)) + { + var data = new ExceptionPropertiesBag(exception, this.destructuringOptions.Filter); + + var destructurer = this.destructurers[exceptionType]; + destructurer.Destructure(exception, data, this.DestructureException); + + return data.GetResultDictionary(); + } + else if (!this.destructuringOptions.DisableReflectionBasedDestructurer) + { + var data = new ExceptionPropertiesBag(exception, this.destructuringOptions.Filter); + + this.reflectionBasedDestructurer.Destructure(exception, data, this.DestructureException); + + return data.GetResultDictionary(); + } + + return null; + } +} diff --git a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs index adf32153..81c8610e 100644 --- a/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/ExceptionPropertiesBag.cs @@ -1,112 +1,112 @@ -namespace Serilog.Exceptions.Core; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Filters; - -/// -internal class ExceptionPropertiesBag : IExceptionPropertiesBag -{ - /// - /// In theory there should not be any properties with same names passed - /// but just as a defensive and robustness measure, we allow small amount - /// so that library can keep working in case of minor error (or unexpected - /// scenario) on the properties provider side. - /// - private const int AcceptableNumberOfSameNameProperties = 5; - private readonly Exception exception; - private readonly IExceptionPropertyFilter? filter; - private readonly Dictionary properties = new(); - - /// - /// We keep a note on whether the results were collected to be sure that after that there are no changes. This - /// is the application of fail-fast principle. - /// - private bool resultsCollected; - - /// - /// Initializes a new instance of the class. - /// - /// The exception which properties will be added to the bag. - /// Filter that should be applied to each property just before adding it to the bag. - public ExceptionPropertiesBag(Exception exception, IExceptionPropertyFilter? filter = null) - { - this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); - this.filter = filter; - } - - /// - public IReadOnlyDictionary GetResultDictionary() - { - this.resultsCollected = true; - return this.properties; - } - - /// - public void AddProperty(string key, object? value) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(key); -#else - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } -#endif - - if (this.resultsCollected) - { - throw new InvalidOperationException($"Cannot add exception property '{key}' to bag, after results were already collected"); - } - - if (this.filter is not null) - { - if (this.filter.ShouldPropertyBeFiltered(this.exception, key, value)) - { - return; - } - } - - this.AddPairToProperties(key, value); - } - - /// - public bool ContainsProperty(string key) => this.properties.ContainsKey(key); - - private static string GetReplacementKey(string key) => key + "$"; - - /// - /// We want to be as robust as possible - /// so even in case of multiple properties with the same name - /// we want to at least try carrying on and keep working. - /// - private void AddPairToProperties(string key, object? value) - { -#if NET5_0_OR_GREATER - var i = 0; - while (!this.properties.TryAdd(key, value) && i < AcceptableNumberOfSameNameProperties) - { - key = GetReplacementKey(key); - i++; - } -#else - key = this.MakeSureKeyIsUnique(key); - - this.properties.Add(key, value); -#endif - } - -#if !NET5_0_OR_GREATER - private string MakeSureKeyIsUnique(string key) - { - var i = 0; - while (this.properties.ContainsKey(key) && i < AcceptableNumberOfSameNameProperties) - { - key = GetReplacementKey(key); - i++; - } - - return key; - } -#endif -} +namespace Serilog.Exceptions.Core; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Filters; + +/// +internal class ExceptionPropertiesBag : IExceptionPropertiesBag +{ + /// + /// In theory there should not be any properties with same names passed + /// but just as a defensive and robustness measure, we allow small amount + /// so that library can keep working in case of minor error (or unexpected + /// scenario) on the properties provider side. + /// + private const int AcceptableNumberOfSameNameProperties = 5; + private readonly Exception exception; + private readonly IExceptionPropertyFilter? filter; + private readonly Dictionary properties = new(); + + /// + /// We keep a note on whether the results were collected to be sure that after that there are no changes. This + /// is the application of fail-fast principle. + /// + private bool resultsCollected; + + /// + /// Initializes a new instance of the class. + /// + /// The exception which properties will be added to the bag. + /// Filter that should be applied to each property just before adding it to the bag. + public ExceptionPropertiesBag(Exception exception, IExceptionPropertyFilter? filter = null) + { + this.exception = exception ?? throw new ArgumentNullException(nameof(exception)); + this.filter = filter; + } + + /// + public IReadOnlyDictionary GetResultDictionary() + { + this.resultsCollected = true; + return this.properties; + } + + /// + public void AddProperty(string key, object? value) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(key); +#else + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } +#endif + + if (this.resultsCollected) + { + throw new InvalidOperationException($"Cannot add exception property '{key}' to bag, after results were already collected"); + } + + if (this.filter is not null) + { + if (this.filter.ShouldPropertyBeFiltered(this.exception, key, value)) + { + return; + } + } + + this.AddPairToProperties(key, value); + } + + /// + public bool ContainsProperty(string key) => this.properties.ContainsKey(key); + + private static string GetReplacementKey(string key) => key + "$"; + + /// + /// We want to be as robust as possible + /// so even in case of multiple properties with the same name + /// we want to at least try carrying on and keep working. + /// + private void AddPairToProperties(string key, object? value) + { +#if NET5_0_OR_GREATER + var i = 0; + while (!this.properties.TryAdd(key, value) && i < AcceptableNumberOfSameNameProperties) + { + key = GetReplacementKey(key); + i++; + } +#else + key = this.MakeSureKeyIsUnique(key); + + this.properties.Add(key, value); +#endif + } + +#if !NET5_0_OR_GREATER + private string MakeSureKeyIsUnique(string key) + { + var i = 0; + while (this.properties.ContainsKey(key) && i < AcceptableNumberOfSameNameProperties) + { + key = GetReplacementKey(key); + i++; + } + + return key; + } +#endif +} diff --git a/Source/Serilog.Exceptions/Core/IDestructuringOptions.cs b/Source/Serilog.Exceptions/Core/IDestructuringOptions.cs index fbaeec93..f6c37d79 100644 --- a/Source/Serilog.Exceptions/Core/IDestructuringOptions.cs +++ b/Source/Serilog.Exceptions/Core/IDestructuringOptions.cs @@ -1,43 +1,43 @@ -namespace Serilog.Exceptions.Core; - -using System.Collections.Generic; -using Serilog.Exceptions.Destructurers; -using Serilog.Exceptions.Filters; - -/// -/// Represents all the configuration options user can specify to influence the destructuring process. -/// -public interface IDestructuringOptions -{ - /// - /// Gets the name of the key dictionary to which destructured exception will be assigned. Default is - /// "ExceptionDetail". - /// - string RootName { get; } - - /// - /// Gets the depth at which reflection based destructurer will stop recursive process of children destructuring. - /// Default is 10. - /// - int DestructuringDepth { get; } - - /// - /// Gets the collection of destructurers that will be used to destructure incoming exceptions. If none of the - /// destructurers can handle given type of exception, a generic, reflection-based destructurer will be used. - /// - IEnumerable Destructurers { get; } - - /// - /// Gets the optional filter that will evaluate and possibly reject each destructured property just before - /// they are about to be written to a result structure. If no filter is set no properties are going to be - /// rejected. Filter is applied to every property regardless of which destructurer was used. - /// - IExceptionPropertyFilter? Filter { get; } - - /// - /// Gets a value indicating whether to disable the reflection based destructurer. - /// You may want to disable this destructurer if you need full control - /// over the process of destructuring and want to provide all the destructurers yourself. - /// - bool DisableReflectionBasedDestructurer { get; } -} +namespace Serilog.Exceptions.Core; + +using System.Collections.Generic; +using Serilog.Exceptions.Destructurers; +using Serilog.Exceptions.Filters; + +/// +/// Represents all the configuration options user can specify to influence the destructuring process. +/// +public interface IDestructuringOptions +{ + /// + /// Gets the name of the key dictionary to which destructured exception will be assigned. Default is + /// "ExceptionDetail". + /// + string RootName { get; } + + /// + /// Gets the depth at which reflection based destructurer will stop recursive process of children destructuring. + /// Default is 10. + /// + int DestructuringDepth { get; } + + /// + /// Gets the collection of destructurers that will be used to destructure incoming exceptions. If none of the + /// destructurers can handle given type of exception, a generic, reflection-based destructurer will be used. + /// + IEnumerable Destructurers { get; } + + /// + /// Gets the optional filter that will evaluate and possibly reject each destructured property just before + /// they are about to be written to a result structure. If no filter is set no properties are going to be + /// rejected. Filter is applied to every property regardless of which destructurer was used. + /// + IExceptionPropertyFilter? Filter { get; } + + /// + /// Gets a value indicating whether to disable the reflection based destructurer. + /// You may want to disable this destructurer if you need full control + /// over the process of destructuring and want to provide all the destructurers yourself. + /// + bool DisableReflectionBasedDestructurer { get; } +} diff --git a/Source/Serilog.Exceptions/Core/IExceptionPropertiesBag.cs b/Source/Serilog.Exceptions/Core/IExceptionPropertiesBag.cs index 6651b710..9458567b 100644 --- a/Source/Serilog.Exceptions/Core/IExceptionPropertiesBag.cs +++ b/Source/Serilog.Exceptions/Core/IExceptionPropertiesBag.cs @@ -1,31 +1,31 @@ -namespace Serilog.Exceptions.Core; - -using System.Collections.Generic; - -/// -/// Container for all properties of single exception instance. All properties must be added before result -/// dictionary is requested. -/// -public interface IExceptionPropertiesBag -{ - /// - /// Results should be collected only once, after all the properties were added using - /// method. - /// - /// Dictionary with all the properties names and values that were added. - IReadOnlyDictionary GetResultDictionary(); - - /// - /// Adds a property to the bag. - /// - /// The key. - /// The value. - void AddProperty(string key, object? value); - - /// - /// Returns true if given key is already present in the bag. - /// - /// The key. - /// True if given key is already present. - bool ContainsProperty(string key); -} +namespace Serilog.Exceptions.Core; + +using System.Collections.Generic; + +/// +/// Container for all properties of single exception instance. All properties must be added before result +/// dictionary is requested. +/// +public interface IExceptionPropertiesBag +{ + /// + /// Results should be collected only once, after all the properties were added using + /// method. + /// + /// Dictionary with all the properties names and values that were added. + IReadOnlyDictionary GetResultDictionary(); + + /// + /// Adds a property to the bag. + /// + /// The key. + /// The value. + void AddProperty(string key, object? value); + + /// + /// Returns true if given key is already present in the bag. + /// + /// The key. + /// True if given key is already present. + bool ContainsProperty(string key); +} diff --git a/Source/Serilog.Exceptions/Destructurers/AggregateExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/AggregateExceptionDestructurer.cs index 3a196189..6909df60 100644 --- a/Source/Serilog.Exceptions/Destructurers/AggregateExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/AggregateExceptionDestructurer.cs @@ -1,31 +1,31 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using System.Linq; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class AggregateExceptionDestructurer : ExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] { typeof(AggregateException) }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var aggregateException = (AggregateException)exception; - propertiesBag.AddProperty( - nameof(AggregateException.InnerExceptions), - aggregateException.InnerExceptions.Select(destructureException).ToList()); -#pragma warning restore CA1062 // Validate arguments of public methods - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using System.Linq; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class AggregateExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] { typeof(AggregateException) }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var aggregateException = (AggregateException)exception; + propertiesBag.AddProperty( + nameof(AggregateException.InnerExceptions), + aggregateException.InnerExceptions.Select(destructureException).ToList()); +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/ArgumentExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ArgumentExceptionDestructurer.cs index 6aa6801d..febf4e19 100644 --- a/Source/Serilog.Exceptions/Destructurers/ArgumentExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ArgumentExceptionDestructurer.cs @@ -1,32 +1,32 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class ArgumentExceptionDestructurer : ExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] - { - typeof(ArgumentException), - typeof(ArgumentNullException), - }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var argumentException = (ArgumentException)exception; - propertiesBag.AddProperty(nameof(ArgumentException.ParamName), argumentException.ParamName); -#pragma warning restore CA1062 // Validate arguments of public methods - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class ArgumentExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] + { + typeof(ArgumentException), + typeof(ArgumentNullException), + }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var argumentException = (ArgumentException)exception; + propertiesBag.AddProperty(nameof(ArgumentException.ParamName), argumentException.ParamName); +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/ArgumentOutOfRangeExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ArgumentOutOfRangeExceptionDestructurer.cs index 883c364a..12b67142 100644 --- a/Source/Serilog.Exceptions/Destructurers/ArgumentOutOfRangeExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ArgumentOutOfRangeExceptionDestructurer.cs @@ -1,31 +1,31 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class ArgumentOutOfRangeExceptionDestructurer : ArgumentExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] - { - typeof(ArgumentOutOfRangeException), - }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var argumentException = (ArgumentOutOfRangeException)exception; - propertiesBag.AddProperty(nameof(ArgumentOutOfRangeException.ActualValue), argumentException.ActualValue); -#pragma warning restore CA1062 // Validate arguments of public methods - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class ArgumentOutOfRangeExceptionDestructurer : ArgumentExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] + { + typeof(ArgumentOutOfRangeException), + }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var argumentException = (ArgumentOutOfRangeException)exception; + propertiesBag.AddProperty(nameof(ArgumentOutOfRangeException.ActualValue), argumentException.ActualValue); +#pragma warning restore CA1062 // Validate arguments of public methods + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/ExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ExceptionDestructurer.cs index 07ff6081..bdee6da7 100644 --- a/Source/Serilog.Exceptions/Destructurers/ExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ExceptionDestructurer.cs @@ -1,286 +1,286 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; - -/// -/// Base class for more specific destructurers. -/// It destructures all the standard properties that every has. -/// -public class ExceptionDestructurer : IExceptionDestructurer -{ - /// -#pragma warning disable CA1819 // Properties should not return arrays - public virtual Type[] TargetTypes -#pragma warning restore CA1819 // Properties should not return arrays - { - get - { - var targetTypes = new List - { -#pragma warning disable IDE0001 // Simplify Names -#if NET461 || NET472 - typeof(Microsoft.SqlServer.Server.InvalidUdtException), -#endif -#if !NETSTANDARD1_3 - typeof(System.AccessViolationException), - typeof(System.AppDomainUnloadedException), - typeof(System.ApplicationException), - typeof(System.ArithmeticException), - typeof(System.ArrayTypeMismatchException), - typeof(System.CannotUnloadAppDomainException), -#endif - typeof(System.Collections.Generic.KeyNotFoundException), -#if !NETSTANDARD1_3 - typeof(System.ComponentModel.Design.CheckoutException), - typeof(System.ComponentModel.InvalidAsynchronousStateException), - typeof(System.ComponentModel.InvalidEnumArgumentException), -#endif -#if NET461 || NET472 - typeof(System.Configuration.SettingsPropertyIsReadOnlyException), - typeof(System.Configuration.SettingsPropertyNotFoundException), - typeof(System.Configuration.SettingsPropertyWrongTypeException), -#endif -#if !NETSTANDARD1_3 - typeof(System.ContextMarshalException), - - typeof(System.Data.ConstraintException), - typeof(System.Data.DataException), - typeof(System.Data.DeletedRowInaccessibleException), - typeof(System.Data.DuplicateNameException), - typeof(System.Data.EvaluateException), - typeof(System.Data.InRowChangingEventException), - typeof(System.Data.InvalidConstraintException), - typeof(System.Data.InvalidExpressionException), - typeof(System.Data.MissingPrimaryKeyException), - typeof(System.Data.NoNullAllowedException), -#endif -#if NET461 || NET472 - typeof(System.Data.OperationAbortedException), -#endif -#if !NETSTANDARD1_3 - typeof(System.Data.ReadOnlyException), - typeof(System.Data.RowNotInTableException), - typeof(System.Data.SqlTypes.SqlAlreadyFilledException), - typeof(System.Data.SqlTypes.SqlNotFilledException), - typeof(System.Data.StrongTypingException), - typeof(System.Data.SyntaxErrorException), - typeof(System.Data.VersionNotFoundException), -#endif -#if NET461 || NET472 - typeof(System.Diagnostics.Eventing.Reader.EventLogInvalidDataException), - typeof(System.Diagnostics.Eventing.Reader.EventLogNotFoundException), - typeof(System.Diagnostics.Eventing.Reader.EventLogProviderDisabledException), - typeof(System.Diagnostics.Eventing.Reader.EventLogReadingException), -#endif - typeof(System.Diagnostics.Tracing.EventSourceException), - typeof(System.DataMisalignedException), - typeof(System.DivideByZeroException), - typeof(System.DllNotFoundException), -#if !NETSTANDARD1_3 - typeof(System.DuplicateWaitObjectException), - typeof(System.EntryPointNotFoundException), -#endif - typeof(System.Exception), - typeof(System.FieldAccessException), - typeof(System.FormatException), - typeof(System.IndexOutOfRangeException), - typeof(System.InsufficientExecutionStackException), -#if !NETSTANDARD1_3 - typeof(System.InsufficientMemoryException), -#endif - typeof(System.InvalidCastException), - typeof(System.InvalidOperationException), - typeof(System.InvalidProgramException), - typeof(System.InvalidTimeZoneException), - typeof(System.IO.DirectoryNotFoundException), -#if !NETSTANDARD1_3 - typeof(System.IO.DriveNotFoundException), -#endif - typeof(System.IO.EndOfStreamException), -#if !NETSTANDARD1_3 - typeof(System.IO.InternalBufferOverflowException), -#endif - typeof(System.IO.InvalidDataException), - typeof(System.IO.IOException), -#if !NETSTANDARD1_3 - typeof(System.IO.IsolatedStorage.IsolatedStorageException), -#endif - typeof(System.IO.PathTooLongException), -#if NET461 || NET472 - typeof(System.Management.Instrumentation.InstanceNotFoundException), - typeof(System.Management.Instrumentation.InstrumentationBaseException), - typeof(System.Management.Instrumentation.InstrumentationException), -#endif - typeof(System.MemberAccessException), - typeof(System.MethodAccessException), -#if !NETSTANDARD1_3 - typeof(System.MulticastNotSupportedException), -#endif - typeof(System.Net.CookieException), -#if !NETSTANDARD1_3 - typeof(System.Net.NetworkInformation.PingException), - typeof(System.Net.ProtocolViolationException), -#endif - typeof(System.NotImplementedException), - typeof(System.NotSupportedException), - typeof(System.NullReferenceException), - typeof(System.OutOfMemoryException), - typeof(System.OverflowException), - typeof(System.PlatformNotSupportedException), - typeof(System.RankException), - typeof(System.Reflection.AmbiguousMatchException), -#if !NETSTANDARD1_3 - typeof(System.Reflection.CustomAttributeFormatException), - typeof(System.Reflection.InvalidFilterCriteriaException), - typeof(System.Reflection.TargetException), -#endif - typeof(System.Reflection.TargetInvocationException), - typeof(System.Reflection.TargetParameterCountException), - typeof(System.Resources.MissingManifestResourceException), -#if NET5_0_OR_GREATER || NETSTANDARD2_1 - typeof(System.Runtime.AmbiguousImplementationException), -#endif - typeof(System.Runtime.InteropServices.COMException), - typeof(System.Runtime.InteropServices.InvalidComObjectException), - typeof(System.Runtime.InteropServices.InvalidOleVariantTypeException), - typeof(System.Runtime.InteropServices.MarshalDirectiveException), - typeof(System.Runtime.InteropServices.SafeArrayRankMismatchException), - typeof(System.Runtime.InteropServices.SafeArrayTypeMismatchException), - typeof(System.Runtime.InteropServices.SEHException), -#if NET461 || NET472 - typeof(System.Runtime.Remoting.RemotingException), - typeof(System.Runtime.Remoting.RemotingTimeoutException), - typeof(System.Runtime.Remoting.ServerException), -#endif -#if !NETSTANDARD1_3 - typeof(System.Runtime.Serialization.SerializationException), - typeof(System.Security.Authentication.AuthenticationException), - typeof(System.Security.Authentication.InvalidCredentialException), -#endif - typeof(System.Security.Cryptography.CryptographicException), -#if !NETSTANDARD1_3 - typeof(System.Security.Cryptography.CryptographicUnexpectedOperationException), -#endif -#if NET461 || NET472 - typeof(System.Security.Policy.PolicyException), -#endif - typeof(System.Security.VerificationException), -#if NET461 || NET472 - typeof(System.Security.XmlSyntaxException), -#endif -#if !NETSTANDARD1_3 - typeof(System.StackOverflowException), - typeof(System.SystemException), -#endif - typeof(System.Threading.BarrierPostPhaseException), - typeof(System.Threading.LockRecursionException), - typeof(System.Threading.SemaphoreFullException), - typeof(System.Threading.SynchronizationLockException), - typeof(System.Threading.Tasks.TaskSchedulerException), -#if !NETSTANDARD1_3 - typeof(System.Threading.ThreadInterruptedException), - typeof(System.Threading.ThreadStartException), - typeof(System.Threading.ThreadStateException), -#endif - typeof(System.Threading.WaitHandleCannotBeOpenedException), - typeof(System.TimeoutException), -#if !NETSTANDARD1_3 - typeof(System.TimeZoneNotFoundException), -#endif - typeof(System.TypeAccessException), -#if !NETSTANDARD1_3 - typeof(System.TypeUnloadedException), -#endif - typeof(System.UnauthorizedAccessException), - typeof(System.UriFormatException), - }; -#pragma warning restore IDE0001 // Simplify Names - - return targetTypes.ToArray(); - } - } - - /// - public virtual void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(exception); - ArgumentNullException.ThrowIfNull(propertiesBag); - ArgumentNullException.ThrowIfNull(destructureException); -#else - if (exception is null) - { - throw new ArgumentNullException(nameof(exception)); - } - - if (propertiesBag is null) - { - throw new ArgumentNullException(nameof(propertiesBag)); - } - - if (destructureException is null) - { - throw new ArgumentNullException(nameof(destructureException)); - } -#endif - - propertiesBag.AddProperty("Type", exception.GetType().FullName); - - DestructureCommonExceptionProperties( - exception, - propertiesBag, - destructureException, - data => data.ToStringObjectDictionary()); - } - - /// - /// Destructures public properties of . - /// Omits less frequently used ones if they are null. - /// - /// The exception that will be destructured. - /// The bag when destructured properties will be put. - /// Function that can be used to destructure inner exceptions if there are any. - /// Injected function for destructuring . - internal static void DestructureCommonExceptionProperties( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> innerDestructure, - Func destructureDataProperty) - { - if (exception.Data.Count != 0) - { - propertiesBag.AddProperty(nameof(Exception.Data), destructureDataProperty(exception.Data)); - } - - if (!string.IsNullOrEmpty(exception.HelpLink)) - { - propertiesBag.AddProperty(nameof(Exception.HelpLink), exception.HelpLink); - } - - if (exception.HResult != 0) - { - propertiesBag.AddProperty(nameof(Exception.HResult), exception.HResult); - } - - propertiesBag.AddProperty(nameof(Exception.Message), exception.Message); - propertiesBag.AddProperty(nameof(Exception.Source), exception.Source); - propertiesBag.AddProperty(nameof(Exception.StackTrace), exception.StackTrace); - -#if !NETSTANDARD1_3 - if (exception.TargetSite is not null) - { - propertiesBag.AddProperty(nameof(Exception.TargetSite), exception.TargetSite.ToString()); - } -#endif - - if (exception.InnerException is not null) - { - propertiesBag.AddProperty(nameof(Exception.InnerException), innerDestructure(exception.InnerException)); - } - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; + +/// +/// Base class for more specific destructurers. +/// It destructures all the standard properties that every has. +/// +public class ExceptionDestructurer : IExceptionDestructurer +{ + /// +#pragma warning disable CA1819 // Properties should not return arrays + public virtual Type[] TargetTypes +#pragma warning restore CA1819 // Properties should not return arrays + { + get + { + var targetTypes = new List + { +#pragma warning disable IDE0001 // Simplify Names +#if NET461 || NET472 + typeof(Microsoft.SqlServer.Server.InvalidUdtException), +#endif +#if !NETSTANDARD1_3 + typeof(System.AccessViolationException), + typeof(System.AppDomainUnloadedException), + typeof(System.ApplicationException), + typeof(System.ArithmeticException), + typeof(System.ArrayTypeMismatchException), + typeof(System.CannotUnloadAppDomainException), +#endif + typeof(System.Collections.Generic.KeyNotFoundException), +#if !NETSTANDARD1_3 + typeof(System.ComponentModel.Design.CheckoutException), + typeof(System.ComponentModel.InvalidAsynchronousStateException), + typeof(System.ComponentModel.InvalidEnumArgumentException), +#endif +#if NET461 || NET472 + typeof(System.Configuration.SettingsPropertyIsReadOnlyException), + typeof(System.Configuration.SettingsPropertyNotFoundException), + typeof(System.Configuration.SettingsPropertyWrongTypeException), +#endif +#if !NETSTANDARD1_3 + typeof(System.ContextMarshalException), + + typeof(System.Data.ConstraintException), + typeof(System.Data.DataException), + typeof(System.Data.DeletedRowInaccessibleException), + typeof(System.Data.DuplicateNameException), + typeof(System.Data.EvaluateException), + typeof(System.Data.InRowChangingEventException), + typeof(System.Data.InvalidConstraintException), + typeof(System.Data.InvalidExpressionException), + typeof(System.Data.MissingPrimaryKeyException), + typeof(System.Data.NoNullAllowedException), +#endif +#if NET461 || NET472 + typeof(System.Data.OperationAbortedException), +#endif +#if !NETSTANDARD1_3 + typeof(System.Data.ReadOnlyException), + typeof(System.Data.RowNotInTableException), + typeof(System.Data.SqlTypes.SqlAlreadyFilledException), + typeof(System.Data.SqlTypes.SqlNotFilledException), + typeof(System.Data.StrongTypingException), + typeof(System.Data.SyntaxErrorException), + typeof(System.Data.VersionNotFoundException), +#endif +#if NET461 || NET472 + typeof(System.Diagnostics.Eventing.Reader.EventLogInvalidDataException), + typeof(System.Diagnostics.Eventing.Reader.EventLogNotFoundException), + typeof(System.Diagnostics.Eventing.Reader.EventLogProviderDisabledException), + typeof(System.Diagnostics.Eventing.Reader.EventLogReadingException), +#endif + typeof(System.Diagnostics.Tracing.EventSourceException), + typeof(System.DataMisalignedException), + typeof(System.DivideByZeroException), + typeof(System.DllNotFoundException), +#if !NETSTANDARD1_3 + typeof(System.DuplicateWaitObjectException), + typeof(System.EntryPointNotFoundException), +#endif + typeof(System.Exception), + typeof(System.FieldAccessException), + typeof(System.FormatException), + typeof(System.IndexOutOfRangeException), + typeof(System.InsufficientExecutionStackException), +#if !NETSTANDARD1_3 + typeof(System.InsufficientMemoryException), +#endif + typeof(System.InvalidCastException), + typeof(System.InvalidOperationException), + typeof(System.InvalidProgramException), + typeof(System.InvalidTimeZoneException), + typeof(System.IO.DirectoryNotFoundException), +#if !NETSTANDARD1_3 + typeof(System.IO.DriveNotFoundException), +#endif + typeof(System.IO.EndOfStreamException), +#if !NETSTANDARD1_3 + typeof(System.IO.InternalBufferOverflowException), +#endif + typeof(System.IO.InvalidDataException), + typeof(System.IO.IOException), +#if !NETSTANDARD1_3 + typeof(System.IO.IsolatedStorage.IsolatedStorageException), +#endif + typeof(System.IO.PathTooLongException), +#if NET461 || NET472 + typeof(System.Management.Instrumentation.InstanceNotFoundException), + typeof(System.Management.Instrumentation.InstrumentationBaseException), + typeof(System.Management.Instrumentation.InstrumentationException), +#endif + typeof(System.MemberAccessException), + typeof(System.MethodAccessException), +#if !NETSTANDARD1_3 + typeof(System.MulticastNotSupportedException), +#endif + typeof(System.Net.CookieException), +#if !NETSTANDARD1_3 + typeof(System.Net.NetworkInformation.PingException), + typeof(System.Net.ProtocolViolationException), +#endif + typeof(System.NotImplementedException), + typeof(System.NotSupportedException), + typeof(System.NullReferenceException), + typeof(System.OutOfMemoryException), + typeof(System.OverflowException), + typeof(System.PlatformNotSupportedException), + typeof(System.RankException), + typeof(System.Reflection.AmbiguousMatchException), +#if !NETSTANDARD1_3 + typeof(System.Reflection.CustomAttributeFormatException), + typeof(System.Reflection.InvalidFilterCriteriaException), + typeof(System.Reflection.TargetException), +#endif + typeof(System.Reflection.TargetInvocationException), + typeof(System.Reflection.TargetParameterCountException), + typeof(System.Resources.MissingManifestResourceException), +#if NET5_0_OR_GREATER || NETSTANDARD2_1 + typeof(System.Runtime.AmbiguousImplementationException), +#endif + typeof(System.Runtime.InteropServices.COMException), + typeof(System.Runtime.InteropServices.InvalidComObjectException), + typeof(System.Runtime.InteropServices.InvalidOleVariantTypeException), + typeof(System.Runtime.InteropServices.MarshalDirectiveException), + typeof(System.Runtime.InteropServices.SafeArrayRankMismatchException), + typeof(System.Runtime.InteropServices.SafeArrayTypeMismatchException), + typeof(System.Runtime.InteropServices.SEHException), +#if NET461 || NET472 + typeof(System.Runtime.Remoting.RemotingException), + typeof(System.Runtime.Remoting.RemotingTimeoutException), + typeof(System.Runtime.Remoting.ServerException), +#endif +#if !NETSTANDARD1_3 + typeof(System.Runtime.Serialization.SerializationException), + typeof(System.Security.Authentication.AuthenticationException), + typeof(System.Security.Authentication.InvalidCredentialException), +#endif + typeof(System.Security.Cryptography.CryptographicException), +#if !NETSTANDARD1_3 + typeof(System.Security.Cryptography.CryptographicUnexpectedOperationException), +#endif +#if NET461 || NET472 + typeof(System.Security.Policy.PolicyException), +#endif + typeof(System.Security.VerificationException), +#if NET461 || NET472 + typeof(System.Security.XmlSyntaxException), +#endif +#if !NETSTANDARD1_3 + typeof(System.StackOverflowException), + typeof(System.SystemException), +#endif + typeof(System.Threading.BarrierPostPhaseException), + typeof(System.Threading.LockRecursionException), + typeof(System.Threading.SemaphoreFullException), + typeof(System.Threading.SynchronizationLockException), + typeof(System.Threading.Tasks.TaskSchedulerException), +#if !NETSTANDARD1_3 + typeof(System.Threading.ThreadInterruptedException), + typeof(System.Threading.ThreadStartException), + typeof(System.Threading.ThreadStateException), +#endif + typeof(System.Threading.WaitHandleCannotBeOpenedException), + typeof(System.TimeoutException), +#if !NETSTANDARD1_3 + typeof(System.TimeZoneNotFoundException), +#endif + typeof(System.TypeAccessException), +#if !NETSTANDARD1_3 + typeof(System.TypeUnloadedException), +#endif + typeof(System.UnauthorizedAccessException), + typeof(System.UriFormatException), + }; +#pragma warning restore IDE0001 // Simplify Names + + return targetTypes.ToArray(); + } + } + + /// + public virtual void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(exception); + ArgumentNullException.ThrowIfNull(propertiesBag); + ArgumentNullException.ThrowIfNull(destructureException); +#else + if (exception is null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (propertiesBag is null) + { + throw new ArgumentNullException(nameof(propertiesBag)); + } + + if (destructureException is null) + { + throw new ArgumentNullException(nameof(destructureException)); + } +#endif + + propertiesBag.AddProperty("Type", exception.GetType().FullName); + + DestructureCommonExceptionProperties( + exception, + propertiesBag, + destructureException, + data => data.ToStringObjectDictionary()); + } + + /// + /// Destructures public properties of . + /// Omits less frequently used ones if they are null. + /// + /// The exception that will be destructured. + /// The bag when destructured properties will be put. + /// Function that can be used to destructure inner exceptions if there are any. + /// Injected function for destructuring . + internal static void DestructureCommonExceptionProperties( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> innerDestructure, + Func destructureDataProperty) + { + if (exception.Data.Count != 0) + { + propertiesBag.AddProperty(nameof(Exception.Data), destructureDataProperty(exception.Data)); + } + + if (!string.IsNullOrEmpty(exception.HelpLink)) + { + propertiesBag.AddProperty(nameof(Exception.HelpLink), exception.HelpLink); + } + + if (exception.HResult != 0) + { + propertiesBag.AddProperty(nameof(Exception.HResult), exception.HResult); + } + + propertiesBag.AddProperty(nameof(Exception.Message), exception.Message); + propertiesBag.AddProperty(nameof(Exception.Source), exception.Source); + propertiesBag.AddProperty(nameof(Exception.StackTrace), exception.StackTrace); + +#if !NETSTANDARD1_3 + if (exception.TargetSite is not null) + { + propertiesBag.AddProperty(nameof(Exception.TargetSite), exception.TargetSite.ToString()); + } +#endif + + if (exception.InnerException is not null) + { + propertiesBag.AddProperty(nameof(Exception.InnerException), innerDestructure(exception.InnerException)); + } + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/IExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/IExceptionDestructurer.cs index a1a6d845..32ad7d44 100644 --- a/Source/Serilog.Exceptions/Destructurers/IExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/IExceptionDestructurer.cs @@ -1,32 +1,32 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using Serilog.Exceptions.Core; - -/// -/// Interface that all exception destructurers that want to be registered must implement. Exception destructurer -/// must explicitly declare which types it can destructure using property. -/// -public interface IExceptionDestructurer -{ - /// - /// Gets a collection of exception types that the destructurer can handle. - /// -#pragma warning disable CA1819 // Properties should not return arrays - Type[] TargetTypes { get; } -#pragma warning restore CA1819 // Properties should not return arrays - - /// - /// Destructures given . It's properties are to be put in - /// . - /// - /// The exception that will be destructured. - /// The bag when destructured properties will be put. - /// Function that can be used to destructure inner exceptions if there are - /// any. - void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException); -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using Serilog.Exceptions.Core; + +/// +/// Interface that all exception destructurers that want to be registered must implement. Exception destructurer +/// must explicitly declare which types it can destructure using property. +/// +public interface IExceptionDestructurer +{ + /// + /// Gets a collection of exception types that the destructurer can handle. + /// +#pragma warning disable CA1819 // Properties should not return arrays + Type[] TargetTypes { get; } +#pragma warning restore CA1819 // Properties should not return arrays + + /// + /// Destructures given . It's properties are to be put in + /// . + /// + /// The exception that will be destructured. + /// The bag when destructured properties will be put. + /// Function that can be used to destructure inner exceptions if there are + /// any. + void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException); +} diff --git a/Source/Serilog.Exceptions/Destructurers/OperationCanceledExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/OperationCanceledExceptionDestructurer.cs index 9e8a350c..b83ec94c 100644 --- a/Source/Serilog.Exceptions/Destructurers/OperationCanceledExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/OperationCanceledExceptionDestructurer.cs @@ -1,47 +1,47 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using System.Threading; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class OperationCanceledExceptionDestructurer : ExceptionDestructurer -{ - private const string CancellationTokenTrue = "CancellationRequested: true"; - private const string CancellationTokenFalse = "CancellationRequested: false"; - - private static readonly Type[] TargetExceptionTypes = - { - typeof(OperationCanceledException), - }; - - /// - public override Type[] TargetTypes => TargetExceptionTypes; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var operationCancelledException = (OperationCanceledException)exception; - propertiesBag.AddProperty( - nameof(OperationCanceledException.CancellationToken), - DestructureCancellationToken(operationCancelledException.CancellationToken)); -#pragma warning restore CA1062 // Validate arguments of public methods - } - - /// - /// Destructures the cancellation token. - /// - /// The cancellation token. - /// The destructured cancellation token. - internal static object DestructureCancellationToken(in CancellationToken cancellationToken) => - cancellationToken.IsCancellationRequested ? CancellationTokenTrue : CancellationTokenFalse; -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using System.Threading; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class OperationCanceledExceptionDestructurer : ExceptionDestructurer +{ + private const string CancellationTokenTrue = "CancellationRequested: true"; + private const string CancellationTokenFalse = "CancellationRequested: false"; + + private static readonly Type[] TargetExceptionTypes = + { + typeof(OperationCanceledException), + }; + + /// + public override Type[] TargetTypes => TargetExceptionTypes; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var operationCancelledException = (OperationCanceledException)exception; + propertiesBag.AddProperty( + nameof(OperationCanceledException.CancellationToken), + DestructureCancellationToken(operationCancelledException.CancellationToken)); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Destructures the cancellation token. + /// + /// The cancellation token. + /// The destructured cancellation token. + internal static object DestructureCancellationToken(in CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? CancellationTokenTrue : CancellationTokenFalse; +} diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs index 43cb65da..3199edd0 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionBasedDestructurer.cs @@ -1,378 +1,378 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Reflection; - -/// -/// Destructures exceptions by gathering all public non-indexer properties using reflection and then dynamically -/// retrieving their values. This class can handle every exception including those with circular references and -/// throwing properties. Additionally, a "Type" property is added to let the user know exact type of destructured -/// exception. -/// -public class ReflectionBasedDestructurer : IExceptionDestructurer -{ - private const string IdLabel = "$id"; - private const string RefLabel = "$ref"; - private const string CyclicReferenceMessage = "Cyclic reference"; - private const string IQueryableDestructureSkippedMessage = "IQueryable skipped"; - private readonly int destructuringDepth; - private readonly ReflectionInfoExtractor reflectionInfoExtractor = new(); - - /// - /// Initializes a new instance of the class. - /// - /// Maximum depth to which the destructurer will go when destructuring an - /// exception object graph. - public ReflectionBasedDestructurer(int destructuringDepth) - { - if (destructuringDepth <= 0) - { - throw new ArgumentOutOfRangeException( - nameof(destructuringDepth), - destructuringDepth, - Resources.DestructuringDepthMustBeMoreThanZero); - } - - this.destructuringDepth = destructuringDepth; - } - - /// -#pragma warning disable CA1819 // Properties should not return arrays - public Type[] TargetTypes => new[] { typeof(Exception) }; -#pragma warning restore CA1819 // Properties should not return arrays - - /// - public void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(exception); - ArgumentNullException.ThrowIfNull(propertiesBag); - ArgumentNullException.ThrowIfNull(destructureException); -#else - if (exception is null) - { - throw new ArgumentNullException(nameof(exception)); - } - - if (propertiesBag is null) - { - throw new ArgumentNullException(nameof(propertiesBag)); - } - - if (destructureException is null) - { - throw new ArgumentNullException(nameof(destructureException)); - } -#endif - - var nextCyclicRefId = 1; - var destructuredObjects = new Dictionary>(); - - ExceptionDestructurer.DestructureCommonExceptionProperties( - exception, - propertiesBag, - destructureException, - data => this.DestructureValueDictionary(data, 1, destructuredObjects, ref nextCyclicRefId)); - - var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(exception.GetType()); - - this.AppendProperties( - exception, - reflectionInfo.PropertiesExceptBaseOnes, - propertiesBag.AddProperty, - destructuredObjects, - level: 0, - nextCyclicRefId: ref nextCyclicRefId); - - AppendTypeIfPossible(propertiesBag, exception.GetType()); - } - - private static string? GetOrGenerateRefId(ref int nextCyclicRefId, IDictionary destructuredObject) - { - string? refId; - if (destructuredObject.ContainsKey(IdLabel)) - { - refId = (string?)destructuredObject[IdLabel]; - } - else - { - var id = nextCyclicRefId; - nextCyclicRefId++; - refId = id.ToString(CultureInfo.InvariantCulture); - destructuredObject[IdLabel] = refId; - } - - return refId; - } - - private static object DestructureUri(Uri value) => value.ToString(); - - private static void AppendTypeIfPossible(IExceptionPropertiesBag propertiesBag, Type valueType) - { - if (propertiesBag.ContainsProperty("Type")) - { - if (!propertiesBag.ContainsProperty("$Type")) - { - propertiesBag.AddProperty("$Type", valueType.FullName); - } - else - { - // If both "Type" and "$Type" are present - // we just give up appending exception type - } - } - else - { - propertiesBag.AddProperty("Type", valueType.FullName); - } - } - - private void AppendProperties( - object value, - ReflectionPropertyInfo[] reflectionPropertyInfos, - Action addPropertyAction, - IDictionary> destructuredObjects, - int level, - ref int nextCyclicRefId) - { - foreach (var property in reflectionPropertyInfos) - { - try - { - var valueToBeDestructured = property.Getter(value); - var localNextCyclicRefId = nextCyclicRefId; - var destructuredValue = this.DestructureValue( - valueToBeDestructured, - level + 1, - destructuredObjects, - ref localNextCyclicRefId); - nextCyclicRefId = localNextCyclicRefId; - addPropertyAction(property.Name, destructuredValue); - } - catch (TargetInvocationException targetInvocationException) - { - var innerException = targetInvocationException.InnerException; - if (innerException is not null) - { - addPropertyAction(property.Name, $"threw {innerException.GetType().FullName}: {innerException.Message}"); - } - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception exception) - { - addPropertyAction(property.Name, $"threw {exception.GetType().FullName}: {exception.Message}"); - } -#pragma warning restore CA1031 // Do not catch general exception types - } - } - - private object? DestructureValue( - object? value, - int level, - IDictionary> destructuredObjects, - ref int nextCyclicRefId) - { - if (value is null) - { - return null; - } - - var valueType = value.GetType(); - var valueTypeInfo = valueType.GetTypeInfo(); - - if (value is MemberInfo) - { - return value; - } - - if (valueType.GetTypeCode() != TypeCode.Object || valueTypeInfo.IsValueType) - { - return value; - } - - if (level > this.destructuringDepth) - { - return value; - } - - if (value is IDictionary dictionary) - { - return this.DestructureValueDictionary(dictionary, level, destructuredObjects, ref nextCyclicRefId); - } - - if (value is IQueryable queryable) - { - return IQueryableDestructureSkippedMessage; - } - else if (value is IEnumerable enumerable) - { - return this.DestructureValueEnumerable(enumerable, level, destructuredObjects, ref nextCyclicRefId); - } - - if (value is Uri uri) - { - return DestructureUri(uri); - } - - if (value is CancellationToken ct) - { - return OperationCanceledExceptionDestructurer.DestructureCancellationToken(ct); - } - - if (value is Task task) - { - return this.DestructureTask(task, level, destructuredObjects, ref nextCyclicRefId); - } - - return this.DestructureObject(value, valueType, level, destructuredObjects, ref nextCyclicRefId); - } - - private object DestructureValueEnumerable( - IEnumerable value, - int level, - IDictionary> destructuredObjects, - ref int nextCyclicRefId) - { - if (destructuredObjects.ContainsKey(value)) - { - return new Dictionary - { - { RefLabel, CyclicReferenceMessage }, - }; - } - - destructuredObjects.Add(value, new Dictionary()); - - var resultList = new List(); - foreach (var o in value.Cast()) - { - resultList.Add(this.DestructureValue(o, level + 1, destructuredObjects, ref nextCyclicRefId)); - } - - return resultList; - } - - private object DestructureValueDictionary( - IDictionary value, - int level, - IDictionary> destructuredObjects, - ref int nextCyclicRefId) - { - if (destructuredObjects.ContainsKey(value)) - { - var destructuredObject = destructuredObjects[value]; - var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredObject); - - return new Dictionary - { - { RefLabel, refId }, - }; - } - - var destructuredDictionary = value.ToStringObjectDictionary(); - destructuredObjects.Add(value, destructuredDictionary); - - foreach (var kvp in destructuredDictionary.ToDictionary(k => k.Key, v => v.Value)) - { - destructuredDictionary[kvp.Key] = - this.DestructureValue(kvp.Value, level + 1, destructuredObjects, ref nextCyclicRefId); - } - - return destructuredDictionary; - } - - private IDictionary DestructureObject( - object value, - Type valueType, - int level, - IDictionary> destructuredObjects, - ref int nextCyclicRefId) - { - if (destructuredObjects.ContainsKey(value)) - { - var destructuredObject = destructuredObjects[value]; - var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredObject); - - return new Dictionary() - { - { RefLabel, refId }, - }; - } - - var values = new Dictionary(); - destructuredObjects.Add(value, values); - - var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(valueType); - - foreach (var property in reflectionInfo.Properties) - { - try - { - var valueToBeDestructured = property.Getter(value); - var destructuredValue = this.DestructureValue( - valueToBeDestructured, - level + 1, - destructuredObjects, - ref nextCyclicRefId); - values.Add(property.Name, destructuredValue); - } - catch (TargetInvocationException targetInvocationException) - { - var innerException = targetInvocationException.InnerException; - if (innerException is not null) - { - values.Add(property.Name, $"threw {innerException.GetType().FullName}: {innerException.Message}"); - } - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception exception) - { - values.Add(property.Name, $"threw {exception.GetType().FullName}: {exception.Message}"); - } -#pragma warning restore CA1031 // Do not catch general exception types - } - - return values; - } - - private object DestructureTask( - Task task, - int level, - IDictionary> destructuredObjects, - ref int nextCyclicRefId) - { - if (destructuredObjects.TryGetValue(task, out var destructuredTask)) - { - var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredTask); - - return new SortedList() - { - { RefLabel, refId }, - }; - } - - var values = new SortedList(); - destructuredObjects.Add(task, values); - - values[nameof(Task.Id)] = task.Id; - values[nameof(Task.Status)] = task.Status.ToString("G"); - values[nameof(Task.CreationOptions)] = task.CreationOptions.ToString("F"); - if (task.IsFaulted) - { - values[nameof(Task.Exception)] = this.DestructureValue(task.Exception, level, destructuredObjects, ref nextCyclicRefId); - } - - return values; - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Reflection; + +/// +/// Destructures exceptions by gathering all public non-indexer properties using reflection and then dynamically +/// retrieving their values. This class can handle every exception including those with circular references and +/// throwing properties. Additionally, a "Type" property is added to let the user know exact type of destructured +/// exception. +/// +public class ReflectionBasedDestructurer : IExceptionDestructurer +{ + private const string IdLabel = "$id"; + private const string RefLabel = "$ref"; + private const string CyclicReferenceMessage = "Cyclic reference"; + private const string IQueryableDestructureSkippedMessage = "IQueryable skipped"; + private readonly int destructuringDepth; + private readonly ReflectionInfoExtractor reflectionInfoExtractor = new(); + + /// + /// Initializes a new instance of the class. + /// + /// Maximum depth to which the destructurer will go when destructuring an + /// exception object graph. + public ReflectionBasedDestructurer(int destructuringDepth) + { + if (destructuringDepth <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(destructuringDepth), + destructuringDepth, + Resources.DestructuringDepthMustBeMoreThanZero); + } + + this.destructuringDepth = destructuringDepth; + } + + /// +#pragma warning disable CA1819 // Properties should not return arrays + public Type[] TargetTypes => new[] { typeof(Exception) }; +#pragma warning restore CA1819 // Properties should not return arrays + + /// + public void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(exception); + ArgumentNullException.ThrowIfNull(propertiesBag); + ArgumentNullException.ThrowIfNull(destructureException); +#else + if (exception is null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (propertiesBag is null) + { + throw new ArgumentNullException(nameof(propertiesBag)); + } + + if (destructureException is null) + { + throw new ArgumentNullException(nameof(destructureException)); + } +#endif + + var nextCyclicRefId = 1; + var destructuredObjects = new Dictionary>(); + + ExceptionDestructurer.DestructureCommonExceptionProperties( + exception, + propertiesBag, + destructureException, + data => this.DestructureValueDictionary(data, 1, destructuredObjects, ref nextCyclicRefId)); + + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(exception.GetType()); + + this.AppendProperties( + exception, + reflectionInfo.PropertiesExceptBaseOnes, + propertiesBag.AddProperty, + destructuredObjects, + level: 0, + nextCyclicRefId: ref nextCyclicRefId); + + AppendTypeIfPossible(propertiesBag, exception.GetType()); + } + + private static string? GetOrGenerateRefId(ref int nextCyclicRefId, IDictionary destructuredObject) + { + string? refId; + if (destructuredObject.ContainsKey(IdLabel)) + { + refId = (string?)destructuredObject[IdLabel]; + } + else + { + var id = nextCyclicRefId; + nextCyclicRefId++; + refId = id.ToString(CultureInfo.InvariantCulture); + destructuredObject[IdLabel] = refId; + } + + return refId; + } + + private static object DestructureUri(Uri value) => value.ToString(); + + private static void AppendTypeIfPossible(IExceptionPropertiesBag propertiesBag, Type valueType) + { + if (propertiesBag.ContainsProperty("Type")) + { + if (!propertiesBag.ContainsProperty("$Type")) + { + propertiesBag.AddProperty("$Type", valueType.FullName); + } + else + { + // If both "Type" and "$Type" are present + // we just give up appending exception type + } + } + else + { + propertiesBag.AddProperty("Type", valueType.FullName); + } + } + + private void AppendProperties( + object value, + ReflectionPropertyInfo[] reflectionPropertyInfos, + Action addPropertyAction, + IDictionary> destructuredObjects, + int level, + ref int nextCyclicRefId) + { + foreach (var property in reflectionPropertyInfos) + { + try + { + var valueToBeDestructured = property.Getter(value); + var localNextCyclicRefId = nextCyclicRefId; + var destructuredValue = this.DestructureValue( + valueToBeDestructured, + level + 1, + destructuredObjects, + ref localNextCyclicRefId); + nextCyclicRefId = localNextCyclicRefId; + addPropertyAction(property.Name, destructuredValue); + } + catch (TargetInvocationException targetInvocationException) + { + var innerException = targetInvocationException.InnerException; + if (innerException is not null) + { + addPropertyAction(property.Name, $"threw {innerException.GetType().FullName}: {innerException.Message}"); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception exception) + { + addPropertyAction(property.Name, $"threw {exception.GetType().FullName}: {exception.Message}"); + } +#pragma warning restore CA1031 // Do not catch general exception types + } + } + + private object? DestructureValue( + object? value, + int level, + IDictionary> destructuredObjects, + ref int nextCyclicRefId) + { + if (value is null) + { + return null; + } + + var valueType = value.GetType(); + var valueTypeInfo = valueType.GetTypeInfo(); + + if (value is MemberInfo) + { + return value; + } + + if (valueType.GetTypeCode() != TypeCode.Object || valueTypeInfo.IsValueType) + { + return value; + } + + if (level > this.destructuringDepth) + { + return value; + } + + if (value is IDictionary dictionary) + { + return this.DestructureValueDictionary(dictionary, level, destructuredObjects, ref nextCyclicRefId); + } + + if (value is IQueryable queryable) + { + return IQueryableDestructureSkippedMessage; + } + else if (value is IEnumerable enumerable) + { + return this.DestructureValueEnumerable(enumerable, level, destructuredObjects, ref nextCyclicRefId); + } + + if (value is Uri uri) + { + return DestructureUri(uri); + } + + if (value is CancellationToken ct) + { + return OperationCanceledExceptionDestructurer.DestructureCancellationToken(ct); + } + + if (value is Task task) + { + return this.DestructureTask(task, level, destructuredObjects, ref nextCyclicRefId); + } + + return this.DestructureObject(value, valueType, level, destructuredObjects, ref nextCyclicRefId); + } + + private object DestructureValueEnumerable( + IEnumerable value, + int level, + IDictionary> destructuredObjects, + ref int nextCyclicRefId) + { + if (destructuredObjects.ContainsKey(value)) + { + return new Dictionary + { + { RefLabel, CyclicReferenceMessage }, + }; + } + + destructuredObjects.Add(value, new Dictionary()); + + var resultList = new List(); + foreach (var o in value.Cast()) + { + resultList.Add(this.DestructureValue(o, level + 1, destructuredObjects, ref nextCyclicRefId)); + } + + return resultList; + } + + private object DestructureValueDictionary( + IDictionary value, + int level, + IDictionary> destructuredObjects, + ref int nextCyclicRefId) + { + if (destructuredObjects.ContainsKey(value)) + { + var destructuredObject = destructuredObjects[value]; + var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredObject); + + return new Dictionary + { + { RefLabel, refId }, + }; + } + + var destructuredDictionary = value.ToStringObjectDictionary(); + destructuredObjects.Add(value, destructuredDictionary); + + foreach (var kvp in destructuredDictionary.ToDictionary(k => k.Key, v => v.Value)) + { + destructuredDictionary[kvp.Key] = + this.DestructureValue(kvp.Value, level + 1, destructuredObjects, ref nextCyclicRefId); + } + + return destructuredDictionary; + } + + private IDictionary DestructureObject( + object value, + Type valueType, + int level, + IDictionary> destructuredObjects, + ref int nextCyclicRefId) + { + if (destructuredObjects.ContainsKey(value)) + { + var destructuredObject = destructuredObjects[value]; + var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredObject); + + return new Dictionary() + { + { RefLabel, refId }, + }; + } + + var values = new Dictionary(); + destructuredObjects.Add(value, values); + + var reflectionInfo = this.reflectionInfoExtractor.GetOrCreateReflectionInfo(valueType); + + foreach (var property in reflectionInfo.Properties) + { + try + { + var valueToBeDestructured = property.Getter(value); + var destructuredValue = this.DestructureValue( + valueToBeDestructured, + level + 1, + destructuredObjects, + ref nextCyclicRefId); + values.Add(property.Name, destructuredValue); + } + catch (TargetInvocationException targetInvocationException) + { + var innerException = targetInvocationException.InnerException; + if (innerException is not null) + { + values.Add(property.Name, $"threw {innerException.GetType().FullName}: {innerException.Message}"); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception exception) + { + values.Add(property.Name, $"threw {exception.GetType().FullName}: {exception.Message}"); + } +#pragma warning restore CA1031 // Do not catch general exception types + } + + return values; + } + + private object DestructureTask( + Task task, + int level, + IDictionary> destructuredObjects, + ref int nextCyclicRefId) + { + if (destructuredObjects.TryGetValue(task, out var destructuredTask)) + { + var refId = GetOrGenerateRefId(ref nextCyclicRefId, destructuredTask); + + return new SortedList() + { + { RefLabel, refId }, + }; + } + + var values = new SortedList(); + destructuredObjects.Add(task, values); + + values[nameof(Task.Id)] = task.Id; + values[nameof(Task.Status)] = task.Status.ToString("G"); + values[nameof(Task.CreationOptions)] = task.CreationOptions.ToString("F"); + if (task.IsFaulted) + { + values[nameof(Task.Exception)] = this.DestructureValue(task.Exception, level, destructuredObjects, ref nextCyclicRefId); + } + + return values; + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/ReflectionTypeLoadExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/ReflectionTypeLoadExceptionDestructurer.cs index dfe3ed20..315562c4 100644 --- a/Source/Serilog.Exceptions/Destructurers/ReflectionTypeLoadExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/ReflectionTypeLoadExceptionDestructurer.cs @@ -1,54 +1,54 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using System.Reflection; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class ReflectionTypeLoadExceptionDestructurer : ExceptionDestructurer -{ - /// - public override Type[] TargetTypes => new[] { typeof(ReflectionTypeLoadException) }; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { - base.Destructure(exception, propertiesBag, destructureException); - -#pragma warning disable CA1062 // Validate arguments of public methods - var reflectionTypeLoadException = (ReflectionTypeLoadException)exception; - if (reflectionTypeLoadException.LoaderExceptions is not null) - { - propertiesBag.AddProperty( - nameof(ReflectionTypeLoadException.LoaderExceptions), - GetLoaderExceptionsValue(reflectionTypeLoadException.LoaderExceptions, destructureException)); - } -#pragma warning restore CA1062 // Validate arguments of public methods - } - - private static List> GetLoaderExceptionsValue( - Exception?[] exceptions, - Func?> destructureException) - { - var loaderExceptionValues = new List>(); - foreach (var exception in exceptions) - { - if (exception is not null) - { - var dictionary = destructureException(exception); - if (dictionary is not null) - { - loaderExceptionValues.Add(dictionary); - } - } - } - - return loaderExceptionValues; - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using System.Reflection; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class ReflectionTypeLoadExceptionDestructurer : ExceptionDestructurer +{ + /// + public override Type[] TargetTypes => new[] { typeof(ReflectionTypeLoadException) }; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { + base.Destructure(exception, propertiesBag, destructureException); + +#pragma warning disable CA1062 // Validate arguments of public methods + var reflectionTypeLoadException = (ReflectionTypeLoadException)exception; + if (reflectionTypeLoadException.LoaderExceptions is not null) + { + propertiesBag.AddProperty( + nameof(ReflectionTypeLoadException.LoaderExceptions), + GetLoaderExceptionsValue(reflectionTypeLoadException.LoaderExceptions, destructureException)); + } +#pragma warning restore CA1062 // Validate arguments of public methods + } + + private static List> GetLoaderExceptionsValue( + Exception?[] exceptions, + Func?> destructureException) + { + var loaderExceptionValues = new List>(); + foreach (var exception in exceptions) + { + if (exception is not null) + { + var dictionary = destructureException(exception); + if (dictionary is not null) + { + loaderExceptionValues.Add(dictionary); + } + } + } + + return loaderExceptionValues; + } +} diff --git a/Source/Serilog.Exceptions/Destructurers/TaskCanceledExceptionDestructurer.cs b/Source/Serilog.Exceptions/Destructurers/TaskCanceledExceptionDestructurer.cs index 38b65a0d..95afd608 100644 --- a/Source/Serilog.Exceptions/Destructurers/TaskCanceledExceptionDestructurer.cs +++ b/Source/Serilog.Exceptions/Destructurers/TaskCanceledExceptionDestructurer.cs @@ -1,73 +1,73 @@ -namespace Serilog.Exceptions.Destructurers; - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Serilog.Exceptions.Core; - -/// -/// Destructurer for . -/// -public class TaskCanceledExceptionDestructurer : OperationCanceledExceptionDestructurer -{ - private static readonly Type[] TargetExceptionTypes = - { - typeof(TaskCanceledException), - }; - - /// - public override Type[] TargetTypes => TargetExceptionTypes; - - /// - public override void Destructure( - Exception exception, - IExceptionPropertiesBag propertiesBag, - Func?> destructureException) - { -#pragma warning disable CA1062 // Validate arguments of public methods - base.Destructure(exception, propertiesBag, destructureException); - - var taskCancelledException = (TaskCanceledException)exception; - propertiesBag.AddProperty( - nameof(TaskCanceledException.Task), - DestructureTask(taskCancelledException.Task, destructureException)); -#pragma warning restore CA1062 // Validate arguments of public methods - } - - /// - /// Destructures the specified task. - /// - /// The task. - /// The inner destructure. - /// The destructured task. - internal static object? DestructureTask( - Task? task, - Func?> innerDestructure) - { - if (task is null) - { - return null; - } - - var taskStatus = task.Status.ToString("G"); - var taskCreationOptions = task.CreationOptions.ToString("F"); - - if (task.IsFaulted && task.Exception is not null) - { - return new - { - task.Id, - Status = taskStatus, - CreationOptions = taskCreationOptions, - Exception = innerDestructure(task.Exception), - }; - } - - return new - { - task.Id, - Status = taskStatus, - CreationOptions = taskCreationOptions, - }; - } -} +namespace Serilog.Exceptions.Destructurers; + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Serilog.Exceptions.Core; + +/// +/// Destructurer for . +/// +public class TaskCanceledExceptionDestructurer : OperationCanceledExceptionDestructurer +{ + private static readonly Type[] TargetExceptionTypes = + { + typeof(TaskCanceledException), + }; + + /// + public override Type[] TargetTypes => TargetExceptionTypes; + + /// + public override void Destructure( + Exception exception, + IExceptionPropertiesBag propertiesBag, + Func?> destructureException) + { +#pragma warning disable CA1062 // Validate arguments of public methods + base.Destructure(exception, propertiesBag, destructureException); + + var taskCancelledException = (TaskCanceledException)exception; + propertiesBag.AddProperty( + nameof(TaskCanceledException.Task), + DestructureTask(taskCancelledException.Task, destructureException)); +#pragma warning restore CA1062 // Validate arguments of public methods + } + + /// + /// Destructures the specified task. + /// + /// The task. + /// The inner destructure. + /// The destructured task. + internal static object? DestructureTask( + Task? task, + Func?> innerDestructure) + { + if (task is null) + { + return null; + } + + var taskStatus = task.Status.ToString("G"); + var taskCreationOptions = task.CreationOptions.ToString("F"); + + if (task.IsFaulted && task.Exception is not null) + { + return new + { + task.Id, + Status = taskStatus, + CreationOptions = taskCreationOptions, + Exception = innerDestructure(task.Exception), + }; + } + + return new + { + task.Id, + Status = taskStatus, + CreationOptions = taskCreationOptions, + }; + } +} diff --git a/Source/Serilog.Exceptions/Filters/CompositeExceptionPropertyFilter.cs b/Source/Serilog.Exceptions/Filters/CompositeExceptionPropertyFilter.cs index 2ee571bf..f4533786 100644 --- a/Source/Serilog.Exceptions/Filters/CompositeExceptionPropertyFilter.cs +++ b/Source/Serilog.Exceptions/Filters/CompositeExceptionPropertyFilter.cs @@ -1,62 +1,62 @@ -namespace Serilog.Exceptions.Filters; - -using System; - -/// -/// Abstraction over collection of filters that filters property is any of given filters alone would filter it. -/// This is equivalent to OR over a set of booleans. Executes filters in the order they were passed to a -/// constructor. -/// -public class CompositeExceptionPropertyFilter : IExceptionPropertyFilter -{ - private readonly IExceptionPropertyFilter[] filters; - - /// - /// Initializes a new instance of the class. - /// - /// The filters. - /// filters was null. - /// filters was empty or filter at index {i} is null. - public CompositeExceptionPropertyFilter(params IExceptionPropertyFilter[] filters) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(filters); -#else - if (filters is null) - { - throw new ArgumentNullException(nameof(filters)); - } -#endif - - if (filters.Length == 0) - { - throw new ArgumentException(Resources.CannotBeEmpty, nameof(filters)); - } - - for (var i = 0; i < filters.Length; ++i) - { - if (filters[i] is null) - { - throw new ArgumentException( - $"Cannot create composite exception properties filter, because filter at index {i} is null", - nameof(filters)); - } - } - - this.filters = filters; - } - - /// - public bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value) - { - for (var i = 0; i < this.filters.Length; ++i) - { - if (this.filters[i].ShouldPropertyBeFiltered(exception, propertyName, value)) - { - return true; - } - } - - return false; - } -} +namespace Serilog.Exceptions.Filters; + +using System; + +/// +/// Abstraction over collection of filters that filters property is any of given filters alone would filter it. +/// This is equivalent to OR over a set of booleans. Executes filters in the order they were passed to a +/// constructor. +/// +public class CompositeExceptionPropertyFilter : IExceptionPropertyFilter +{ + private readonly IExceptionPropertyFilter[] filters; + + /// + /// Initializes a new instance of the class. + /// + /// The filters. + /// filters was null. + /// filters was empty or filter at index {i} is null. + public CompositeExceptionPropertyFilter(params IExceptionPropertyFilter[] filters) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(filters); +#else + if (filters is null) + { + throw new ArgumentNullException(nameof(filters)); + } +#endif + + if (filters.Length == 0) + { + throw new ArgumentException(Resources.CannotBeEmpty, nameof(filters)); + } + + for (var i = 0; i < filters.Length; ++i) + { + if (filters[i] is null) + { + throw new ArgumentException( + $"Cannot create composite exception properties filter, because filter at index {i} is null", + nameof(filters)); + } + } + + this.filters = filters; + } + + /// + public bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value) + { + for (var i = 0; i < this.filters.Length; ++i) + { + if (this.filters[i].ShouldPropertyBeFiltered(exception, propertyName, value)) + { + return true; + } + } + + return false; + } +} diff --git a/Source/Serilog.Exceptions/Filters/IExceptionPropertyFilter.cs b/Source/Serilog.Exceptions/Filters/IExceptionPropertyFilter.cs index 1d57af3b..bed98f71 100644 --- a/Source/Serilog.Exceptions/Filters/IExceptionPropertyFilter.cs +++ b/Source/Serilog.Exceptions/Filters/IExceptionPropertyFilter.cs @@ -1,19 +1,19 @@ -namespace Serilog.Exceptions.Filters; - -using System; - -/// -/// Interface used for filtering exception properties. Filtering process is global, each property of every -/// exception will go through a configured exception property filter. -/// -public interface IExceptionPropertyFilter -{ - /// - /// Called after the property was discovered and destructured but just before it is added to results. - /// - /// Exception for which properties are filtered. - /// Name of the property. - /// Destructured value of the property. - /// Boolean flag indicating whether property will be rejected. - bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value); -} +namespace Serilog.Exceptions.Filters; + +using System; + +/// +/// Interface used for filtering exception properties. Filtering process is global, each property of every +/// exception will go through a configured exception property filter. +/// +public interface IExceptionPropertyFilter +{ + /// + /// Called after the property was discovered and destructured but just before it is added to results. + /// + /// Exception for which properties are filtered. + /// Name of the property. + /// Destructured value of the property. + /// Boolean flag indicating whether property will be rejected. + bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value); +} diff --git a/Source/Serilog.Exceptions/Filters/IgnorePropertyByNameExceptionFilter.cs b/Source/Serilog.Exceptions/Filters/IgnorePropertyByNameExceptionFilter.cs index f1639804..be55d64e 100644 --- a/Source/Serilog.Exceptions/Filters/IgnorePropertyByNameExceptionFilter.cs +++ b/Source/Serilog.Exceptions/Filters/IgnorePropertyByNameExceptionFilter.cs @@ -1,43 +1,43 @@ -namespace Serilog.Exceptions.Filters; - -using System; - -/// -/// Filters the exception properties based only on their name. If exception property matches any of provided -/// property names, exception property is ignored altogether. Comparison method is exact case-sensitive. -/// -public class IgnorePropertyByNameExceptionFilter : IExceptionPropertyFilter -{ - /// - /// The usage of array instead of HashSet is dictated by the assumption - /// that there will be only small number of properties to ignore and for such - /// case array is much faster than HashSet. - /// - private readonly string[] propertiesToIgnore; - - /// - /// Initializes a new instance of the class. - /// - /// The properties to ignore. - public IgnorePropertyByNameExceptionFilter(params string[] propertiesToIgnore) => - this.propertiesToIgnore = propertiesToIgnore; - - /// - public bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value) - { - if (this.propertiesToIgnore is null) - { - return false; - } - - for (var i = 0; i < this.propertiesToIgnore.Length; i++) - { - if (this.propertiesToIgnore[i].Equals(propertyName, StringComparison.Ordinal)) - { - return true; - } - } - - return false; - } -} +namespace Serilog.Exceptions.Filters; + +using System; + +/// +/// Filters the exception properties based only on their name. If exception property matches any of provided +/// property names, exception property is ignored altogether. Comparison method is exact case-sensitive. +/// +public class IgnorePropertyByNameExceptionFilter : IExceptionPropertyFilter +{ + /// + /// The usage of array instead of HashSet is dictated by the assumption + /// that there will be only small number of properties to ignore and for such + /// case array is much faster than HashSet. + /// + private readonly string[] propertiesToIgnore; + + /// + /// Initializes a new instance of the class. + /// + /// The properties to ignore. + public IgnorePropertyByNameExceptionFilter(params string[] propertiesToIgnore) => + this.propertiesToIgnore = propertiesToIgnore; + + /// + public bool ShouldPropertyBeFiltered(Exception exception, string propertyName, object? value) + { + if (this.propertiesToIgnore is null) + { + return false; + } + + for (var i = 0; i < this.propertiesToIgnore.Length; i++) + { + if (this.propertiesToIgnore[i].Equals(propertyName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } +} diff --git a/Source/Serilog.Exceptions/LoggerEnrichmentConfigurationExtensions.cs b/Source/Serilog.Exceptions/LoggerEnrichmentConfigurationExtensions.cs index cf6d7381..e9f914a9 100644 --- a/Source/Serilog.Exceptions/LoggerEnrichmentConfigurationExtensions.cs +++ b/Source/Serilog.Exceptions/LoggerEnrichmentConfigurationExtensions.cs @@ -1,63 +1,63 @@ -namespace Serilog.Exceptions; - -using System; -using Serilog.Configuration; -using Serilog.Core; -using Serilog.Exceptions.Core; - -/// -/// Serilog logger enrichment extension methods. -/// -public static class LoggerEnrichmentConfigurationExtensions -{ - /// - /// Enrich logger output with a destructured object containing exception's public properties. Default - /// destructurers are registered. and Exception.TargetSite - /// are omitted by the destructuring process because Serilog already attaches them to log event. - /// - /// The enrichment configuration. - /// Configuration object allowing method chaining. - public static LoggerConfiguration WithExceptionDetails( - this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(loggerEnrichmentConfiguration); -#else - if (loggerEnrichmentConfiguration is null) - { - throw new ArgumentNullException(nameof(loggerEnrichmentConfiguration)); - } -#endif - - var options = new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithIgnoreStackTraceAndTargetSiteExceptionFilter(); - var logEventEnricher = new ExceptionEnricher(options); - return loggerEnrichmentConfiguration.With(logEventEnricher); - } - - /// - /// Enrich logger output with a destuctured object containing exception's public properties. - /// - /// The enrichment configuration. - /// - /// Options that will influence the process of destructuring exception's properties into result object. - /// - /// Configuration object allowing method chaining. - public static LoggerConfiguration WithExceptionDetails( - this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration, - IDestructuringOptions destructuringOptions) - { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(loggerEnrichmentConfiguration); -#else - if (loggerEnrichmentConfiguration is null) - { - throw new ArgumentNullException(nameof(loggerEnrichmentConfiguration)); - } -#endif - - ILogEventEnricher enricher = new ExceptionEnricher(destructuringOptions); - return loggerEnrichmentConfiguration.With(enricher); - } -} +namespace Serilog.Exceptions; + +using System; +using Serilog.Configuration; +using Serilog.Core; +using Serilog.Exceptions.Core; + +/// +/// Serilog logger enrichment extension methods. +/// +public static class LoggerEnrichmentConfigurationExtensions +{ + /// + /// Enrich logger output with a destructured object containing exception's public properties. Default + /// destructurers are registered. and Exception.TargetSite + /// are omitted by the destructuring process because Serilog already attaches them to log event. + /// + /// The enrichment configuration. + /// Configuration object allowing method chaining. + public static LoggerConfiguration WithExceptionDetails( + this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(loggerEnrichmentConfiguration); +#else + if (loggerEnrichmentConfiguration is null) + { + throw new ArgumentNullException(nameof(loggerEnrichmentConfiguration)); + } +#endif + + var options = new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithIgnoreStackTraceAndTargetSiteExceptionFilter(); + var logEventEnricher = new ExceptionEnricher(options); + return loggerEnrichmentConfiguration.With(logEventEnricher); + } + + /// + /// Enrich logger output with a destuctured object containing exception's public properties. + /// + /// The enrichment configuration. + /// + /// Options that will influence the process of destructuring exception's properties into result object. + /// + /// Configuration object allowing method chaining. + public static LoggerConfiguration WithExceptionDetails( + this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration, + IDestructuringOptions destructuringOptions) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(loggerEnrichmentConfiguration); +#else + if (loggerEnrichmentConfiguration is null) + { + throw new ArgumentNullException(nameof(loggerEnrichmentConfiguration)); + } +#endif + + ILogEventEnricher enricher = new ExceptionEnricher(destructuringOptions); + return loggerEnrichmentConfiguration.With(enricher); + } +} diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ExceptionDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ExceptionDestructurerTest.cs index 94b29e7f..e093a23f 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ExceptionDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ExceptionDestructurerTest.cs @@ -1,238 +1,238 @@ -namespace Serilog.Exceptions.Test.Destructurers; - -using System; -using System.Collections.Generic; -using System.Globalization; -using FluentAssertions; -using Microsoft.EntityFrameworkCore; -using Moq; -using Newtonsoft.Json.Linq; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Filters; -using Xunit; -using static LogJsonOutputUtils; - -#pragma warning disable CA2208 // Instantiate argument exceptions correctly -public class ExceptionDestructurerTest -{ - [Fact] - public void ArgumentException_ContainsMessage() - { - var applicationException = new ArgumentException("MSG"); - Test_LoggedExceptionContainsProperty(applicationException, "Message", "MSG"); - } - - [Fact] - public void ArgumentException_ContainsHelpLink() - { - var applicationException = new ArgumentException() { HelpLink = "HELP LINK" }; - Test_LoggedExceptionContainsProperty(applicationException, "HelpLink", "HELP LINK"); - } - - [Fact] - public void ArgumentException_ContainsSource() - { - var applicationException = new ArgumentException() { Source = "SOURCE" }; - Test_LoggedExceptionContainsProperty(applicationException, "Source", "SOURCE"); - } - - [Fact] - public void ArgumentException_WithoutStackTrace_ContainsNullStackTrace() - { - var applicationException = new ArgumentException(); - Test_LoggedExceptionContainsProperty(applicationException, "StackTrace", null!); - } - - [Fact] - public void ArgumentException_ContainsData() - { - var applicationException = new ArgumentException(); - applicationException.Data["SOMEKEY"] = "SOMEVALUE"; - - var rootObject = LogAndDestructureException(applicationException); - var exceptionDetail = ExtractExceptionDetails(rootObject); - - var dataProperty = Assert.Single(exceptionDetail.Properties(), x => x.Name == "Data"); - var dataObject = Assert.IsType(dataProperty.Value); - - var someKeyProperty = Assert.Single(dataObject.Properties(), x => x.Name == "SOMEKEY"); - var someKeyValue = Assert.IsType(someKeyProperty.Value); - Assert.Equal("SOMEVALUE", someKeyValue.Value); - } - - [Fact] - public void ArgumentException_WithCustomRootName_ContainsDataInCustomRootName() - { - const string customRootName = "Ex"; - var applicationException = new ArgumentException(); - applicationException.Data["SOMEKEY"] = "SOMEVALUE"; - - var rootObject = LogAndDestructureException( - applicationException, - destructuringOptions: new DestructuringOptionsBuilder().WithDefaultDestructurers().WithRootName(customRootName)); - var exceptionDetail = ExtractExceptionDetails(rootObject, customRootName); - - Assert.Single(exceptionDetail.Properties(), x => x.Name == "Data"); - } - - [Fact] - public void PassedFilter_IsCalledWithCorrectArguments() - { - // Arrange - var exception = new Exception(); - var filterMock = new Mock(); - - // Act - LogAndDestructureException(exception, new DestructuringOptionsBuilder().WithFilter(filterMock.Object)); - - // Assert - filterMock.Verify(x => x.ShouldPropertyBeFiltered(exception, "StackTrace", null)); - } - - [Fact] - public void WithoutReflectionBasedDestructurer_CustomExceptionIsNotLogged() - { - // Arrange - var exception = new DictNonScalarKeyException(); - var options = new DestructuringOptionsBuilder().WithoutReflectionBasedDestructurer(); - - // Act - var rootObject = LogAndDestructureException(exception, options); - - // Assert - rootObject.Properties().Should().NotContain(x => x.Name == "Properties"); - } - - [Fact] - public void WithoutReflectionBasedDestructurerAndCustomRootName_StandardExceptionIsLogged() - { - // Arrange - var exception = new ArgumentException("ARG", "arg"); - var options = new DestructuringOptionsBuilder() - .WithDefaultDestructurers() - .WithoutReflectionBasedDestructurer() - .WithRootName("CUSTOM-ROOT"); - - // Act - var rootObject = LogAndDestructureException(exception, options); - - // Assert - var exceptionObject = ExtractExceptionDetails(rootObject, "CUSTOM-ROOT"); - var paramObject = exceptionObject.Properties().Should().ContainSingle(x => x.Name == "ParamName").Which; - paramObject.Value.Should().BeOfType().Which.Value.Should().Be("arg"); - } - - [Fact] - public void ArgumentException_WithStackTrace_ContainsStackTrace() - { - try - { - throw new ArgumentException(); - } - catch (ArgumentException ex) - { - Test_LoggedExceptionContainsProperty(ex, "StackTrace", ex.StackTrace?.ToString(CultureInfo.InvariantCulture)); - } - } - - [Fact] - public void ArgumentException_ContainsType() - { - var applicationException = new ArgumentException(); - Test_LoggedExceptionContainsProperty(applicationException, "Type", "System.ArgumentException"); - } - - [Fact] - public void WhenExceptionContainsDictionaryWithNonScalarValue_ShouldNotThrow() - { - // Arrange - var exception = new DictNonScalarKeyException(); - exception.Reference.Add(new List() { 1, 2, 3 }, "VALUE"); - - // Act - var result = LogAndDestructureException(exception, new DestructuringOptionsBuilder()); - - // Assert - var exceptionDetails = ExtractExceptionDetails(result); - var referenceProperty = exceptionDetails.Should().BeOfType().Which - .Properties().Should().ContainSingle(x => x.Name == "Reference").Which; - - var referenceObject = referenceProperty.Value.Should().BeOfType().Which; - var kvp = referenceObject.Properties().Should().ContainSingle() - .Which.Should().BeOfType() - .Which.Name.Should().Be("System.Collections.Generic.List`1[System.Int32]"); - } - - [Fact] - public void WhenExceptionContainsDbContext_ShouldSkipIQueryableProperties() - { - // Arrange - using var context = new ExceptionDbContext(); - var exception = new CustomDbContextException("hello world", context); - - // Act - var result = LogAndDestructureException(exception, new DestructuringOptionsBuilder()); - - // Assert - var exceptionDetails = ExtractExceptionDetails(result).Should().BeOfType().Which; - var nameProperty = exceptionDetails - .Properties().Should().ContainSingle(x => x.Name == nameof(CustomDbContextException.Name)).Which - .Should().BeOfType().Which; - - nameProperty.Value.Should().BeOfType().Which.Value.Should().Be("hello world"); - - var contextProperty = exceptionDetails - .Properties().Should().ContainSingle(x => x.Name == nameof(CustomDbContextException.Context)).Which; - - var customerProperty = contextProperty.Value.Should().BeOfType().Which - .Properties().Should().ContainSingle(x => x.Name == nameof(ExceptionDbContext.Customer)).Which; - - customerProperty.Value.Should().BeOfType().Which.Value.Should().BeOfType().Which - .Should().Be("IQueryable skipped"); - } - - public class DictNonScalarKeyException : Exception - { - public DictNonScalarKeyException() => this.Reference = new Dictionary, object>(); - - public DictNonScalarKeyException(string message) - : base(message) => - this.Reference = new Dictionary, object>(); - - public DictNonScalarKeyException(string message, Exception innerException) - : base(message, innerException) => - this.Reference = new Dictionary, object>(); - - public Dictionary, object> Reference { get; } - } - -#pragma warning disable CS3001 // Argument type is not CLS-compliant -#pragma warning disable CS3003 // Type is not CLS-compliant - public class CustomDbContextException : Exception - { - public CustomDbContextException(string name, DbContext context) - { - this.Name = name; - this.Context = context; - } - - public string Name { get; set; } - - public DbContext Context { get; } - } - - private class ExceptionDbContext : DbContext - { - public DbSet Customer => this.Set(); - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase(databaseName: "TestDebUpdateException"); - - public class CustomerEntity - { - public string? Name { get; set; } - - public int Id { get; set; } - } - } -} -#pragma warning restore CA2208 // Instantiate argument exceptions correctly +namespace Serilog.Exceptions.Test.Destructurers; + +using System; +using System.Collections.Generic; +using System.Globalization; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Moq; +using Newtonsoft.Json.Linq; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Filters; +using Xunit; +using static LogJsonOutputUtils; + +#pragma warning disable CA2208 // Instantiate argument exceptions correctly +public class ExceptionDestructurerTest +{ + [Fact] + public void ArgumentException_ContainsMessage() + { + var applicationException = new ArgumentException("MSG"); + Test_LoggedExceptionContainsProperty(applicationException, "Message", "MSG"); + } + + [Fact] + public void ArgumentException_ContainsHelpLink() + { + var applicationException = new ArgumentException() { HelpLink = "HELP LINK" }; + Test_LoggedExceptionContainsProperty(applicationException, "HelpLink", "HELP LINK"); + } + + [Fact] + public void ArgumentException_ContainsSource() + { + var applicationException = new ArgumentException() { Source = "SOURCE" }; + Test_LoggedExceptionContainsProperty(applicationException, "Source", "SOURCE"); + } + + [Fact] + public void ArgumentException_WithoutStackTrace_ContainsNullStackTrace() + { + var applicationException = new ArgumentException(); + Test_LoggedExceptionContainsProperty(applicationException, "StackTrace", null!); + } + + [Fact] + public void ArgumentException_ContainsData() + { + var applicationException = new ArgumentException(); + applicationException.Data["SOMEKEY"] = "SOMEVALUE"; + + var rootObject = LogAndDestructureException(applicationException); + var exceptionDetail = ExtractExceptionDetails(rootObject); + + var dataProperty = Assert.Single(exceptionDetail.Properties(), x => x.Name == "Data"); + var dataObject = Assert.IsType(dataProperty.Value); + + var someKeyProperty = Assert.Single(dataObject.Properties(), x => x.Name == "SOMEKEY"); + var someKeyValue = Assert.IsType(someKeyProperty.Value); + Assert.Equal("SOMEVALUE", someKeyValue.Value); + } + + [Fact] + public void ArgumentException_WithCustomRootName_ContainsDataInCustomRootName() + { + const string customRootName = "Ex"; + var applicationException = new ArgumentException(); + applicationException.Data["SOMEKEY"] = "SOMEVALUE"; + + var rootObject = LogAndDestructureException( + applicationException, + destructuringOptions: new DestructuringOptionsBuilder().WithDefaultDestructurers().WithRootName(customRootName)); + var exceptionDetail = ExtractExceptionDetails(rootObject, customRootName); + + Assert.Single(exceptionDetail.Properties(), x => x.Name == "Data"); + } + + [Fact] + public void PassedFilter_IsCalledWithCorrectArguments() + { + // Arrange + var exception = new Exception(); + var filterMock = new Mock(); + + // Act + LogAndDestructureException(exception, new DestructuringOptionsBuilder().WithFilter(filterMock.Object)); + + // Assert + filterMock.Verify(x => x.ShouldPropertyBeFiltered(exception, "StackTrace", null)); + } + + [Fact] + public void WithoutReflectionBasedDestructurer_CustomExceptionIsNotLogged() + { + // Arrange + var exception = new DictNonScalarKeyException(); + var options = new DestructuringOptionsBuilder().WithoutReflectionBasedDestructurer(); + + // Act + var rootObject = LogAndDestructureException(exception, options); + + // Assert + rootObject.Properties().Should().NotContain(x => x.Name == "Properties"); + } + + [Fact] + public void WithoutReflectionBasedDestructurerAndCustomRootName_StandardExceptionIsLogged() + { + // Arrange + var exception = new ArgumentException("ARG", "arg"); + var options = new DestructuringOptionsBuilder() + .WithDefaultDestructurers() + .WithoutReflectionBasedDestructurer() + .WithRootName("CUSTOM-ROOT"); + + // Act + var rootObject = LogAndDestructureException(exception, options); + + // Assert + var exceptionObject = ExtractExceptionDetails(rootObject, "CUSTOM-ROOT"); + var paramObject = exceptionObject.Properties().Should().ContainSingle(x => x.Name == "ParamName").Which; + paramObject.Value.Should().BeOfType().Which.Value.Should().Be("arg"); + } + + [Fact] + public void ArgumentException_WithStackTrace_ContainsStackTrace() + { + try + { + throw new ArgumentException(); + } + catch (ArgumentException ex) + { + Test_LoggedExceptionContainsProperty(ex, "StackTrace", ex.StackTrace?.ToString(CultureInfo.InvariantCulture)); + } + } + + [Fact] + public void ArgumentException_ContainsType() + { + var applicationException = new ArgumentException(); + Test_LoggedExceptionContainsProperty(applicationException, "Type", "System.ArgumentException"); + } + + [Fact] + public void WhenExceptionContainsDictionaryWithNonScalarValue_ShouldNotThrow() + { + // Arrange + var exception = new DictNonScalarKeyException(); + exception.Reference.Add(new List() { 1, 2, 3 }, "VALUE"); + + // Act + var result = LogAndDestructureException(exception, new DestructuringOptionsBuilder()); + + // Assert + var exceptionDetails = ExtractExceptionDetails(result); + var referenceProperty = exceptionDetails.Should().BeOfType().Which + .Properties().Should().ContainSingle(x => x.Name == "Reference").Which; + + var referenceObject = referenceProperty.Value.Should().BeOfType().Which; + var kvp = referenceObject.Properties().Should().ContainSingle() + .Which.Should().BeOfType() + .Which.Name.Should().Be("System.Collections.Generic.List`1[System.Int32]"); + } + + [Fact] + public void WhenExceptionContainsDbContext_ShouldSkipIQueryableProperties() + { + // Arrange + using var context = new ExceptionDbContext(); + var exception = new CustomDbContextException("hello world", context); + + // Act + var result = LogAndDestructureException(exception, new DestructuringOptionsBuilder()); + + // Assert + var exceptionDetails = ExtractExceptionDetails(result).Should().BeOfType().Which; + var nameProperty = exceptionDetails + .Properties().Should().ContainSingle(x => x.Name == nameof(CustomDbContextException.Name)).Which + .Should().BeOfType().Which; + + nameProperty.Value.Should().BeOfType().Which.Value.Should().Be("hello world"); + + var contextProperty = exceptionDetails + .Properties().Should().ContainSingle(x => x.Name == nameof(CustomDbContextException.Context)).Which; + + var customerProperty = contextProperty.Value.Should().BeOfType().Which + .Properties().Should().ContainSingle(x => x.Name == nameof(ExceptionDbContext.Customer)).Which; + + customerProperty.Value.Should().BeOfType().Which.Value.Should().BeOfType().Which + .Should().Be("IQueryable skipped"); + } + + public class DictNonScalarKeyException : Exception + { + public DictNonScalarKeyException() => this.Reference = new Dictionary, object>(); + + public DictNonScalarKeyException(string message) + : base(message) => + this.Reference = new Dictionary, object>(); + + public DictNonScalarKeyException(string message, Exception innerException) + : base(message, innerException) => + this.Reference = new Dictionary, object>(); + + public Dictionary, object> Reference { get; } + } + +#pragma warning disable CS3001 // Argument type is not CLS-compliant +#pragma warning disable CS3003 // Type is not CLS-compliant + public class CustomDbContextException : Exception + { + public CustomDbContextException(string name, DbContext context) + { + this.Name = name; + this.Context = context; + } + + public string Name { get; set; } + + public DbContext Context { get; } + } + + private class ExceptionDbContext : DbContext + { + public DbSet Customer => this.Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseInMemoryDatabase(databaseName: "TestDebUpdateException"); + + public class CustomerEntity + { + public string? Name { get; set; } + + public int Id { get; set; } + } + } +} +#pragma warning restore CA2208 // Instantiate argument exceptions correctly diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs index 920cdb5b..d77329c4 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/ReflectionBasedDestructurerTest.cs @@ -1,642 +1,642 @@ -namespace Serilog.Exceptions.Test.Destructurers; - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Serilog.Exceptions.Core; -using Serilog.Exceptions.Destructurers; -using Xunit; -using static LogJsonOutputUtils; - -public class ReflectionBasedDestructurerTest -{ - [Fact] - public void DestructureComplexException_EachTypeOfPropertyIsDestructuredAsExpected() - { - // Arrange - var exception = ThrowAndCatchException(() => throw new TestException()); - var propertiesBag = new ExceptionPropertiesBag(exception); - - // Act - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - // Assert - var properties = propertiesBag.GetResultDictionary(); - Assert.Equal("PublicValue", properties[nameof(TestException.PublicProperty)]); - Assert.Equal("threw System.Exception: Exception of type 'System.Exception' was thrown.", properties[nameof(TestException.ExceptionProperty)]); - Assert.DoesNotContain(properties, x => string.Equals(x.Key, "InternalProperty", StringComparison.Ordinal)); - Assert.DoesNotContain(properties, x => string.Equals(x.Key, "ProtectedProperty", StringComparison.Ordinal)); - Assert.DoesNotContain(properties, x => string.Equals(x.Key, "PrivateProperty", StringComparison.Ordinal)); - Assert.Equal("MessageValue", properties[nameof(TestException.Message)]); -#if NET461 || NET472 - Assert.StartsWith("Void DestructureComplexException_EachTypeOfPropertyIsDestructuredAsExpected(", properties[nameof(TestException.TargetSite)].ToString()); -#endif - Assert.NotEmpty(properties[nameof(TestException.StackTrace)]?.ToString()); - Assert.Equal("Serilog.Exceptions.Test", properties[nameof(TestException.Source)]); - Assert.Equal(-2146233088, properties[nameof(TestException.HResult)]); - Assert.Contains(typeof(TestException).FullName, properties["Type"]?.ToString(), StringComparison.Ordinal); - } - - [Fact] - public void CanDestructureUriProperty() - { - const string uriValue = "http://localhost/property"; - var exception = new UriException("test", new Uri(uriValue)); - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var uriPropertyValue = properties[nameof(UriException.Uri)]; - Assert.IsType(uriPropertyValue); - Assert.Equal(uriValue, uriPropertyValue); - } - - [Fact] - public void CanDestructureUriDataItem() - { - const string uriValue = "http://localhost/data-item"; - var exception = new Exception("test") - { - Data = - { - { "UriDataItem", new Uri(uriValue) }, - }, - }; - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var data = (IDictionary?)properties[nameof(Exception.Data)]; - var uriDataValue = data?["UriDataItem"]; - Assert.IsType(uriDataValue); - Assert.Equal(uriValue, uriDataValue); - } - - [Fact] - public async Task CanDestructureTaskAsync() - { - using var cancellationTokenSource = new CancellationTokenSource(0); - - TaskCanceledException exception; - try - { - await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); - Assert.True(false, "TaskCanceledException was not thrown."); - return; - } - catch (TaskCanceledException taskCancelledException) - { - exception = taskCancelledException; - } - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var destructuredTaskObject = (IDictionary?)properties[nameof(TaskCanceledException.Task)]; - var destructuredTaskProperties = Assert.IsAssignableFrom>(destructuredTaskObject); - destructuredTaskProperties.Should().ContainKey(nameof(Task.Id)); - destructuredTaskProperties.Should().ContainKey(nameof(Task.Status)) - .WhoseValue.Should().BeOfType() - .Which.Should().Be(nameof(TaskStatus.Canceled)); - destructuredTaskProperties.Should().ContainKey(nameof(Task.CreationOptions)) - .WhoseValue.Should().BeOfType() - .Which.Should().Contain(nameof(TaskCreationOptions.None)); - } - - [Fact] - public void CanDestructureFaultedTask() - { - var taskException = new Exception("INNER EXCEPTION MESSAGE"); - var task = Task.FromException(taskException); - var exception = new TaskException("TASK EXCEPTION MESSAGE", task); - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, InnerDestructurer(CreateReflectionBasedDestructurer())); - - var properties = propertiesBag.GetResultDictionary(); - var destructuredTaskObject = (IDictionary?)properties[nameof(TaskCanceledException.Task)]; - var destructuredTaskProperties = Assert.IsAssignableFrom>(destructuredTaskObject); - destructuredTaskProperties.Should().ContainKey(nameof(Task.Id)); - destructuredTaskProperties.Should().ContainKey(nameof(Task.Status)) - .WhoseValue.Should().BeOfType() - .Which.Should().Be(nameof(TaskStatus.Faulted)); - destructuredTaskProperties.Should().ContainKey(nameof(Task.CreationOptions)) - .WhoseValue.Should().BeOfType() - .Which.Should().Be(nameof(TaskCreationOptions.None)); - var taskFirstLevelExceptionDictionary = destructuredTaskProperties.Should().ContainKey(nameof(Task.Exception)) - .WhoseValue.Should().BeAssignableTo>() - .Which; - taskFirstLevelExceptionDictionary.Should().ContainKey("Message") - .WhoseValue.Should().BeOfType() - .Which.Should().Contain("One or more errors occurred.", "task's first level exception is aggregate exception"); - taskFirstLevelExceptionDictionary.Should().ContainKey("InnerExceptions") - .WhoseValue.Should().BeAssignableTo>() - .Which.Should().ContainSingle() - .Which.Should().BeAssignableTo>() - .Which.Should().ContainKey("Message") - .WhoseValue.Should().BeOfType() - .Which.Should().Be("INNER EXCEPTION MESSAGE"); - } - - [Fact] - public void CanDestructureStructDataItem() - { - // Arrange - var exception = new Exception("test"); - exception.Data["data"] = new TestStruct() - { - ValueType = 10, - ReferenceType = "ABC", - }; - var propertiesBag = new ExceptionPropertiesBag(exception); - - // Act - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - // Assert - var properties = propertiesBag.GetResultDictionary(); - var data = (IDictionary?)properties[nameof(Exception.Data)]; - var testStructDataValue = data?["data"]; - Assert.IsAssignableFrom(testStructDataValue); - } - - [Fact] - public void CanDestructureClassDataItem() - { - // Arrange - var exception = new Exception("test"); - exception.Data["data"] = new TestClass() - { - ValueType = 10, - ReferenceType = "ABC", - }; - var propertiesBag = new ExceptionPropertiesBag(exception); - - // Act - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - // Assert - var properties = propertiesBag.GetResultDictionary(); - var data = (IDictionary?)properties[nameof(Exception.Data)]; - var testStructDataValue = data?["data"]; - var destructuredStructDictionary = Assert.IsAssignableFrom>(testStructDataValue); - Assert.Equal(10, destructuredStructDictionary[nameof(TestClass.ValueType)]); - Assert.Equal("ABC", destructuredStructDictionary[nameof(TestClass.ReferenceType)]); - } - - [Fact] - public void DestructuringDepthIsLimitedByConfiguredDepth() - { - // Arrange - var exception = new RecursiveException() - { - Node = new RecursiveNode() - { - Name = "PARENT", - Child = new RecursiveNode() - { - Name = "CHILD 1", - Child = new RecursiveNode() - { - Name = "CHILD 2", - }, - }, - }, - }; - var destructurer = new ReflectionBasedDestructurer(1); - - // Act - var propertiesBag = new ExceptionPropertiesBag(exception); - destructurer.Destructure(exception, propertiesBag, EmptyDestructurer()); - - // Assert - // Parent is depth 1 - // First child is depth 2 - var properties = propertiesBag.GetResultDictionary(); - var parent = (IDictionary?)properties[nameof(RecursiveException.Node)]; - Assert.Equal("PARENT", parent?[nameof(RecursiveNode.Name)]); - Assert.IsType(parent?[nameof(RecursiveNode.Child)]); - } - - [Fact] - public void ExceptionWithTypeProperty_StillContainsType_JustWithDollarAsPrefixInLabel() - { - var exceptionWithTypeProperty = new TypePropertyException() { Type = 13 }; - Test_LoggedExceptionContainsProperty(exceptionWithTypeProperty, "$Type", $"Serilog.Exceptions.Test.Destructurers.{nameof(ReflectionBasedDestructurerTest)}+{nameof(TypePropertyException)}"); - } - - [Fact] - public void WhenObjectContainsCyclicReferences_ThenNoStackoverflowExceptionIsThrown() - { - // Arrange - var exception = new CyclicException - { - MyObject = new MyObject(), - }; - exception.MyObject.Foo = "bar"; - exception.MyObject.Reference = exception.MyObject; - exception.MyObject.Reference2 = exception.MyObject; - - // Act - var result = new ExceptionPropertiesBag(new Exception()); - var destructurer = new ReflectionBasedDestructurer(10); - destructurer.Destructure(exception, result, EmptyDestructurer()); - - // Assert - var myObject = (Dictionary?)result.GetResultDictionary()["MyObject"]; - - Assert.Equal("bar", myObject?["Foo"]); - var reference1 = (Dictionary?)myObject?["Reference"]; - Assert.Equal(myObject?["$id"], reference1?["$ref"]); - var reference2 = (Dictionary?)myObject?["Reference2"]; - Assert.Equal(myObject?["$id"], reference2?["$ref"]); - Assert.Equal("1", myObject?["$id"]); - } - - [Fact] - public void WhenObjectContainsCyclicReferencesInList_ThenRecursiveDestructureIsImmediatelyStopped() - { - // Arrange - var cyclic = new MyObjectCollection - { - Foo = "Cyclic", - }; - cyclic.Reference = cyclic; - var exception = new Cyclic2Exception - { - MyObjectCollection = new MyObjectCollection(), - }; - exception.MyObjectCollection.Foo = "bar"; - exception.MyObjectCollection.Reference = cyclic; - - // Act - var result = new ExceptionPropertiesBag(new Exception()); - var destructurer = new ReflectionBasedDestructurer(10); - destructurer.Destructure(exception, result, EmptyDestructurer()); - - // Assert - var myObject = (List?)result.GetResultDictionary()[nameof(Cyclic2Exception.MyObjectCollection)]; - - // exception.MyObjectCollection[0] is still list - var firstLevelList = Assert.IsType>(myObject?[0]); - - // exception.MyObjectCollection[0][0] we notice that we would again destructure "cyclic" - var secondLevelList = Assert.IsType>(firstLevelList[0]); - Assert.Equal("Cyclic reference", secondLevelList["$ref"]); - } - - [Fact] - public void WhenObjectContainsCyclicReferencesInDict_ThenRecursiveDestructureIsImmediatelyStopped() - { - // Arrange - var cyclic = new MyObjectDict - { - Foo = "Cyclic", - Reference = new Dictionary(), - }; - cyclic.Reference["x"] = cyclic.Reference; - var exception = new CyclicDictException - { - MyObjectDict = cyclic, - }; - - // Act - var result = new ExceptionPropertiesBag(new Exception()); - var destructurer = new ReflectionBasedDestructurer(10); - destructurer.Destructure(exception, result, EmptyDestructurer()); - - // Assert - var myObject = (Dictionary?)result.GetResultDictionary()["MyObjectDict"]; - - // exception.MyObjectDict["Reference"] is still regular dictionary - var firstLevelDict = Assert.IsType>(myObject?["Reference"]); - var id = firstLevelDict["$id"]; - Assert.Equal("1", id); - - // exception.MyObjectDict["Reference"]["x"] we notice that we are destructuring same dictionary - var secondLevelDict = Assert.IsType>(firstLevelDict["x"]); - var refId = Assert.IsType(secondLevelDict["$ref"]); - Assert.Equal(id, refId); - } - - [Fact] - public void WhenObjectContainsCyclicReferencesInTask_ThenRecursiveDestructureIsImmediatelyStopped() - { - // Arrange - var exception = new CyclicTaskException(); - var task = Task.FromException(exception); - exception.Task = task; - - // Act - var result = new ExceptionPropertiesBag(exception); - var destructurer = CreateReflectionBasedDestructurer(); - destructurer.Destructure(exception, result, InnerDestructurer(destructurer)); - - // Assert - var resultsDictionary = result.GetResultDictionary(); - var destructuredTask = resultsDictionary[nameof(CyclicTaskException.Task)].Should().BeAssignableTo>().Which; - var destructuredCyclicException = destructuredTask.Should().ContainKey(nameof(Task.Exception)) - .WhoseValue.Should().BeAssignableTo>() - .Which.Should().ContainKey(nameof(AggregateException.InnerExceptions)) - .WhoseValue.Should().BeAssignableTo>() - .Which.Should().ContainSingle() - .Which.Should().BeAssignableTo>().Which; - destructuredCyclicException.Should().ContainKey(nameof(Exception.Message)) - .WhoseValue.Should().BeOfType() - .Which.Should().Contain(nameof(CyclicTaskException)); - destructuredCyclicException.Should().ContainKey(nameof(CyclicTaskException.Task)) - .WhoseValue.Should().BeAssignableTo>() - .Which.Should().ContainKey("$ref", "task was already destructured, so inner task should just contain ref"); - } - - [Fact] - public void WhenDestruringArgumentException_ResultShouldBeEquivalentToArgumentExceptionDestructurer() - { -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - var exception = ThrowAndCatchException(() => throw new ArgumentException("MESSAGE", "paramName")); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly - Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne(exception, new ArgumentExceptionDestructurer()); - } - - [Fact] - public void CanDestructureObjectWithHiddenProperty() - { - var derived = new DerivedClass - { - HiddenProperty = 123, - }; - var baseClass = (BaseClass)derived; - baseClass.HiddenProperty = 456; - var exception = new HiddenException("test", derived); - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var info = properties[nameof(HiddenException.Info)] as IDictionary; - Assert.Equal(derived.HiddenProperty, info?[nameof(DerivedClass.HiddenProperty)]); - Assert.Equal(baseClass.HiddenProperty, info?[$"{nameof(BaseClass)}.{nameof(BaseClass.HiddenProperty)}"]); - } - - [Fact] - public void CanDestructureObjectWithRedefinedProperty() - { - var exception = new TestExceptionClassWithNewDefinition() { PublicProperty = 20 }; - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var info = properties[nameof(TestExceptionClassWithNewDefinition.PublicProperty)]; - } - - [Fact] - public void CanDestructureObjectWithDataWithRedefinedProperty() - { - var exception = new RecursiveException - { - Node = new RecursiveNodeWithRedefinedProperty - { - Name = 123, - }, - }; - - var propertiesBag = new ExceptionPropertiesBag(exception); - CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); - - var properties = propertiesBag.GetResultDictionary(); - var parent = (IDictionary?)properties[nameof(RecursiveException.Node)]; - Assert.Equal(123, parent?[nameof(RecursiveNode.Name)]); - } - - private static void Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne( - Exception exception, - IExceptionDestructurer customDestructurer) - { - // Arrange - var reflectionBasedResult = new ExceptionPropertiesBag(exception); - var customBasedResult = new ExceptionPropertiesBag(exception); - var reflectionBasedDestructurer = CreateReflectionBasedDestructurer(); - - // Act - reflectionBasedDestructurer.Destructure(exception, reflectionBasedResult, InnerDestructurer(reflectionBasedDestructurer)); - customDestructurer.Destructure(exception, customBasedResult, InnerDestructurer(new ArgumentExceptionDestructurer())); - - // Assert - var reflectionBasedDictionary = (Dictionary)reflectionBasedResult.GetResultDictionary(); - var customBasedDictionary = (Dictionary)customBasedResult.GetResultDictionary(); - - reflectionBasedDictionary.Should().BeEquivalentTo(customBasedDictionary); - } - - private static Func> EmptyDestructurer() => - (ex) => new ExceptionPropertiesBag(ex).GetResultDictionary(); - - private static Func?> InnerDestructurer( - IExceptionDestructurer destructurer) => - (ex) => - { - var resultsBag = new ExceptionPropertiesBag(ex); - - destructurer.Destructure(ex, resultsBag, InnerDestructurer(destructurer)); - - return resultsBag.GetResultDictionary(); - }; - - private static Exception ThrowAndCatchException(Action throwingAction) - { - try - { - throwingAction(); - } -#pragma warning disable CA1031 // Do not catch general exception types - catch (Exception ex) -#pragma warning restore CA1031 // Do not catch general exception types - { - return ex; - } - - Assert.True(false, $"{nameof(throwingAction)} did not throw"); - return null!; // We should never reach this line. - } - - private static ReflectionBasedDestructurer CreateReflectionBasedDestructurer() => - new(10); - - public class MyObject - { - public string? Foo { get; set; } - - public MyObject? Reference { get; set; } - - public MyObject? Reference2 { get; set; } - } - - public class CyclicException : Exception - { - public MyObject? MyObject { get; set; } - } - - public class MyObjectCollection : IEnumerable - { - public string? Foo { get; set; } - - public MyObjectCollection? Reference { get; set; } - - public IEnumerator GetEnumerator() => - new List { this.Reference }.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - } - - public class Cyclic2Exception : Exception - { - public MyObjectCollection? MyObjectCollection { get; set; } - } - - public class CyclicDictException : Exception - { - public MyObjectDict? MyObjectDict { get; set; } - } - - public class CyclicTaskException : Exception - { - public Task? Task { get; set; } - } - - public class MyObjectDict - { - public string? Foo { get; set; } - -#pragma warning disable CA2227 // Collection properties should be read only - public Dictionary? Reference { get; set; } -#pragma warning restore CA2227 // Collection properties should be read only - } - - public class TypePropertyException : Exception - { -#pragma warning disable CA1721 // Property names should not match get methods - public int? Type { get; set; } -#pragma warning restore CA1721 // Property names should not match get methods - } - - public class TestException : Exception - { - public TestException() - : base("MessageValue") - { - StaticProperty = "StaticValue"; - this.PublicProperty = "PublicValue"; - this.InternalProperty = "InternalValue"; - this.ProtectedProperty = "ProtectedValue"; - this.PrivateProperty = "PrivateValue"; - } - - public static string? StaticProperty { get; set; } - - public string PublicProperty { get; set; } - -#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations -#pragma warning disable CA1822 // Member does not access instance data and can be marked as static - public string ExceptionProperty => throw new Exception(); -#pragma warning restore CA1822 // Member does not access instance data and can be marked as static -#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations - - internal string InternalProperty { get; set; } - - protected string ProtectedProperty { get; set; } - -#pragma warning disable IDE0052 // Remove unread private members - private string PrivateProperty { get; set; } -#pragma warning restore IDE0052 // Remove unread private members - -#pragma warning disable CA1822 // Member does not access instance data and can be marked as static - public string this[int i] => "IndexerValue"; -#pragma warning restore CA1822 // Member does not access instance data and can be marked as static - } - - public class UriException : Exception - { - public UriException(string message, Uri uri) - : base(message) => - this.Uri = uri; - - public Uri Uri { get; } - } - - public class TaskException : Exception - { - public TaskException(string message, Task task) - : base(message) => - this.Task = task; - - public Task Task { get; } - } - - public class RecursiveNode - { - public string? Name { get; set; } - - public RecursiveNode? Child { get; set; } - } - - public class RecursiveNodeWithRedefinedProperty : RecursiveNode - { - public new int Name { get; set; } - } - - public class RecursiveException : Exception - { - public RecursiveNode? Node { get; set; } - } - - [Serializable] -#pragma warning disable SA1201 // Elements should appear in the correct order - internal struct TestStruct -#pragma warning restore SA1201 // Elements should appear in the correct order - { - public int ValueType { get; set; } - - public string ReferenceType { get; set; } - } - - [Serializable] - internal class TestClass - { - public int ValueType { get; set; } - - public string? ReferenceType { get; set; } - } - - internal class TestExceptionClassWithNewDefinition : TestException - { - public new int PublicProperty { get; set; } - } - - internal class BaseClass - { - public virtual int HiddenProperty { get; set; } - } - - internal class DerivedClass : BaseClass - { - public new T? HiddenProperty { get; set; } - } - -#pragma warning disable CA1064 // Exceptions should be public - internal class HiddenException : Exception -#pragma warning restore CA1064 // Exceptions should be public - { - public HiddenException(string message, object info) - : base(message) => - this.Info = info; - - public object Info { get; set; } - } -} +namespace Serilog.Exceptions.Test.Destructurers; + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Serilog.Exceptions.Core; +using Serilog.Exceptions.Destructurers; +using Xunit; +using static LogJsonOutputUtils; + +public class ReflectionBasedDestructurerTest +{ + [Fact] + public void DestructureComplexException_EachTypeOfPropertyIsDestructuredAsExpected() + { + // Arrange + var exception = ThrowAndCatchException(() => throw new TestException()); + var propertiesBag = new ExceptionPropertiesBag(exception); + + // Act + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + // Assert + var properties = propertiesBag.GetResultDictionary(); + Assert.Equal("PublicValue", properties[nameof(TestException.PublicProperty)]); + Assert.Equal("threw System.Exception: Exception of type 'System.Exception' was thrown.", properties[nameof(TestException.ExceptionProperty)]); + Assert.DoesNotContain(properties, x => string.Equals(x.Key, "InternalProperty", StringComparison.Ordinal)); + Assert.DoesNotContain(properties, x => string.Equals(x.Key, "ProtectedProperty", StringComparison.Ordinal)); + Assert.DoesNotContain(properties, x => string.Equals(x.Key, "PrivateProperty", StringComparison.Ordinal)); + Assert.Equal("MessageValue", properties[nameof(TestException.Message)]); +#if NET461 || NET472 + Assert.StartsWith("Void DestructureComplexException_EachTypeOfPropertyIsDestructuredAsExpected(", properties[nameof(TestException.TargetSite)].ToString()); +#endif + Assert.NotEmpty(properties[nameof(TestException.StackTrace)]?.ToString()); + Assert.Equal("Serilog.Exceptions.Test", properties[nameof(TestException.Source)]); + Assert.Equal(-2146233088, properties[nameof(TestException.HResult)]); + Assert.Contains(typeof(TestException).FullName, properties["Type"]?.ToString(), StringComparison.Ordinal); + } + + [Fact] + public void CanDestructureUriProperty() + { + const string uriValue = "http://localhost/property"; + var exception = new UriException("test", new Uri(uriValue)); + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var uriPropertyValue = properties[nameof(UriException.Uri)]; + Assert.IsType(uriPropertyValue); + Assert.Equal(uriValue, uriPropertyValue); + } + + [Fact] + public void CanDestructureUriDataItem() + { + const string uriValue = "http://localhost/data-item"; + var exception = new Exception("test") + { + Data = + { + { "UriDataItem", new Uri(uriValue) }, + }, + }; + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var data = (IDictionary?)properties[nameof(Exception.Data)]; + var uriDataValue = data?["UriDataItem"]; + Assert.IsType(uriDataValue); + Assert.Equal(uriValue, uriDataValue); + } + + [Fact] + public async Task CanDestructureTaskAsync() + { + using var cancellationTokenSource = new CancellationTokenSource(0); + + TaskCanceledException exception; + try + { + await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false); + Assert.True(false, "TaskCanceledException was not thrown."); + return; + } + catch (TaskCanceledException taskCancelledException) + { + exception = taskCancelledException; + } + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var destructuredTaskObject = (IDictionary?)properties[nameof(TaskCanceledException.Task)]; + var destructuredTaskProperties = Assert.IsAssignableFrom>(destructuredTaskObject); + destructuredTaskProperties.Should().ContainKey(nameof(Task.Id)); + destructuredTaskProperties.Should().ContainKey(nameof(Task.Status)) + .WhoseValue.Should().BeOfType() + .Which.Should().Be(nameof(TaskStatus.Canceled)); + destructuredTaskProperties.Should().ContainKey(nameof(Task.CreationOptions)) + .WhoseValue.Should().BeOfType() + .Which.Should().Contain(nameof(TaskCreationOptions.None)); + } + + [Fact] + public void CanDestructureFaultedTask() + { + var taskException = new Exception("INNER EXCEPTION MESSAGE"); + var task = Task.FromException(taskException); + var exception = new TaskException("TASK EXCEPTION MESSAGE", task); + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, InnerDestructurer(CreateReflectionBasedDestructurer())); + + var properties = propertiesBag.GetResultDictionary(); + var destructuredTaskObject = (IDictionary?)properties[nameof(TaskCanceledException.Task)]; + var destructuredTaskProperties = Assert.IsAssignableFrom>(destructuredTaskObject); + destructuredTaskProperties.Should().ContainKey(nameof(Task.Id)); + destructuredTaskProperties.Should().ContainKey(nameof(Task.Status)) + .WhoseValue.Should().BeOfType() + .Which.Should().Be(nameof(TaskStatus.Faulted)); + destructuredTaskProperties.Should().ContainKey(nameof(Task.CreationOptions)) + .WhoseValue.Should().BeOfType() + .Which.Should().Be(nameof(TaskCreationOptions.None)); + var taskFirstLevelExceptionDictionary = destructuredTaskProperties.Should().ContainKey(nameof(Task.Exception)) + .WhoseValue.Should().BeAssignableTo>() + .Which; + taskFirstLevelExceptionDictionary.Should().ContainKey("Message") + .WhoseValue.Should().BeOfType() + .Which.Should().Contain("One or more errors occurred.", "task's first level exception is aggregate exception"); + taskFirstLevelExceptionDictionary.Should().ContainKey("InnerExceptions") + .WhoseValue.Should().BeAssignableTo>() + .Which.Should().ContainSingle() + .Which.Should().BeAssignableTo>() + .Which.Should().ContainKey("Message") + .WhoseValue.Should().BeOfType() + .Which.Should().Be("INNER EXCEPTION MESSAGE"); + } + + [Fact] + public void CanDestructureStructDataItem() + { + // Arrange + var exception = new Exception("test"); + exception.Data["data"] = new TestStruct() + { + ValueType = 10, + ReferenceType = "ABC", + }; + var propertiesBag = new ExceptionPropertiesBag(exception); + + // Act + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + // Assert + var properties = propertiesBag.GetResultDictionary(); + var data = (IDictionary?)properties[nameof(Exception.Data)]; + var testStructDataValue = data?["data"]; + Assert.IsAssignableFrom(testStructDataValue); + } + + [Fact] + public void CanDestructureClassDataItem() + { + // Arrange + var exception = new Exception("test"); + exception.Data["data"] = new TestClass() + { + ValueType = 10, + ReferenceType = "ABC", + }; + var propertiesBag = new ExceptionPropertiesBag(exception); + + // Act + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + // Assert + var properties = propertiesBag.GetResultDictionary(); + var data = (IDictionary?)properties[nameof(Exception.Data)]; + var testStructDataValue = data?["data"]; + var destructuredStructDictionary = Assert.IsAssignableFrom>(testStructDataValue); + Assert.Equal(10, destructuredStructDictionary[nameof(TestClass.ValueType)]); + Assert.Equal("ABC", destructuredStructDictionary[nameof(TestClass.ReferenceType)]); + } + + [Fact] + public void DestructuringDepthIsLimitedByConfiguredDepth() + { + // Arrange + var exception = new RecursiveException() + { + Node = new RecursiveNode() + { + Name = "PARENT", + Child = new RecursiveNode() + { + Name = "CHILD 1", + Child = new RecursiveNode() + { + Name = "CHILD 2", + }, + }, + }, + }; + var destructurer = new ReflectionBasedDestructurer(1); + + // Act + var propertiesBag = new ExceptionPropertiesBag(exception); + destructurer.Destructure(exception, propertiesBag, EmptyDestructurer()); + + // Assert + // Parent is depth 1 + // First child is depth 2 + var properties = propertiesBag.GetResultDictionary(); + var parent = (IDictionary?)properties[nameof(RecursiveException.Node)]; + Assert.Equal("PARENT", parent?[nameof(RecursiveNode.Name)]); + Assert.IsType(parent?[nameof(RecursiveNode.Child)]); + } + + [Fact] + public void ExceptionWithTypeProperty_StillContainsType_JustWithDollarAsPrefixInLabel() + { + var exceptionWithTypeProperty = new TypePropertyException() { Type = 13 }; + Test_LoggedExceptionContainsProperty(exceptionWithTypeProperty, "$Type", $"Serilog.Exceptions.Test.Destructurers.{nameof(ReflectionBasedDestructurerTest)}+{nameof(TypePropertyException)}"); + } + + [Fact] + public void WhenObjectContainsCyclicReferences_ThenNoStackoverflowExceptionIsThrown() + { + // Arrange + var exception = new CyclicException + { + MyObject = new MyObject(), + }; + exception.MyObject.Foo = "bar"; + exception.MyObject.Reference = exception.MyObject; + exception.MyObject.Reference2 = exception.MyObject; + + // Act + var result = new ExceptionPropertiesBag(new Exception()); + var destructurer = new ReflectionBasedDestructurer(10); + destructurer.Destructure(exception, result, EmptyDestructurer()); + + // Assert + var myObject = (Dictionary?)result.GetResultDictionary()["MyObject"]; + + Assert.Equal("bar", myObject?["Foo"]); + var reference1 = (Dictionary?)myObject?["Reference"]; + Assert.Equal(myObject?["$id"], reference1?["$ref"]); + var reference2 = (Dictionary?)myObject?["Reference2"]; + Assert.Equal(myObject?["$id"], reference2?["$ref"]); + Assert.Equal("1", myObject?["$id"]); + } + + [Fact] + public void WhenObjectContainsCyclicReferencesInList_ThenRecursiveDestructureIsImmediatelyStopped() + { + // Arrange + var cyclic = new MyObjectCollection + { + Foo = "Cyclic", + }; + cyclic.Reference = cyclic; + var exception = new Cyclic2Exception + { + MyObjectCollection = new MyObjectCollection(), + }; + exception.MyObjectCollection.Foo = "bar"; + exception.MyObjectCollection.Reference = cyclic; + + // Act + var result = new ExceptionPropertiesBag(new Exception()); + var destructurer = new ReflectionBasedDestructurer(10); + destructurer.Destructure(exception, result, EmptyDestructurer()); + + // Assert + var myObject = (List?)result.GetResultDictionary()[nameof(Cyclic2Exception.MyObjectCollection)]; + + // exception.MyObjectCollection[0] is still list + var firstLevelList = Assert.IsType>(myObject?[0]); + + // exception.MyObjectCollection[0][0] we notice that we would again destructure "cyclic" + var secondLevelList = Assert.IsType>(firstLevelList[0]); + Assert.Equal("Cyclic reference", secondLevelList["$ref"]); + } + + [Fact] + public void WhenObjectContainsCyclicReferencesInDict_ThenRecursiveDestructureIsImmediatelyStopped() + { + // Arrange + var cyclic = new MyObjectDict + { + Foo = "Cyclic", + Reference = new Dictionary(), + }; + cyclic.Reference["x"] = cyclic.Reference; + var exception = new CyclicDictException + { + MyObjectDict = cyclic, + }; + + // Act + var result = new ExceptionPropertiesBag(new Exception()); + var destructurer = new ReflectionBasedDestructurer(10); + destructurer.Destructure(exception, result, EmptyDestructurer()); + + // Assert + var myObject = (Dictionary?)result.GetResultDictionary()["MyObjectDict"]; + + // exception.MyObjectDict["Reference"] is still regular dictionary + var firstLevelDict = Assert.IsType>(myObject?["Reference"]); + var id = firstLevelDict["$id"]; + Assert.Equal("1", id); + + // exception.MyObjectDict["Reference"]["x"] we notice that we are destructuring same dictionary + var secondLevelDict = Assert.IsType>(firstLevelDict["x"]); + var refId = Assert.IsType(secondLevelDict["$ref"]); + Assert.Equal(id, refId); + } + + [Fact] + public void WhenObjectContainsCyclicReferencesInTask_ThenRecursiveDestructureIsImmediatelyStopped() + { + // Arrange + var exception = new CyclicTaskException(); + var task = Task.FromException(exception); + exception.Task = task; + + // Act + var result = new ExceptionPropertiesBag(exception); + var destructurer = CreateReflectionBasedDestructurer(); + destructurer.Destructure(exception, result, InnerDestructurer(destructurer)); + + // Assert + var resultsDictionary = result.GetResultDictionary(); + var destructuredTask = resultsDictionary[nameof(CyclicTaskException.Task)].Should().BeAssignableTo>().Which; + var destructuredCyclicException = destructuredTask.Should().ContainKey(nameof(Task.Exception)) + .WhoseValue.Should().BeAssignableTo>() + .Which.Should().ContainKey(nameof(AggregateException.InnerExceptions)) + .WhoseValue.Should().BeAssignableTo>() + .Which.Should().ContainSingle() + .Which.Should().BeAssignableTo>().Which; + destructuredCyclicException.Should().ContainKey(nameof(Exception.Message)) + .WhoseValue.Should().BeOfType() + .Which.Should().Contain(nameof(CyclicTaskException)); + destructuredCyclicException.Should().ContainKey(nameof(CyclicTaskException.Task)) + .WhoseValue.Should().BeAssignableTo>() + .Which.Should().ContainKey("$ref", "task was already destructured, so inner task should just contain ref"); + } + + [Fact] + public void WhenDestruringArgumentException_ResultShouldBeEquivalentToArgumentExceptionDestructurer() + { +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + var exception = ThrowAndCatchException(() => throw new ArgumentException("MESSAGE", "paramName")); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne(exception, new ArgumentExceptionDestructurer()); + } + + [Fact] + public void CanDestructureObjectWithHiddenProperty() + { + var derived = new DerivedClass + { + HiddenProperty = 123, + }; + var baseClass = (BaseClass)derived; + baseClass.HiddenProperty = 456; + var exception = new HiddenException("test", derived); + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var info = properties[nameof(HiddenException.Info)] as IDictionary; + Assert.Equal(derived.HiddenProperty, info?[nameof(DerivedClass.HiddenProperty)]); + Assert.Equal(baseClass.HiddenProperty, info?[$"{nameof(BaseClass)}.{nameof(BaseClass.HiddenProperty)}"]); + } + + [Fact] + public void CanDestructureObjectWithRedefinedProperty() + { + var exception = new TestExceptionClassWithNewDefinition() { PublicProperty = 20 }; + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var info = properties[nameof(TestExceptionClassWithNewDefinition.PublicProperty)]; + } + + [Fact] + public void CanDestructureObjectWithDataWithRedefinedProperty() + { + var exception = new RecursiveException + { + Node = new RecursiveNodeWithRedefinedProperty + { + Name = 123, + }, + }; + + var propertiesBag = new ExceptionPropertiesBag(exception); + CreateReflectionBasedDestructurer().Destructure(exception, propertiesBag, EmptyDestructurer()); + + var properties = propertiesBag.GetResultDictionary(); + var parent = (IDictionary?)properties[nameof(RecursiveException.Node)]; + Assert.Equal(123, parent?[nameof(RecursiveNode.Name)]); + } + + private static void Test_ResultOfReflectionDestructurerShouldBeEquivalentToCustomOne( + Exception exception, + IExceptionDestructurer customDestructurer) + { + // Arrange + var reflectionBasedResult = new ExceptionPropertiesBag(exception); + var customBasedResult = new ExceptionPropertiesBag(exception); + var reflectionBasedDestructurer = CreateReflectionBasedDestructurer(); + + // Act + reflectionBasedDestructurer.Destructure(exception, reflectionBasedResult, InnerDestructurer(reflectionBasedDestructurer)); + customDestructurer.Destructure(exception, customBasedResult, InnerDestructurer(new ArgumentExceptionDestructurer())); + + // Assert + var reflectionBasedDictionary = (Dictionary)reflectionBasedResult.GetResultDictionary(); + var customBasedDictionary = (Dictionary)customBasedResult.GetResultDictionary(); + + reflectionBasedDictionary.Should().BeEquivalentTo(customBasedDictionary); + } + + private static Func> EmptyDestructurer() => + (ex) => new ExceptionPropertiesBag(ex).GetResultDictionary(); + + private static Func?> InnerDestructurer( + IExceptionDestructurer destructurer) => + (ex) => + { + var resultsBag = new ExceptionPropertiesBag(ex); + + destructurer.Destructure(ex, resultsBag, InnerDestructurer(destructurer)); + + return resultsBag.GetResultDictionary(); + }; + + private static Exception ThrowAndCatchException(Action throwingAction) + { + try + { + throwingAction(); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return ex; + } + + Assert.True(false, $"{nameof(throwingAction)} did not throw"); + return null!; // We should never reach this line. + } + + private static ReflectionBasedDestructurer CreateReflectionBasedDestructurer() => + new(10); + + public class MyObject + { + public string? Foo { get; set; } + + public MyObject? Reference { get; set; } + + public MyObject? Reference2 { get; set; } + } + + public class CyclicException : Exception + { + public MyObject? MyObject { get; set; } + } + + public class MyObjectCollection : IEnumerable + { + public string? Foo { get; set; } + + public MyObjectCollection? Reference { get; set; } + + public IEnumerator GetEnumerator() => + new List { this.Reference }.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } + + public class Cyclic2Exception : Exception + { + public MyObjectCollection? MyObjectCollection { get; set; } + } + + public class CyclicDictException : Exception + { + public MyObjectDict? MyObjectDict { get; set; } + } + + public class CyclicTaskException : Exception + { + public Task? Task { get; set; } + } + + public class MyObjectDict + { + public string? Foo { get; set; } + +#pragma warning disable CA2227 // Collection properties should be read only + public Dictionary? Reference { get; set; } +#pragma warning restore CA2227 // Collection properties should be read only + } + + public class TypePropertyException : Exception + { +#pragma warning disable CA1721 // Property names should not match get methods + public int? Type { get; set; } +#pragma warning restore CA1721 // Property names should not match get methods + } + + public class TestException : Exception + { + public TestException() + : base("MessageValue") + { + StaticProperty = "StaticValue"; + this.PublicProperty = "PublicValue"; + this.InternalProperty = "InternalValue"; + this.ProtectedProperty = "ProtectedValue"; + this.PrivateProperty = "PrivateValue"; + } + + public static string? StaticProperty { get; set; } + + public string PublicProperty { get; set; } + +#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations +#pragma warning disable CA1822 // Member does not access instance data and can be marked as static + public string ExceptionProperty => throw new Exception(); +#pragma warning restore CA1822 // Member does not access instance data and can be marked as static +#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations + + internal string InternalProperty { get; set; } + + protected string ProtectedProperty { get; set; } + +#pragma warning disable IDE0052 // Remove unread private members + private string PrivateProperty { get; set; } +#pragma warning restore IDE0052 // Remove unread private members + +#pragma warning disable CA1822 // Member does not access instance data and can be marked as static + public string this[int i] => "IndexerValue"; +#pragma warning restore CA1822 // Member does not access instance data and can be marked as static + } + + public class UriException : Exception + { + public UriException(string message, Uri uri) + : base(message) => + this.Uri = uri; + + public Uri Uri { get; } + } + + public class TaskException : Exception + { + public TaskException(string message, Task task) + : base(message) => + this.Task = task; + + public Task Task { get; } + } + + public class RecursiveNode + { + public string? Name { get; set; } + + public RecursiveNode? Child { get; set; } + } + + public class RecursiveNodeWithRedefinedProperty : RecursiveNode + { + public new int Name { get; set; } + } + + public class RecursiveException : Exception + { + public RecursiveNode? Node { get; set; } + } + + [Serializable] +#pragma warning disable SA1201 // Elements should appear in the correct order + internal struct TestStruct +#pragma warning restore SA1201 // Elements should appear in the correct order + { + public int ValueType { get; set; } + + public string ReferenceType { get; set; } + } + + [Serializable] + internal class TestClass + { + public int ValueType { get; set; } + + public string? ReferenceType { get; set; } + } + + internal class TestExceptionClassWithNewDefinition : TestException + { + public new int PublicProperty { get; set; } + } + + internal class BaseClass + { + public virtual int HiddenProperty { get; set; } + } + + internal class DerivedClass : BaseClass + { + public new T? HiddenProperty { get; set; } + } + +#pragma warning disable CA1064 // Exceptions should be public + internal class HiddenException : Exception +#pragma warning restore CA1064 // Exceptions should be public + { + public HiddenException(string message, object info) + : base(message) => + this.Info = info; + + public object Info { get; set; } + } +} diff --git a/Tests/Serilog.Exceptions.Test/Destructurers/TaskCanceledExceptionDestructurerTest.cs b/Tests/Serilog.Exceptions.Test/Destructurers/TaskCanceledExceptionDestructurerTest.cs index 2542cdc6..83f479ab 100644 --- a/Tests/Serilog.Exceptions.Test/Destructurers/TaskCanceledExceptionDestructurerTest.cs +++ b/Tests/Serilog.Exceptions.Test/Destructurers/TaskCanceledExceptionDestructurerTest.cs @@ -1,98 +1,98 @@ -namespace Serilog.Exceptions.Test.Destructurers; - -using System; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Newtonsoft.Json.Linq; -using Xunit; -using static LogJsonOutputUtils; - -public sealed class TaskCanceledExceptionDestructurerTest : IDisposable -{ - private readonly CancellationTokenSource cancellationTokenSource = new(); - - [Fact] - public void TaskCanceledException_SimplePropertiesAreAttached() - { - // Arrange - this.cancellationTokenSource.Cancel(); - var task = Task.FromCanceled(this.cancellationTokenSource.Token); - - // Act - var ex = new TaskCanceledException(task); - - // Assert - var tce = ex.Should().BeOfType().Which; - var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); - Assert_ContainsPropertyWithValue(exceptionDetails, nameof(TaskCanceledException.CancellationToken), "CancellationRequested: true"); - - var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); - var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; - Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.Status), nameof(TaskStatus.Canceled)); - Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), nameof(TaskCreationOptions.None)); - } - - [Fact] - public void TaskCanceledException_TaskWithSomeCreationOptions_TheyAreDestructured() - { - // Arrange - var task = new Task(() => { }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); - - // Act - var ex = new TaskCanceledException(task); - - // Assert - var tce = ex.Should().BeOfType().Which; - var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); - var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); - var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; - Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), $"{nameof(TaskCreationOptions.PreferFairness)}, {nameof(TaskCreationOptions.LongRunning)}"); - } - - [Fact] - public void TaskCanceledException_TaskNull() - { - // Arrange - - // Act - var ex = new TaskCanceledException(); - - // Assert - var tce = ex.Should().BeOfType().Which; - var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); - Assert_ContainsPropertyWithValue(exceptionDetails, nameof(TaskCanceledException.Task), null); - } - - [Fact] - public void FaultedTaskCanceledException_SimplePropertiesAreAttached() - { - // Arrange - var innerException = new Exception("Inner exception message"); - var task = Task.FromException(innerException); - var ex = new TaskCanceledException(task); - - // Act - var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(ex)); - - // Assert - Assert_ContainsPropertyWithValue(exceptionDetails, "CancellationToken", "CancellationRequested: false"); - - var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); - var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; - Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.Status), nameof(TaskStatus.Faulted)); - Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), nameof(TaskCreationOptions.None)); - var taskException = ExtractProperty(taskPropertyObject, nameof(Task.Exception)); - var taskExceptionObject = taskException.Should().BeOfType() - .Which.Value.Should().BeOfType() - .Which; - - var typeOfTaskException = ExtractProperty(taskExceptionObject, "Type"); - typeOfTaskException.Should().BeOfType() - .Which.Value.Should().BeOfType() - .Which.Value.Should().BeOfType() - .Which.Should().Be("System.AggregateException"); - } - - public void Dispose() => this.cancellationTokenSource?.Dispose(); -} +namespace Serilog.Exceptions.Test.Destructurers; + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Newtonsoft.Json.Linq; +using Xunit; +using static LogJsonOutputUtils; + +public sealed class TaskCanceledExceptionDestructurerTest : IDisposable +{ + private readonly CancellationTokenSource cancellationTokenSource = new(); + + [Fact] + public void TaskCanceledException_SimplePropertiesAreAttached() + { + // Arrange + this.cancellationTokenSource.Cancel(); + var task = Task.FromCanceled(this.cancellationTokenSource.Token); + + // Act + var ex = new TaskCanceledException(task); + + // Assert + var tce = ex.Should().BeOfType().Which; + var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); + Assert_ContainsPropertyWithValue(exceptionDetails, nameof(TaskCanceledException.CancellationToken), "CancellationRequested: true"); + + var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); + var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; + Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.Status), nameof(TaskStatus.Canceled)); + Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), nameof(TaskCreationOptions.None)); + } + + [Fact] + public void TaskCanceledException_TaskWithSomeCreationOptions_TheyAreDestructured() + { + // Arrange + var task = new Task(() => { }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); + + // Act + var ex = new TaskCanceledException(task); + + // Assert + var tce = ex.Should().BeOfType().Which; + var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); + var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); + var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; + Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), $"{nameof(TaskCreationOptions.PreferFairness)}, {nameof(TaskCreationOptions.LongRunning)}"); + } + + [Fact] + public void TaskCanceledException_TaskNull() + { + // Arrange + + // Act + var ex = new TaskCanceledException(); + + // Assert + var tce = ex.Should().BeOfType().Which; + var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(tce)); + Assert_ContainsPropertyWithValue(exceptionDetails, nameof(TaskCanceledException.Task), null); + } + + [Fact] + public void FaultedTaskCanceledException_SimplePropertiesAreAttached() + { + // Arrange + var innerException = new Exception("Inner exception message"); + var task = Task.FromException(innerException); + var ex = new TaskCanceledException(task); + + // Act + var exceptionDetails = ExtractExceptionDetails(LogAndDestructureException(ex)); + + // Assert + Assert_ContainsPropertyWithValue(exceptionDetails, "CancellationToken", "CancellationRequested: false"); + + var taskProperty = ExtractProperty(exceptionDetails, nameof(TaskCanceledException.Task)); + var taskPropertyObject = taskProperty.Value.Should().BeOfType().Which; + Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.Status), nameof(TaskStatus.Faulted)); + Assert_ContainsPropertyWithValue(taskPropertyObject, nameof(Task.CreationOptions), nameof(TaskCreationOptions.None)); + var taskException = ExtractProperty(taskPropertyObject, nameof(Task.Exception)); + var taskExceptionObject = taskException.Should().BeOfType() + .Which.Value.Should().BeOfType() + .Which; + + var typeOfTaskException = ExtractProperty(taskExceptionObject, "Type"); + typeOfTaskException.Should().BeOfType() + .Which.Value.Should().BeOfType() + .Which.Value.Should().BeOfType() + .Which.Should().Be("System.AggregateException"); + } + + public void Dispose() => this.cancellationTokenSource?.Dispose(); +} diff --git a/Tools/ExceptionFinderTool/ExceptionFinderTool.csproj b/Tools/ExceptionFinderTool/ExceptionFinderTool.csproj index a2e86edc..917a6733 100644 --- a/Tools/ExceptionFinderTool/ExceptionFinderTool.csproj +++ b/Tools/ExceptionFinderTool/ExceptionFinderTool.csproj @@ -1,13 +1,13 @@ - - - - Exe - net6.0;net5.0;netstandard2.1;netstandard2.0;net472;net461 - false - - - - - - - + + + + Exe + net6.0;net5.0;netstandard2.1;netstandard2.0;net472;net461 + false + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index 3217ee23..4c7e7947 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,53 +1,53 @@ -# AppVeyor documentation https://www.appveyor.com/docs - -version: "{build}" - -image: - - macOS - - Ubuntu - - Visual Studio 2022 - -environment: - # Disable the .NET logo in the console output. - DOTNET_NOLOGO: true - # Disable the .NET first time experience to skip caching NuGet packages and speed up the build. - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - # Disable sending .NET CLI telemetry to Microsoft. - DOTNET_CLI_TELEMETRY_OPTOUT: true - # Set the build number in MinVer. - MINVERBUILDMETADATA: build.%APPVEYOR_BUILD_NUMBER% - -build_script: - # https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script - - pwsh: | - if ($isWindows) { - Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" - ./dotnet-install.ps1 -JSonFile global.json -InstallDir 'C:\Program Files\dotnet' - } - else { - Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh" - sudo chmod u+x dotnet-install.sh - if ($isMacOS) { - sudo ./dotnet-install.sh --jsonfile global.json --install-dir '/usr/local/share/dotnet' - } else { - sudo ./dotnet-install.sh --jsonfile global.json --install-dir '/usr/share/dotnet' - } - } - - pwsh: dotnet tool restore - - pwsh: dotnet cake --target=Build - - pwsh: dotnet cake --target=Test - - pwsh: dotnet cake --target=Pack - -test: off - -artifacts: - - name: NuGet Packages - path: ./Artefacts/**/*.nupkg - - name: NuGet Symbol Packages - path: ./Artefacts/**/*.snupkg - - name: xUnit XML Test Results - path: ./Artefacts/**/*.xml - - name: xUnit HTML Test Results - path: ./Artefacts/**/*.html - - name: Code Coverage - path: ./Artefacts/**/*.cobertura.xml +# AppVeyor documentation https://www.appveyor.com/docs + +version: "{build}" + +image: + - macOS + - Ubuntu + - Visual Studio 2022 + +environment: + # Disable the .NET logo in the console output. + DOTNET_NOLOGO: true + # Disable the .NET first time experience to skip caching NuGet packages and speed up the build. + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + # Disable sending .NET CLI telemetry to Microsoft. + DOTNET_CLI_TELEMETRY_OPTOUT: true + # Set the build number in MinVer. + MINVERBUILDMETADATA: build.%APPVEYOR_BUILD_NUMBER% + +build_script: + # https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script + - pwsh: | + if ($isWindows) { + Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" + ./dotnet-install.ps1 -JSonFile global.json -InstallDir 'C:\Program Files\dotnet' + } + else { + Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh" + sudo chmod u+x dotnet-install.sh + if ($isMacOS) { + sudo ./dotnet-install.sh --jsonfile global.json --install-dir '/usr/local/share/dotnet' + } else { + sudo ./dotnet-install.sh --jsonfile global.json --install-dir '/usr/share/dotnet' + } + } + - pwsh: dotnet tool restore + - pwsh: dotnet cake --target=Build + - pwsh: dotnet cake --target=Test + - pwsh: dotnet cake --target=Pack + +test: off + +artifacts: + - name: NuGet Packages + path: ./Artefacts/**/*.nupkg + - name: NuGet Symbol Packages + path: ./Artefacts/**/*.snupkg + - name: xUnit XML Test Results + path: ./Artefacts/**/*.xml + - name: xUnit HTML Test Results + path: ./Artefacts/**/*.html + - name: Code Coverage + path: ./Artefacts/**/*.cobertura.xml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0294eaf6..a50a47b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,103 +1,103 @@ -# Azure Pipelines documentation https://aka.ms/yaml - -trigger: - branches: - include: - - "main" - tags: - include: - - "*" -pr: - branches: - include: - - "*" - -variables: - # Disable the .NET logo in the console output. - DOTNET_NOLOGO: true - # Disable the .NET first time experience to skip caching NuGet packages and speed up the build. - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - # Disable sending .NET CLI telemetry to Microsoft. - DOTNET_CLI_TELEMETRY_OPTOUT: true - # Set the build number in MinVer. - MINVERBUILDMETADATA: build.$(Build.BuildId) - -stages: - - stage: Build - jobs: - - job: Build - strategy: - matrix: - Linux: - matrixName: Ubuntu - vmImageName: ubuntu-latest - Mac: - matrixName: Mac - vmImageName: macos-latest - Windows: - matrixName: Windows - vmImageName: windows-latest - pool: - vmImage: $(vmImageName) - timeoutInMinutes: 10 - steps: - - checkout: self - lfs: true - - task: UseDotNet@2 - displayName: "Install .NET Core 3.1 SDK" - inputs: - packageType: "sdk" - version: 3.1.x - - task: UseDotNet@2 - displayName: "Install .NET Core 5.0 SDK" - inputs: - packageType: "sdk" - version: 5.0.x - - task: UseDotNet@2 - displayName: "Install .NET Core SDK" - inputs: - packageType: "sdk" - useGlobalJson: true - - pwsh: "dotnet tool restore" - displayName: "Dotnet Tool Restore" - failOnStderr: true - - pwsh: "dotnet cake --target=Build" - displayName: "Dotnet Cake Build" - failOnStderr: true - - pwsh: "dotnet cake --target=Test" - displayName: "Dotnet Cake Test" - failOnStderr: true - - pwsh: "dotnet cake --target=Pack" - displayName: "Dotnet Cake Pack" - failOnStderr: true - - task: PublishTestResults@2 - displayName: "Publish Test Results" - inputs: - testResultsFormat: "VSTest" - testResultsFiles: "**/*.trx" - - task: PublishCodeCoverageResults@1 - inputs: - codeCoverageTool: cobertura - summaryFileLocation: "**/*.cobertura.xml" - - publish: "./Artefacts" - artifact: $(matrixName) - displayName: "Publish Artefacts" - - stage: Deploy - jobs: - - deployment: AzureArtefacts - displayName: "Azure Artefacts" - condition: ne(variables['Build.Reason'], 'PullRequest') - pool: - vmImage: windows-latest - environment: "Azure Artefacts" - strategy: - runOnce: - deploy: - steps: - - task: NuGetToolInstaller@1 - displayName: "NuGet Install" - - task: NuGetAuthenticate@0 - displayName: "NuGet Authenticate" - - pwsh: nuget push $(Agent.BuildDirectory)\Windows\*.nupkg -Source https://pkgs.dev.azure.com/serilog-exceptions/_packaging/serilog-exceptions/nuget/v3/index.json -ApiKey AzureArtifacts -SkipDuplicate - displayName: "NuGet Push" - failOnStderr: true +# Azure Pipelines documentation https://aka.ms/yaml + +trigger: + branches: + include: + - "main" + tags: + include: + - "*" +pr: + branches: + include: + - "*" + +variables: + # Disable the .NET logo in the console output. + DOTNET_NOLOGO: true + # Disable the .NET first time experience to skip caching NuGet packages and speed up the build. + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + # Disable sending .NET CLI telemetry to Microsoft. + DOTNET_CLI_TELEMETRY_OPTOUT: true + # Set the build number in MinVer. + MINVERBUILDMETADATA: build.$(Build.BuildId) + +stages: + - stage: Build + jobs: + - job: Build + strategy: + matrix: + Linux: + matrixName: Ubuntu + vmImageName: ubuntu-latest + Mac: + matrixName: Mac + vmImageName: macos-latest + Windows: + matrixName: Windows + vmImageName: windows-latest + pool: + vmImage: $(vmImageName) + timeoutInMinutes: 10 + steps: + - checkout: self + lfs: true + - task: UseDotNet@2 + displayName: "Install .NET Core 3.1 SDK" + inputs: + packageType: "sdk" + version: 3.1.x + - task: UseDotNet@2 + displayName: "Install .NET Core 5.0 SDK" + inputs: + packageType: "sdk" + version: 5.0.x + - task: UseDotNet@2 + displayName: "Install .NET Core SDK" + inputs: + packageType: "sdk" + useGlobalJson: true + - pwsh: "dotnet tool restore" + displayName: "Dotnet Tool Restore" + failOnStderr: true + - pwsh: "dotnet cake --target=Build" + displayName: "Dotnet Cake Build" + failOnStderr: true + - pwsh: "dotnet cake --target=Test" + displayName: "Dotnet Cake Test" + failOnStderr: true + - pwsh: "dotnet cake --target=Pack" + displayName: "Dotnet Cake Pack" + failOnStderr: true + - task: PublishTestResults@2 + displayName: "Publish Test Results" + inputs: + testResultsFormat: "VSTest" + testResultsFiles: "**/*.trx" + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: cobertura + summaryFileLocation: "**/*.cobertura.xml" + - publish: "./Artefacts" + artifact: $(matrixName) + displayName: "Publish Artefacts" + - stage: Deploy + jobs: + - deployment: AzureArtefacts + displayName: "Azure Artefacts" + condition: ne(variables['Build.Reason'], 'PullRequest') + pool: + vmImage: windows-latest + environment: "Azure Artefacts" + strategy: + runOnce: + deploy: + steps: + - task: NuGetToolInstaller@1 + displayName: "NuGet Install" + - task: NuGetAuthenticate@0 + displayName: "NuGet Authenticate" + - pwsh: nuget push $(Agent.BuildDirectory)\Windows\*.nupkg -Source https://pkgs.dev.azure.com/serilog-exceptions/_packaging/serilog-exceptions/nuget/v3/index.json -ApiKey AzureArtifacts -SkipDuplicate + displayName: "NuGet Push" + failOnStderr: true diff --git a/build.cake b/build.cake index d421f7f2..049d302c 100644 --- a/build.cake +++ b/build.cake @@ -1,88 +1,88 @@ -var target = Argument("Target", "Default"); -var configuration = - HasArgument("Configuration") ? Argument("Configuration") : - EnvironmentVariable("Configuration", "Release"); - -var artefactsDirectory = Directory("./Artefacts"); - -Task("Clean") - .Description("Cleans the artefacts, bin and obj directories.") - .Does(() => - { - CleanDirectory(artefactsDirectory); - DeleteDirectories(GetDirectories("**/bin"), new DeleteDirectorySettings() { Force = true, Recursive = true }); - DeleteDirectories(GetDirectories("**/obj"), new DeleteDirectorySettings() { Force = true, Recursive = true }); - }); - -Task("Restore") - .Description("Restores NuGet packages.") - .IsDependentOn("Clean") - .Does(() => - { - DotNetRestore(); - }); - -Task("Build") - .Description("Builds the solution.") - .IsDependentOn("Restore") - .Does(() => - { - DotNetBuild( - ".", - new DotNetBuildSettings() - { - Configuration = configuration, - NoRestore = true, - }); - }); - -Task("Test") - .Description("Runs unit tests and outputs test results to the artefacts directory.") - .DoesForEach(GetFiles("./Tests/**/*.csproj"), project => - { - DotNetTest( - project.ToString(), - new DotNetTestSettings() - { - Blame = true, - Collectors = new string[] { "Code Coverage", "XPlat Code Coverage" }, - Configuration = configuration, - Loggers = new string[] - { - $"trx;LogFileName={project.GetFilenameWithoutExtension()}.trx", - $"junit;LogFileName={project.GetFilenameWithoutExtension()}.xml", - $"html;LogFileName={project.GetFilenameWithoutExtension()}.html", - }, - NoBuild = true, - NoRestore = true, - ResultsDirectory = artefactsDirectory, - }); - }); - -Task("Pack") - .Description("Creates NuGet packages and outputs them to the artefacts directory.") - .Does(() => - { - DotNetPack( - ".", - new DotNetPackSettings() - { - Configuration = configuration, - IncludeSymbols = true, - MSBuildSettings = new DotNetMSBuildSettings() - { - ContinuousIntegrationBuild = !BuildSystem.IsLocalBuild, - }, - NoBuild = true, - NoRestore = true, - OutputDirectory = artefactsDirectory, - }); - }); - -Task("Default") - .Description("Cleans, restores NuGet packages, builds the solution, runs unit tests and then creates NuGet packages.") - .IsDependentOn("Build") - .IsDependentOn("Test") - .IsDependentOn("Pack"); - -RunTarget(target); +var target = Argument("Target", "Default"); +var configuration = + HasArgument("Configuration") ? Argument("Configuration") : + EnvironmentVariable("Configuration", "Release"); + +var artefactsDirectory = Directory("./Artefacts"); + +Task("Clean") + .Description("Cleans the artefacts, bin and obj directories.") + .Does(() => + { + CleanDirectory(artefactsDirectory); + DeleteDirectories(GetDirectories("**/bin"), new DeleteDirectorySettings() { Force = true, Recursive = true }); + DeleteDirectories(GetDirectories("**/obj"), new DeleteDirectorySettings() { Force = true, Recursive = true }); + }); + +Task("Restore") + .Description("Restores NuGet packages.") + .IsDependentOn("Clean") + .Does(() => + { + DotNetRestore(); + }); + +Task("Build") + .Description("Builds the solution.") + .IsDependentOn("Restore") + .Does(() => + { + DotNetBuild( + ".", + new DotNetBuildSettings() + { + Configuration = configuration, + NoRestore = true, + }); + }); + +Task("Test") + .Description("Runs unit tests and outputs test results to the artefacts directory.") + .DoesForEach(GetFiles("./Tests/**/*.csproj"), project => + { + DotNetTest( + project.ToString(), + new DotNetTestSettings() + { + Blame = true, + Collectors = new string[] { "Code Coverage", "XPlat Code Coverage" }, + Configuration = configuration, + Loggers = new string[] + { + $"trx;LogFileName={project.GetFilenameWithoutExtension()}.trx", + $"junit;LogFileName={project.GetFilenameWithoutExtension()}.xml", + $"html;LogFileName={project.GetFilenameWithoutExtension()}.html", + }, + NoBuild = true, + NoRestore = true, + ResultsDirectory = artefactsDirectory, + }); + }); + +Task("Pack") + .Description("Creates NuGet packages and outputs them to the artefacts directory.") + .Does(() => + { + DotNetPack( + ".", + new DotNetPackSettings() + { + Configuration = configuration, + IncludeSymbols = true, + MSBuildSettings = new DotNetMSBuildSettings() + { + ContinuousIntegrationBuild = !BuildSystem.IsLocalBuild, + }, + NoBuild = true, + NoRestore = true, + OutputDirectory = artefactsDirectory, + }); + }); + +Task("Default") + .Description("Cleans, restores NuGet packages, builds the solution, runs unit tests and then creates NuGet packages.") + .IsDependentOn("Build") + .IsDependentOn("Test") + .IsDependentOn("Pack"); + +RunTarget(target);