diff --git a/.gitignore b/.gitignore index 8ff53697f..bcaad9494 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ bld/ [Bb]in/ [Oo]bj/ BenchmarkDotNet.Artifacts/ +.dotnetcli/ # Visual Studio 2015 cache/options directory .vs/ diff --git a/.travis.yml b/.travis.yml index 562d018d5..3f7297646 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,11 @@ matrix: - os: linux # Ubuntu 14.04 dist: trusty sudo: required - dotnet: 1.0.0-preview2-003121 - - os: osx # OSX 10.11 - osx_image: xcode7.2 - dotnet: 1.0.0-preview2-003121 + dotnet: 1.0.4 + # Disabled temporarily due to Travis OSX issues + # - os: osx # OSX 10.11 + # osx_image: xcode7.3 + # dotnet: 1.0.1 script: - ./build.sh \ No newline at end of file diff --git a/Build.ps1 b/Build.ps1 index 55db40f54..648b06823 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -24,30 +24,30 @@ foreach ($src in ls src/*) { echo "build: Packaging project in $src" & dotnet build -c Release --version-suffix=$buildSuffix - & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build + & dotnet pack -c Release --include-symbols -o ..\..\artifacts --version-suffix=$suffix --no-build if($LASTEXITCODE -ne 0) { exit 1 } Pop-Location } -foreach ($test in ls test/*.PerformanceTests) { +foreach ($test in ls test/*.Tests) { Push-Location $test - echo "build: Building performance test project in $test" + echo "build: Testing project in $test" - & dotnet build -c Release - if($LASTEXITCODE -ne 0) { exit 2 } + & dotnet test -c Release + if($LASTEXITCODE -ne 0) { exit 3 } Pop-Location } -foreach ($test in ls test/*.Tests) { +foreach ($test in ls test/*.PerformanceTests) { Push-Location $test - echo "build: Testing project in $test" + echo "build: Building performance test project in $test" - & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } + & dotnet build -c Release + if($LASTEXITCODE -ne 0) { exit 2 } Pop-Location } diff --git a/CHANGES.md b/CHANGES.md index 26b75dfd7..896534e27 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,14 @@ +2.4.0 +* #866 and #877- additional event payload limiting controls +* #833 - improve performance of message template cache lookup +* #885 - fix JSON formatting of `NaN` and infinity values +* #888 - allow minimum level overrides to be specified by configuration providers like _Serilog.Settings.AppSettings_ +* #903 - add further `Log` static methods to match `ILogger` methods +* #907 - properly dispose audit sinks +* #913 - include commit hash in `AssemblyInformationalVersion` +* #925 - allow configuration providers to specify `filter` directives +* Build and test coverage work in #821, #824, #896. + 2.3.0 * #870 - fix dispose for level-restricted sinks * #852 - fix dictionary capturing when key/value are anonymous types diff --git a/README.md b/README.md index 8db74f24a..71d8a07d1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) +# Serilog [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.svg?style=flat)](https://www.nuget.org/packages/Serilog/) [![Rager Releases](http://rager.io/badge.svg?url=https%3A%2F%2Fwww.nuget.org%2Fpackages%2FSerilog%2F)](http://rager.io/projects/search?badge=1&query=nuget.org/packages/Serilog/) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) Serilog is a diagnostic logging library for .NET applications. It is easy to set up, has a clean API, and runs on all recent .NET platforms. While it's useful even in the simplest applications, Serilog's support for structured logging shines when instrumenting complex, distributed, and asynchronous applications and systems. @@ -107,4 +107,4 @@ Branch | AppVeyor | Travis dev | [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/dev?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/dev) | [![Build Status](https://travis-ci.org/serilog/serilog.svg?branch=dev)](https://travis-ci.org/serilog/serilog) master | [![Build status](https://ci.appveyor.com/api/projects/status/b9rm3l7kduryjgcj/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog/branch/master) | [![Build Status](https://travis-ci.org/serilog/serilog.svg?branch=master)](https://travis-ci.org/serilog/serilog) -_Serilog is copyright © 2013-2016 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html). Needle and thread logo a derivative of work by [Kenneth Appiah](http://www.kensets.com/)._ +_Serilog is copyright © 2013-2017 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html). Needle and thread logo a derivative of work by [Kenneth Appiah](http://www.kensets.com/)._ diff --git a/Serilog.sln b/Serilog.sln index 32d2b0d9b..be7104f64 100644 --- a/Serilog.sln +++ b/Serilog.sln @@ -1,15 +1,11 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D135C0C-A60B-454A-A2F4-CD74A30E04B0}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5E1-DEB9-4A04-8BAB-24EC7240ADAF}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .travis.yml = .travis.yml appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 build.sh = build.sh @@ -22,44 +18,84 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5 assets\Serilog.snk = assets\Serilog.snk EndProjectSection EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog", "src\Serilog\Serilog.xproj", "{803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{791D4267-0D6F-4FDF-80F2-11F4E793B0F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog", "src\Serilog\Serilog.csproj", "{AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{290A2775-7CA0-4F81-9DDC-32E28C3A7565}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Tests", "test\Serilog.Tests\Serilog.Tests.xproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDummies", "test\TestDummies\TestDummies.csproj", "{37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TestDummies", "test\TestDummies\TestDummies.xproj", "{2BB12CE5-C867-43BD-AE5D-253FE3248C7F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Tests", "test\Serilog.Tests\Serilog.Tests.csproj", "{B11B911D-977A-42CE-900A-596CF59F6FFA}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.PerformanceTests", "test\Serilog.PerformanceTests\Serilog.PerformanceTests.xproj", "{D7A37F73-BBA3-4DAE-9648-1A753A86F968}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.PerformanceTests", "test\Serilog.PerformanceTests\Serilog.PerformanceTests.csproj", "{B4AC7ED9-517B-47E9-BB49-15F8A8478E62}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {803CD13A-D54B-4CEC-A55F-E22AE3D93B3C}.Release|Any CPU.Build.0 = Release|Any CPU - {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU - {2BB12CE5-C867-43BD-AE5D-253FE3248C7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BB12CE5-C867-43BD-AE5D-253FE3248C7F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BB12CE5-C867-43BD-AE5D-253FE3248C7F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BB12CE5-C867-43BD-AE5D-253FE3248C7F}.Release|Any CPU.Build.0 = Release|Any CPU - {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.Build.0 = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|x64.ActiveCfg = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|x64.Build.0 = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|x86.ActiveCfg = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Debug|x86.Build.0 = Debug|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|Any CPU.Build.0 = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|x64.ActiveCfg = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|x64.Build.0 = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|x86.ActiveCfg = Release|Any CPU + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1}.Release|x86.Build.0 = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|x64.ActiveCfg = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|x64.Build.0 = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|x86.ActiveCfg = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Debug|x86.Build.0 = Debug|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|Any CPU.Build.0 = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|x64.ActiveCfg = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|x64.Build.0 = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|x86.ActiveCfg = Release|Any CPU + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF}.Release|x86.Build.0 = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|x64.ActiveCfg = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|x64.Build.0 = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|x86.ActiveCfg = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Debug|x86.Build.0 = Debug|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|Any CPU.Build.0 = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|x64.ActiveCfg = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|x64.Build.0 = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|x86.ActiveCfg = Release|Any CPU + {B11B911D-977A-42CE-900A-596CF59F6FFA}.Release|x86.Build.0 = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|x64.ActiveCfg = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|x64.Build.0 = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|x86.ActiveCfg = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Debug|x86.Build.0 = Debug|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|Any CPU.Build.0 = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|x64.ActiveCfg = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|x64.Build.0 = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|x86.ActiveCfg = Release|Any CPU + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {803CD13A-D54B-4CEC-A55F-E22AE3D93B3C} = {037440DE-440B-4129-9F7A-09B42D00397E} - {3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {0D135C0C-A60B-454A-A2F4-CD74A30E04B0} - {2BB12CE5-C867-43BD-AE5D-253FE3248C7F} = {0D135C0C-A60B-454A-A2F4-CD74A30E04B0} - {D7A37F73-BBA3-4DAE-9648-1A753A86F968} = {0D135C0C-A60B-454A-A2F4-CD74A30E04B0} + {AB00B377-9F1E-4D4B-B6B0-B95F53BCAEF1} = {791D4267-0D6F-4FDF-80F2-11F4E793B0F2} + {37EF0B5E-0363-4A47-AF3E-51FA6E79E3CF} = {290A2775-7CA0-4F81-9DDC-32E28C3A7565} + {B11B911D-977A-42CE-900A-596CF59F6FFA} = {290A2775-7CA0-4F81-9DDC-32E28C3A7565} + {B4AC7ED9-517B-47E9-BB49-15F8A8478E62} = {290A2775-7CA0-4F81-9DDC-32E28C3A7565} EndGlobalSection EndGlobal diff --git a/Serilog.sln.DotSettings b/Serilog.sln.DotSettings index 77a52ea2d..a84b8ae38 100644 --- a/Serilog.sln.DotSettings +++ b/Serilog.sln.DotSettings @@ -2,10 +2,13 @@ True + True True True False SOLUTION + DO_NOT_SHOW + DO_NOT_SHOW DO_NOT_SHOW ERROR DO_NOT_SHOW @@ -16,6 +19,7 @@ ERROR WARNING ERROR + HINT ERROR ERROR ERROR @@ -27,8 +31,9 @@ ERROR DO_NOT_SHOW DO_NOT_SHOW - HINT + DO_NOT_SHOW DO_NOT_SHOW + HINT DO_NOT_SHOW HINT ERROR @@ -103,6 +108,7 @@ DO_NOT_SHOW SUGGESTION ERROR + HINT ERROR ERROR ERROR @@ -111,6 +117,8 @@ <?xml version="1.0" encoding="utf-16"?><Profile name="Format My Code Using &quot;Particular&quot; conventions"><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_IMPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><JsInsertSemicolon>True</JsInsertSemicolon><JsReformatCode>True</JsReformatCode><CssReformatCode>True</CssReformatCode><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><HtmlReformatCode>True</HtmlReformatCode><CSShortenReferences>True</CSShortenReferences><CSharpFormatDocComments>True</CSharpFormatDocComments><CssAlphabetizeProperties>True</CssAlphabetizeProperties></Profile> Default: Reformat Code Format My Code Using "Particular" conventions + Implicit + Implicit False DO_NOT_CHANGE DO_NOT_CHANGE @@ -477,10 +485,13 @@ II.2.12 <HandlesEvent /> True Automatic property True + False False False + AD DB DTC + GT ID NSB SLA diff --git a/appveyor-perftest.yml b/appveyor-perftest.yml new file mode 100644 index 000000000..cb30b3e60 --- /dev/null +++ b/appveyor-perftest.yml @@ -0,0 +1,7 @@ +version: '{build}' +skip_tags: true +image: Visual Studio 2017 +configuration: Release +test: off +build_script: +- ps: ./RunPerfTests.ps1 \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 185375dde..7df8c2fe4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,21 +1,10 @@ version: '{build}' skip_tags: true -image: Visual Studio 2015 +image: Visual Studio 2017 configuration: Release -install: - - ps: mkdir -Force ".\build\" | Out-Null - - ps: Invoke-WebRequest "https://raw.githubusercontent.com/dotnet/cli/rel/1.0.0-preview2/scripts/obtain/dotnet-install.ps1" -OutFile ".\build\installcli.ps1" - - ps: $env:DOTNET_INSTALL_DIR = "$pwd\.dotnetcli" - - ps: '& .\build\installcli.ps1 -InstallDir "$env:DOTNET_INSTALL_DIR" -NoPath -Version 1.0.0-preview2-003121' - - ps: $env:Path = "$env:DOTNET_INSTALL_DIR;$env:Path" +test: off build_script: - ps: ./Build.ps1 -test_script: - - nuget.exe install OpenCover -ExcludeVersion - - OpenCover\tools\OpenCover.Console.exe -register:user -filter:"+[Serilog]*" -target:"dotnet.exe" "-targetargs:test test\Serilog.Tests" -returntargetcode -hideskipped:All -output:coverage.xml - - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" - - pip install codecov - - codecov -f "coverage.xml" artifacts: - path: artifacts/Serilog.*.nupkg deploy: diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..29af91cd3 --- /dev/null +++ b/build.cmd @@ -0,0 +1 @@ +@powershell .\Build.ps1 %* \ No newline at end of file diff --git a/build.sh b/build.sh index ff84facad..9d02afdfc 100755 --- a/build.sh +++ b/build.sh @@ -1,17 +1,17 @@ #!/bin/bash +dotnet --info dotnet restore -for path in src/*/project.json; do - dirname="$(dirname "${path}")" - dotnet build ${dirname} -c Release + +for path in src/**/*.csproj; do + dotnet build -f netstandard1.0 -c Release ${path} + dotnet build -f netstandard1.3 -c Release ${path} done -for path in test/Serilog.Tests/project.json; do - dirname="$(dirname "${path}")" - dotnet build ${dirname} -f netcoreapp1.0 -c Release - dotnet test ${dirname} -f netcoreapp1.0 -c Release +for path in test/*.Tests/*.csproj; do + dotnet test -f netcoreapp1.0 -c Release ${path} done -for path in test/Serilog.PerformanceTests/project.json; do - dirname="$(dirname "${path}")" - dotnet build ${dirname} -f netcoreapp1.0 -c Release -done \ No newline at end of file +for path in test/*.PerformanceTests/*.PerformanceTests.csproj; do + dotnet build -f netcoreapp1.1 -c Release ${path} + # dotnet test -f netcoreapp1.1 -c Release ${path} +done diff --git a/global.json b/global.json index a2b2a4152..a88380bf8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "src", "test" ], "sdk": { - "version": "1.0.0-preview2-003121" + "version": "1.0.1" } } diff --git a/results/net46/AllocationsBenchmark-report-github.md b/results/net46/AllocationsBenchmark-report-github.md new file mode 100644 index 000000000..d40893e56 --- /dev/null +++ b/results/net46/AllocationsBenchmark-report-github.md @@ -0,0 +1,18 @@ +``` ini + +BenchmarkDotNet=v0.10.6, OS=Windows 10 Redstone 1 (10.0.14393) +Processor=Intel Core i7-4790 CPU 3.60GHz (Haswell), ProcessorCount=8 +Frequency=3507500 Hz, Resolution=285.1033 ns, Timer=TSC + [Host] : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.7.2053.0 + DefaultJob : Clr 4.0.30319.42000, 32bit LegacyJIT-v4.7.2053.0 + + +``` + | Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + |--------------------- |-------------:|-----------:|-----------:|-------:|---------:|-------:|----------:| + | LogEmpty | 9.749 ns | 0.0346 ns | 0.0324 ns | 1.00 | 0.00 | - | 0 B | + | LogEmptyWithEnricher | 103.460 ns | 0.1742 ns | 0.1629 ns | 10.61 | 0.04 | 0.0066 | 28 B | + | LogScalar | 478.723 ns | 0.6996 ns | 0.6201 ns | 49.11 | 0.17 | 0.0591 | 248 B | + | LogDictionary | 3,867.137 ns | 13.5751 ns | 12.6982 ns | 396.67 | 1.79 | 0.3128 | 1324 B | + | LogSequence | 1,309.241 ns | 1.4345 ns | 1.3418 ns | 134.30 | 0.45 | 0.1144 | 484 B | + | LogAnonymous | 6,128.421 ns | 11.3529 ns | 10.6195 ns | 628.62 | 2.28 | 0.4654 | 1960 B | diff --git a/results/netcoreapp1.1/AllocationsBenchmark-report-github.md b/results/netcoreapp1.1/AllocationsBenchmark-report-github.md new file mode 100644 index 000000000..369846ee3 --- /dev/null +++ b/results/netcoreapp1.1/AllocationsBenchmark-report-github.md @@ -0,0 +1,19 @@ +``` ini + +BenchmarkDotNet=v0.10.6, OS=Windows 10 Redstone 1 (10.0.14393) +Processor=Intel Core i7-4790 CPU 3.60GHz (Haswell), ProcessorCount=8 +Frequency=3507500 Hz, Resolution=285.1033 ns, Timer=TSC +dotnet cli version=2.0.0-preview1-005977 + [Host] : .NET Core 4.6.25211.01, 64bit RyuJIT + DefaultJob : .NET Core 4.6.25211.01, 64bit RyuJIT + + +``` + | Method | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + |--------------------- |-------------:|-----------:|-----------:|-------:|---------:|-------:|----------:| + | LogEmpty | 8.652 ns | 0.0230 ns | 0.0215 ns | 1.00 | 0.00 | - | 0 B | + | LogEmptyWithEnricher | 104.790 ns | 0.4970 ns | 0.4405 ns | 12.11 | 0.06 | 0.0132 | 56 B | + | LogScalar | 432.424 ns | 0.6263 ns | 0.5858 ns | 49.98 | 0.14 | 0.1030 | 432 B | + | LogDictionary | 3,887.068 ns | 4.4649 ns | 3.7284 ns | 449.26 | 1.16 | 0.5417 | 2296 B | + | LogSequence | 1,428.896 ns | 3.6324 ns | 3.2200 ns | 165.15 | 0.53 | 0.2079 | 880 B | + | LogAnonymous | 6,694.431 ns | 22.4848 ns | 21.0323 ns | 773.73 | 3.00 | 0.8392 | 3528 B | diff --git a/run_perf_tests.sh b/run_perf_tests.sh index 18403fd8e..c332aa20d 100755 --- a/run_perf_tests.sh +++ b/run_perf_tests.sh @@ -1,10 +1,6 @@ #!/bin/bash dotnet restore -for path in src/*/project.json; do - dirname="$(dirname "${path}")" - dotnet build ${dirname} -c Release -done -for path in test/Serilog.PerformanceTests/project.json; do - dirname="$(dirname "${path}")" - dotnet test ${dirname} -f netcoreapp1.0 -c Release -done \ No newline at end of file + +for path in test/*.PerformanceTests/*.csproj; do + dotnet test -f netcoreapp1.1 -c Release ${path} +done diff --git a/src/Serilog/Parameters/DepthLimiter.cs b/src/Serilog/Capturing/DepthLimiter.cs similarity index 58% rename from src/Serilog/Parameters/DepthLimiter.cs rename to src/Serilog/Capturing/DepthLimiter.cs index e51b23794..005a3be55 100644 --- a/src/Serilog/Parameters/DepthLimiter.cs +++ b/src/Serilog/Capturing/DepthLimiter.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,43 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; + using Serilog.Core; using Serilog.Debugging; using Serilog.Events; using Serilog.Parsing; -namespace Serilog.Parameters +namespace Serilog.Capturing { partial class PropertyValueConverter { class DepthLimiter : ILogEventPropertyValueFactory { + [ThreadStatic] + static int _currentDepth; + readonly int _maximumDestructuringDepth; - readonly int _currentDepth; readonly PropertyValueConverter _propertyValueConverter; - public DepthLimiter(int currentDepth, int maximumDepth, PropertyValueConverter propertyValueConverter) + public DepthLimiter(int maximumDepth, PropertyValueConverter propertyValueConverter) { _maximumDestructuringDepth = maximumDepth; - _currentDepth = currentDepth; _propertyValueConverter = propertyValueConverter; } + public void SetCurrentDepth(int depth) + { + _currentDepth = depth; + } + public LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring) { - return DefaultIfMaximumDepth() ?? - _propertyValueConverter.CreatePropertyValue(value, destructuring, _currentDepth + 1); + var storedDepth = _currentDepth; + + var result = DefaultIfMaximumDepth(storedDepth) ?? + _propertyValueConverter.CreatePropertyValue(value, destructuring, storedDepth + 1); + + _currentDepth = storedDepth; + + return result; } - public LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false) + LogEventPropertyValue ILogEventPropertyValueFactory.CreatePropertyValue(object value, bool destructureObjects) { - return DefaultIfMaximumDepth() ?? - _propertyValueConverter.CreatePropertyValue(value, destructureObjects, _currentDepth + 1); + var storedDepth = _currentDepth; + + var result = DefaultIfMaximumDepth(storedDepth) ?? + _propertyValueConverter.CreatePropertyValue(value, destructureObjects, storedDepth + 1); + + _currentDepth = storedDepth; + + return result; } - LogEventPropertyValue DefaultIfMaximumDepth() + LogEventPropertyValue DefaultIfMaximumDepth(int depth) { - if (_currentDepth == _maximumDestructuringDepth) + if (depth == _maximumDestructuringDepth) { SelfLog.WriteLine("Maximum destructuring depth reached."); return new ScalarValue(null); diff --git a/src/Serilog/Parameters/GetablePropertyFinder.cs b/src/Serilog/Capturing/GetablePropertyFinder.cs similarity index 98% rename from src/Serilog/Parameters/GetablePropertyFinder.cs rename to src/Serilog/Capturing/GetablePropertyFinder.cs index e7304547f..9c0f74e06 100644 --- a/src/Serilog/Parameters/GetablePropertyFinder.cs +++ b/src/Serilog/Capturing/GetablePropertyFinder.cs @@ -17,7 +17,7 @@ using System.Linq; using System.Reflection; -namespace Serilog.Parameters +namespace Serilog.Capturing { static class GetablePropertyFinder { diff --git a/src/Serilog/Parameters/MessageTemplateProcessor.cs b/src/Serilog/Capturing/MessageTemplateProcessor.cs similarity index 92% rename from src/Serilog/Parameters/MessageTemplateProcessor.cs rename to src/Serilog/Capturing/MessageTemplateProcessor.cs index 780fed64d..974f6fe8a 100644 --- a/src/Serilog/Parameters/MessageTemplateProcessor.cs +++ b/src/Serilog/Capturing/MessageTemplateProcessor.cs @@ -18,11 +18,11 @@ using Serilog.Events; using Serilog.Parsing; -namespace Serilog.Parameters +namespace Serilog.Capturing { class MessageTemplateProcessor : ILogEventPropertyFactory { - readonly IMessageTemplateParser _parser = new MessageTemplateCache(new MessageTemplateParser()); + readonly MessageTemplateCache _parser = new MessageTemplateCache(new MessageTemplateParser()); readonly PropertyBinder _propertyBinder; readonly PropertyValueConverter _propertyValueConverter; diff --git a/src/Serilog/Parameters/PropertyBinder.cs b/src/Serilog/Capturing/PropertyBinder.cs similarity index 99% rename from src/Serilog/Parameters/PropertyBinder.cs rename to src/Serilog/Capturing/PropertyBinder.cs index 1461f61c5..23ebd6047 100644 --- a/src/Serilog/Parameters/PropertyBinder.cs +++ b/src/Serilog/Capturing/PropertyBinder.cs @@ -19,7 +19,7 @@ using Serilog.Events; using Serilog.Parsing; -namespace Serilog.Parameters +namespace Serilog.Capturing { // Performance relevant - on the hot path when creating log events from existing templates. class PropertyBinder diff --git a/src/Serilog/Parameters/PropertyValueConverter.cs b/src/Serilog/Capturing/PropertyValueConverter.cs old mode 100755 new mode 100644 similarity index 61% rename from src/Serilog/Parameters/PropertyValueConverter.cs rename to src/Serilog/Capturing/PropertyValueConverter.cs index 712af6f18..d244271c9 --- a/src/Serilog/Parameters/PropertyValueConverter.cs +++ b/src/Serilog/Capturing/PropertyValueConverter.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ using Serilog.Policies; using System.Runtime.CompilerServices; -namespace Serilog.Parameters +namespace Serilog.Capturing { // Values in Serilog are simplified down into a lowest-common-denominator internal // type system so that there is a better chance of code written with one sink in @@ -45,7 +45,7 @@ partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventProper readonly IDestructuringPolicy[] _destructuringPolicies; readonly IScalarConversionPolicy[] _scalarConversionPolicies; - readonly int _maximumDestructuringDepth; + readonly DepthLimiter _depthLimiter; readonly int _maximumStringLength; readonly int _maximumCollectionCount; readonly bool _propagateExceptions; @@ -63,8 +63,7 @@ public PropertyValueConverter( if (maximumDestructuringDepth < 0) throw new ArgumentOutOfRangeException(nameof(maximumDestructuringDepth)); if (maximumStringLength < 2) throw new ArgumentOutOfRangeException(nameof(maximumStringLength)); if (maximumCollectionCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCollectionCount)); - - _maximumDestructuringDepth = maximumDestructuringDepth; + _propagateExceptions = propagateExceptions; _maximumStringLength = maximumStringLength; _maximumCollectionCount = maximumCollectionCount; @@ -72,9 +71,8 @@ public PropertyValueConverter( _scalarConversionPolicies = new IScalarConversionPolicy[] { new SimpleScalarConversionPolicy(BuiltInScalarTypes.Concat(additionalScalarTypes)), - new NullableScalarConversionPolicy(), new EnumScalarConversionPolicy(), - new ByteArrayScalarConversionPolicy(), + new ByteArrayScalarConversionPolicy() }; _destructuringPolicies = additionalDestructuringPolicies @@ -84,6 +82,8 @@ public PropertyValueConverter( new ReflectionTypesScalarDestructuringPolicy() }) .ToArray(); + + _depthLimiter = new DepthLimiter(maximumDestructuringDepth, this); } public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false) @@ -134,21 +134,19 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur } var valueType = value.GetType(); - var limiter = new DepthLimiter(depth, _maximumDestructuringDepth, this); + _depthLimiter.SetCurrentDepth(depth); if (destructuring == Destructuring.Destructure) { - var stringValue = value as string; - if (stringValue != null) + if (value is string stringValue) { value = TruncateIfNecessary(stringValue); } } foreach (var scalarConversionPolicy in _scalarConversionPolicies) - { - ScalarValue converted; - if (scalarConversionPolicy.TryConvertToScalar(value, limiter, out converted)) + { + if (scalarConversionPolicy.TryConvertToScalar(value, out var converted)) return converted; } @@ -156,14 +154,26 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur { foreach (var destructuringPolicy in _destructuringPolicies) { - LogEventPropertyValue result; - if (destructuringPolicy.TryDestructure(value, limiter, out result)) + if (destructuringPolicy.TryDestructure(value, _depthLimiter, out var result)) return result; } } - var enumerable = value as IEnumerable; - if (enumerable != null) + if (TryConvertEnumerable(value, destructuring, valueType, out var enumerableResult)) + return enumerableResult; + + if (TryConvertValueTuple(value, destructuring, valueType, out var tupleResult)) + return tupleResult; + + if (TryConvertCompilerGeneratedType(value, destructuring, valueType, out var compilerGeneratedResult)) + return compilerGeneratedResult; + + return new ScalarValue(value.ToString()); + } + + bool TryConvertEnumerable(object value, Destructuring destructuring, Type valueType, out LogEventPropertyValue result) + { + if (value is IEnumerable enumerable) { // Only dictionaries with 'scalar' keys are permitted, as // more complex keys may not serialize to unique values for @@ -173,36 +183,112 @@ LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructur // Only actual dictionaries are supported, as arbitrary types // can implement multiple IDictionary interfaces and thus introduce // multiple different interpretations. - if (IsValueTypeDictionary(valueType)) + if (TryGetDictionary(value, valueType, out var dictionary)) { - var typeInfo = typeof(KeyValuePair<,>).MakeGenericType(valueType.GenericTypeArguments).GetTypeInfo(); - var keyProperty = typeInfo.GetDeclaredProperty("Key"); - var valueProperty = typeInfo.GetDeclaredProperty("Value"); - - return new DictionaryValue(enumerable.Cast().Take(_maximumCollectionCount) - .Select(kvp => new KeyValuePair( - (ScalarValue)limiter.CreatePropertyValue(keyProperty.GetValue(kvp), destructuring), - limiter.CreatePropertyValue(valueProperty.GetValue(kvp), destructuring))) - .Where(kvp => kvp.Key.Value != null)); + result = new DictionaryValue(MapToDictionaryElements(dictionary, destructuring)); + return true; + + IEnumerable> MapToDictionaryElements(IDictionary dictionaryEntries, Destructuring destructure) + { + var count = 0; + foreach (DictionaryEntry entry in dictionaryEntries) + { + if (++count > _maximumCollectionCount) + { + yield break; + } + + var pair = new KeyValuePair( + (ScalarValue)_depthLimiter.CreatePropertyValue(entry.Key, destructure), + _depthLimiter.CreatePropertyValue(entry.Value, destructure)); + + if (pair.Key.Value != null) + yield return pair; + } + } } - return new SequenceValue( - enumerable.Cast().Take(_maximumCollectionCount).Select(o => limiter.CreatePropertyValue(o, destructuring))); + result = new SequenceValue(MapToSequenceElements(enumerable, destructuring)); + return true; + + IEnumerable MapToSequenceElements(IEnumerable sequence, Destructuring destructure) + { + var count = 0; + foreach (var element in sequence) + { + if (++count > _maximumCollectionCount) + { + yield break; + } + + yield return _depthLimiter.CreatePropertyValue(element, destructure); + } + } } + result = null; + return false; + } + + bool TryConvertValueTuple(object value, Destructuring destructuring, Type valueType, out LogEventPropertyValue result) + { + if (!(value is IStructuralEquatable && valueType.IsConstructedGenericType)) + { + result = null; + return false; + } + + var definition = valueType.GetGenericTypeDefinition(); + + // Ignore the 8+ value case for now. +#if VALUETUPLE + if (definition == typeof(ValueTuple<>) || definition == typeof(ValueTuple<,>) || + definition == typeof(ValueTuple<,,>) || definition == typeof(ValueTuple<,,,>) || + definition == typeof(ValueTuple<,,,,>) || definition == typeof(ValueTuple<,,,,,>) || + definition == typeof(ValueTuple<,,,,,,>)) +#else + var defn = definition.FullName; + if (defn == "System.ValueTuple`1" || defn == "System.ValueTuple`2" || + defn == "System.ValueTuple`3" || defn == "System.ValueTuple`4" || + defn == "System.ValueTuple`5" || defn == "System.ValueTuple`6" || + defn == "System.ValueTuple`7") +#endif + { + var elements = new List(); + foreach (var field in valueType.GetTypeInfo().DeclaredFields) + { + if (field.IsPublic && !field.IsStatic) + { + var fieldValue = field.GetValue(value); + var propertyValue = _depthLimiter.CreatePropertyValue(fieldValue, destructuring); + elements.Add(propertyValue); + } + } + + result = new SequenceValue(elements); + return true; + } + + result = null; + return false; + } + + bool TryConvertCompilerGeneratedType(object value, Destructuring destructuring, Type valueType, out LogEventPropertyValue result) + { if (destructuring == Destructuring.Destructure) { - var type = value.GetType(); - var typeTag = type.Name; - if (typeTag.Length <= 0 || IsCompilerGeneratedType(type)) + var typeTag = valueType.Name; + if (typeTag.Length <= 0 || IsCompilerGeneratedType(valueType)) { typeTag = null; } - return new StructureValue(GetProperties(value, limiter), typeTag); + result = new StructureValue(GetProperties(value), typeTag); + return true; } - return new ScalarValue(value.ToString()); + result = null; + return false; } LogEventPropertyValue Stringify(object value) @@ -222,11 +308,18 @@ string TruncateIfNecessary(string text) return text; } - bool IsValueTypeDictionary(Type valueType) + bool TryGetDictionary(object value, Type valueType, out IDictionary dictionary) { - return valueType.IsConstructedGenericType && - valueType.GetGenericTypeDefinition() == typeof (Dictionary<,>) && - IsValidDictionaryKeyType(valueType.GenericTypeArguments[0]); + if (valueType.IsConstructedGenericType && + valueType.GetGenericTypeDefinition() == typeof(Dictionary<,>) && + IsValidDictionaryKeyType(valueType.GenericTypeArguments[0])) + { + dictionary = (IDictionary)value; + return true; + } + + dictionary = null; + return false; } bool IsValidDictionaryKeyType(Type valueType) @@ -235,7 +328,7 @@ bool IsValidDictionaryKeyType(Type valueType) valueType.GetTypeInfo().IsEnum; } - IEnumerable GetProperties(object value, ILogEventPropertyValueFactory recursive) + IEnumerable GetProperties(object value) { foreach (var prop in value.GetType().GetPropertiesRecursive()) { @@ -258,9 +351,9 @@ IEnumerable GetProperties(object value, ILogEventPropertyValue if (_propagateExceptions) throw; - propValue = "The property accessor threw an exception: " + ex.InnerException.GetType().Name; + propValue = "The property accessor threw an exception: " + ex.InnerException?.GetType().Name; } - yield return new LogEventProperty(prop.Name, recursive.CreatePropertyValue(propValue, true)); + yield return new LogEventProperty(prop.Name, _depthLimiter.CreatePropertyValue(propValue, Destructuring.Destructure)); } } diff --git a/src/Serilog/Configuration/LoggerSinkConfiguration.cs b/src/Serilog/Configuration/LoggerSinkConfiguration.cs index 0552387cd..47d024e1a 100644 --- a/src/Serilog/Configuration/LoggerSinkConfiguration.cs +++ b/src/Serilog/Configuration/LoggerSinkConfiguration.cs @@ -150,6 +150,47 @@ public LoggerConfiguration Logger( { if (logger == null) throw new ArgumentNullException(nameof(logger)); return Sink(new SecondaryLoggerSink(logger, attemptDispose: false), restrictedToMinimumLevel); - } + } + + /// + /// Helper method for wrapping sinks. + /// + /// The parent sink configuration. + /// A function that allows for wrapping s + /// added in . + /// An action that configures sinks to be wrapped in . + /// Configuration object allowing method chaining. + public static LoggerConfiguration Wrap( + LoggerSinkConfiguration loggerSinkConfiguration, + Func wrapSink, + Action configureWrappedSink) + { + if (loggerSinkConfiguration == null) throw new ArgumentNullException(nameof(loggerSinkConfiguration)); + if (wrapSink == null) throw new ArgumentNullException(nameof(wrapSink)); + if (configureWrappedSink == null) throw new ArgumentNullException(nameof(configureWrappedSink)); + + void WrapAndAddSink(ILogEventSink sink) + { + bool sinkIsDisposable = sink is IDisposable; + + ILogEventSink wrappedSink = wrapSink(sink); + + if (sinkIsDisposable && !(wrappedSink is IDisposable)) + { + SelfLog.WriteLine("Wrapping sink {0} does not implement IDisposable, but wrapped sink {1} does.", wrappedSink, sink); + } + + loggerSinkConfiguration.Sink(wrappedSink); + } + + var capturingLoggerSinkConfiguration = new LoggerSinkConfiguration( + loggerSinkConfiguration._loggerConfiguration, + WrapAndAddSink, + loggerSinkConfiguration._applyInheritedConfiguration); + + configureWrappedSink(capturingLoggerSinkConfiguration); + + return loggerSinkConfiguration._loggerConfiguration; + } } } diff --git a/src/Serilog/Context/LogContext.cs b/src/Serilog/Context/LogContext.cs index 701c5252a..8d2657134 100644 --- a/src/Serilog/Context/LogContext.cs +++ b/src/Serilog/Context/LogContext.cs @@ -14,12 +14,12 @@ using System; +using System.ComponentModel; using Serilog.Core; using Serilog.Core.Enrichers; using Serilog.Events; #if ASYNCLOCAL -using System.Collections.Generic; using System.Threading; #elif REMOTING using System.Runtime.Remoting; @@ -62,7 +62,7 @@ public static class LogContext /// /// Push a property onto the context, returning an - /// that can later be used to remove the property, along with any others that + /// that must later be used to remove the property, along with any others that /// may have been pushed on top of it and not yet popped. The property must /// be popped from the same thread/logical call context. /// @@ -75,37 +75,80 @@ public static class LogContext /// A token that must be disposed, in order, to pop properties back off the stack. public static IDisposable PushProperty(string name, object value, bool destructureObjects = false) { + return Push(new PropertyEnricher(name, value, destructureObjects)); + } + + /// + /// Push an enricher onto the context, returning an + /// that must later be used to remove the property, along with any others that + /// may have been pushed on top of it and not yet popped. The property must + /// be popped from the same thread/logical call context. + /// + /// An enricher to push onto the log context + /// A token that must be disposed, in order, to pop properties back off the stack. + /// + public static IDisposable Push(ILogEventEnricher enricher) + { + if (enricher == null) throw new ArgumentNullException(nameof(enricher)); + var stack = GetOrCreateEnricherStack(); var bookmark = new ContextStackBookmark(stack); - Enrichers = stack.Push(new PropertyEnricher(name, value, destructureObjects)); + Enrichers = stack.Push(enricher); return bookmark; } /// - /// Push multiple properties onto the context, returning an - /// that can later be used to remove the properties. The properties must + /// Push multiple enrichers onto the context, returning an + /// that must later be used to remove the property, along with any others that + /// may have been pushed on top of it and not yet popped. The property must /// be popped from the same thread/logical call context. /// - /// Log Properties to push onto the log context + /// . + /// Enrichers to push onto the log context /// A token that must be disposed, in order, to pop properties back off the stack. /// - public static IDisposable PushProperties(params ILogEventEnricher[] properties) + public static IDisposable Push(params ILogEventEnricher[] enrichers) { - if (properties == null) throw new ArgumentNullException(nameof(properties)); + if (enrichers == null) throw new ArgumentNullException(nameof(enrichers)); var stack = GetOrCreateEnricherStack(); var bookmark = new ContextStackBookmark(stack); - foreach (var prop in properties) - stack = stack.Push(prop); + for (var i = 0; i < enrichers.Length; ++i) + stack = stack.Push(enrichers[i]); Enrichers = stack; return bookmark; } + /// + /// Push enrichers onto the log context. This method is obsolete, please + /// use instead. + /// + /// Enrichers to push onto the log context + /// A token that must be disposed, in order, to pop properties back off the stack. + /// + [Obsolete("Please use `LogContext.Push(properties)` instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public static IDisposable PushProperties(params ILogEventEnricher[] properties) + { + return Push(properties); + } + + /// + /// Obtain an enricher that represents the current contents of the . This + /// can be pushed back onto the context in a different location/thread when required. + /// + /// An enricher that represents the current contents of the . + public static ILogEventEnricher Clone() + { + var stack = GetOrCreateEnricherStack(); + return new SafeAggregateEnricher(stack); + } + static ImmutableStack GetOrCreateEnricherStack() { var enrichers = Enrichers; @@ -148,14 +191,8 @@ public void Dispose() static ImmutableStack Enrichers { - get - { - return Data.Value; - } - set - { - Data.Value = value; - } + get => Data.Value; + set => Data.Value = value; } #elif REMOTING @@ -178,14 +215,8 @@ static ImmutableStack Enrichers static ImmutableStack Enrichers { - get - { - return Data; - } - set - { - Data = value; - } + get => Data; + set => Data = value; } #endif } diff --git a/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs b/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs index 840af394b..16df1c427 100644 --- a/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs +++ b/src/Serilog/Core/Enrichers/FixedPropertyEnricher.cs @@ -23,8 +23,7 @@ class FixedPropertyEnricher : ILogEventEnricher public FixedPropertyEnricher(LogEventProperty logEventProperty) { - if (logEventProperty == null) throw new ArgumentNullException(nameof(logEventProperty)); - _logEventProperty = logEventProperty; + _logEventProperty = logEventProperty ?? throw new ArgumentNullException(nameof(logEventProperty)); } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) diff --git a/src/Serilog/Core/IScalarConversionPolicy.cs b/src/Serilog/Core/IScalarConversionPolicy.cs index f4e9ed2b5..a7fd7466a 100644 --- a/src/Serilog/Core/IScalarConversionPolicy.cs +++ b/src/Serilog/Core/IScalarConversionPolicy.cs @@ -12,9 +12,8 @@ interface IScalarConversionPolicy /// If supported, convert the provided value into an immutable scalar. /// /// The value to convert. - /// Recursively apply policies to convert additional values. /// The converted value, or null. /// True if the value could be converted under this policy. - bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result); + bool TryConvertToScalar(object value, out ScalarValue result); } } \ No newline at end of file diff --git a/src/Serilog/Core/Logger.cs b/src/Serilog/Core/Logger.cs index 141b6363d..b9207c31d 100644 --- a/src/Serilog/Core/Logger.cs +++ b/src/Serilog/Core/Logger.cs @@ -14,10 +14,10 @@ using System; using System.Collections.Generic; +using Serilog.Capturing; using Serilog.Core.Enrichers; using Serilog.Debugging; using Serilog.Events; -using Serilog.Parameters; #pragma warning disable Serilog004 // Constant MessageTemplate verifier diff --git a/src/Serilog/Core/Pipeline/MessageTemplateCache.cs b/src/Serilog/Core/Pipeline/MessageTemplateCache.cs index 0688ee0e1..459a224a5 100644 --- a/src/Serilog/Core/Pipeline/MessageTemplateCache.cs +++ b/src/Serilog/Core/Pipeline/MessageTemplateCache.cs @@ -13,9 +13,13 @@ // limitations under the License. using System; -using System.Collections.Generic; using Serilog.Events; + +#if HASHTABLE using System.Collections; +#else +using System.Collections.Generic; +#endif namespace Serilog.Core.Pipeline { diff --git a/src/Serilog/Data/LogEventPropertyValueVisitor.cs b/src/Serilog/Data/LogEventPropertyValueVisitor.cs index dd1ae1687..d4bea708c 100644 --- a/src/Serilog/Data/LogEventPropertyValueVisitor.cs +++ b/src/Serilog/Data/LogEventPropertyValueVisitor.cs @@ -15,6 +15,8 @@ using System; using Serilog.Events; +// ReSharper disable VirtualMemberNeverOverridden.Global + namespace Serilog.Data { /// @@ -40,7 +42,6 @@ public abstract class LogEventPropertyValueVisitor /// Operation state. /// The value to visit. /// The result of visiting . - // ReSharper disable once VirtualMemberNeverOverriden.Global protected virtual TResult Visit(TState state, LogEventPropertyValue value) { if (value == null) throw new ArgumentNullException(nameof(value)); @@ -103,7 +104,6 @@ protected virtual TResult Visit(TState state, LogEventPropertyValue value) /// The value to visit. /// The result of visiting . // ReSharper disable once UnusedParameter.Global - // ReSharper disable once VirtualMemberNeverOverriden.Global protected virtual TResult VisitUnsupportedValue(TState state, LogEventPropertyValue value) { if (value == null) throw new ArgumentNullException(nameof(value)); diff --git a/src/Serilog/Events/MessageTemplate.cs b/src/Serilog/Events/MessageTemplate.cs index fa545d266..b1664aeea 100644 --- a/src/Serilog/Events/MessageTemplate.cs +++ b/src/Serilog/Events/MessageTemplate.cs @@ -18,6 +18,7 @@ using System.Linq; using Serilog.Debugging; using Serilog.Parsing; +using Serilog.Rendering; namespace Serilog.Events { @@ -28,17 +29,21 @@ namespace Serilog.Events /// public class MessageTemplate { - readonly MessageTemplateToken[] _tokens; + /// + /// Represents the empty message template. + /// + public static MessageTemplate Empty { get; } = new MessageTemplate(Enumerable.Empty()); - // Optimisation for when the template is bound to - // property values. + readonly MessageTemplateToken[] _tokens; /// /// Construct a message template using manually-defined text and property tokens. /// /// The text and property tokens defining the template. public MessageTemplate(IEnumerable tokens) + // ReSharper disable PossibleMultipleEnumeration : this(string.Join("", tokens), tokens) + // ReSharper enable PossibleMultipleEnumeration { } @@ -86,7 +91,7 @@ public MessageTemplate(string text, IEnumerable tokens) /// /// Similar to , but faster. /// - static TResult[] GetElementsOfTypeToArray(object[] tokens) + static TResult[] GetElementsOfTypeToArray(MessageTemplateToken[] tokens) where TResult: class { var result = new List(tokens.Length / 2); @@ -120,6 +125,8 @@ public override string ToString() /// public IEnumerable Tokens => _tokens; + internal MessageTemplateToken[] TokenArray => _tokens; + internal PropertyToken[] NamedProperties { get; } internal PropertyToken[] PositionalProperties { get; } @@ -151,10 +158,9 @@ public string Render(IReadOnlyDictionary properti /// Supplies culture-specific formatting information, or null. public void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null) { - foreach (var token in _tokens) - { - token.Render(properties, output, formatProvider); - } + if (properties == null) throw new ArgumentNullException(nameof(properties)); + if (output == null) throw new ArgumentNullException(nameof(output)); + MessageTemplateRenderer.Render(this, properties, output, null, formatProvider); } } } diff --git a/src/Serilog/Events/ScalarValue.cs b/src/Serilog/Events/ScalarValue.cs index e5b280d6f..01f514fe5 100644 --- a/src/Serilog/Events/ScalarValue.cs +++ b/src/Serilog/Events/ScalarValue.cs @@ -46,16 +46,21 @@ public ScalarValue(object value) /// A format provider to apply to the value, or null to use the default. /// . public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + { + Render(Value, output, format, formatProvider); + } + + internal static void Render(object value, TextWriter output, string format = null, IFormatProvider formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); - if (Value == null) + if (value == null) { output.Write("null"); return; } - var s = Value as string; + var s = value as string; if (s != null) { if (format != "l") @@ -76,19 +81,19 @@ public override void Render(TextWriter output, string format = null, IFormatProv var custom = (ICustomFormatter)formatProvider.GetFormat(typeof(ICustomFormatter)); if (custom != null) { - output.Write(custom.Format(format, Value, formatProvider)); + output.Write(custom.Format(format, value, formatProvider)); return; } } - var f = Value as IFormattable; + var f = value as IFormattable; if (f != null) { output.Write(f.ToString(format, formatProvider ?? CultureInfo.InvariantCulture)); } else { - output.Write(Value.ToString()); + output.Write(value.ToString()); } } diff --git a/src/Serilog/Formatting/Display/LevelOutputFormat.cs b/src/Serilog/Formatting/Display/LevelOutputFormat.cs new file mode 100644 index 000000000..76f9b3d5e --- /dev/null +++ b/src/Serilog/Formatting/Display/LevelOutputFormat.cs @@ -0,0 +1,97 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Serilog.Events; +using Serilog.Rendering; + +namespace Serilog.Formatting.Display +{ + /// + /// Implements the {Level} element. + /// can now have a fixed width applied to it, as well as casing rules. + /// Width is set through formats like "u3" (uppercase three chars), + /// "w1" (one lowercase char), or "t4" (title case four chars). + /// + static class LevelOutputFormat + { + static readonly string[][] _titleCaseLevelMap = { + new []{ "V", "Vb", "Vrb", "Verb" }, + new []{ "D", "De", "Dbg", "Dbug" }, + new []{ "I", "In", "Inf", "Info" }, + new []{ "W", "Wn", "Wrn", "Warn" }, + new []{ "E", "Er", "Err", "Eror" }, + new []{ "F", "Fa", "Ftl", "Fatl" } + }; + + static readonly string[][] _lowercaseLevelMap = { + new []{ "v", "vb", "vrb", "verb" }, + new []{ "d", "de", "dbg", "dbug" }, + new []{ "i", "in", "inf", "info" }, + new []{ "w", "wn", "wrn", "warn" }, + new []{ "e", "er", "err", "eror" }, + new []{ "f", "fa", "ftl", "fatl" } + }; + + static readonly string[][] _uppercaseLevelMap = { + new []{ "V", "VB", "VRB", "VERB" }, + new []{ "D", "DE", "DBG", "DBUG" }, + new []{ "I", "IN", "INF", "INFO" }, + new []{ "W", "WN", "WRN", "WARN" }, + new []{ "E", "ER", "ERR", "EROR" }, + new []{ "F", "FA", "FTL", "FATL" } + }; + + public static string GetLevelMoniker(LogEventLevel value, string format = null) + { + if (format == null || format.Length != 2 && format.Length != 3) + return Casing.Format(value.ToString(), format); + + // Using int.Parse() here requires allocating a string to exclude the first character prefix. + // Junk like "wxy" will be accepted but produce benign results. + var width = format[1] - '0'; + if (format.Length == 3) + { + width *= 10; + width += format[2] - '0'; + } + + if (width < 1) + return string.Empty; + + if (width > 4) + { + var stringValue = value.ToString(); + if (stringValue.Length > width) + stringValue = stringValue.Substring(0, width); + return Casing.Format(stringValue); + } + + var index = (int)value; + if (index >= 0 && index <= (int) LogEventLevel.Fatal) + { + switch (format[0]) + { + case 'w': + return _lowercaseLevelMap[index][width - 1]; + case 'u': + return _uppercaseLevelMap[index][width - 1]; + case 't': + return _titleCaseLevelMap[index][width - 1]; + } + } + + return Casing.Format(value.ToString(), format); + } + } +} \ No newline at end of file diff --git a/src/Serilog/Formatting/Display/LogEventLevelValue.cs b/src/Serilog/Formatting/Display/LogEventLevelValue.cs deleted file mode 100644 index d850722c3..000000000 --- a/src/Serilog/Formatting/Display/LogEventLevelValue.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2013-2015 Serilog Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.IO; - -using Serilog.Events; - -namespace Serilog.Formatting.Display -{ - // Allows for the specific handling of the {Level} element. - // can now have a fixed width applied to it, as well as casing rules. - // Width is set through formats like "u3" (uppercase three chars), - // "w1" (one lowercase char), or "t4" (title case four chars). - class LogEventLevelValue : LogEventPropertyValue - { - readonly LogEventLevel _value; - - static readonly string[][] _titleCaseLevelMap = { - new []{ "V", "Vb", "Vrb", "Verb" }, - new []{ "D", "De", "Dbg", "Dbug" }, - new []{ "I", "In", "Inf", "Info" }, - new []{ "W", "Wn", "Wrn", "Warn" }, - new []{ "E", "Er", "Err", "Eror" }, - new []{ "F", "Fa", "Ftl", "Fatl" } - }; - - static readonly string[][] _lowercaseLevelMap = { - new []{ "v", "vb", "vrb", "verb" }, - new []{ "d", "de", "dbg", "dbug" }, - new []{ "i", "in", "inf", "info" }, - new []{ "w", "wn", "wrn", "warn" }, - new []{ "e", "er", "err", "eror" }, - new []{ "f", "fa", "ftl", "fatl" } - }; - - static readonly string[][] _uppercaseLevelMap = { - new []{ "V", "VB", "VRB", "VERB" }, - new []{ "D", "DE", "DBG", "DBUG" }, - new []{ "I", "IN", "INF", "INFO" }, - new []{ "W", "WN", "WRN", "WARN" }, - new []{ "E", "ER", "ERR", "EROR" }, - new []{ "F", "FA", "FTL", "FATL" } - }; - - public LogEventLevelValue(LogEventLevel value) - { - _value = value; - } - - /// - /// This method will apply only upper or lower case formatting, not fixed width - /// - public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) - { - if (format != null && (format.Length == 2 || format.Length == 3)) - { - // Using int.Parse() here requires allocating a string to exclude the first character prefix. - // Junk like "wxy" will be accepted but produce benign results. - var width = format[1] - '0'; - if (format.Length == 3) - { - width *= 10; - width += format[2] - '0'; - } - - if (width < 1) - return; - - if (width > 4) - { - var value = _value.ToString(); - if (value.Length > width) - value = value.Substring(0, width); - output.Write(Casing.Format(value)); - return; - } - - var index = (int)_value; - if (index >= 0 && index <= (int) LogEventLevel.Fatal) - { - switch (format[0]) - { - case 'w': - output.Write(_lowercaseLevelMap[index][width - 1]); - return; - case 'u': - output.Write(_uppercaseLevelMap[index][width - 1]); - return; - case 't': - output.Write(_titleCaseLevelMap[index][width - 1]); - return; - } - } - } - - output.Write(Casing.Format(_value.ToString(), format)); - } - } -} \ No newline at end of file diff --git a/src/Serilog/Formatting/Display/MessageTemplateTextFormatter.cs b/src/Serilog/Formatting/Display/MessageTemplateTextFormatter.cs index 54f9a1fe7..ac45223db 100644 --- a/src/Serilog/Formatting/Display/MessageTemplateTextFormatter.cs +++ b/src/Serilog/Formatting/Display/MessageTemplateTextFormatter.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ // limitations under the License. using System; -using System.Collections.Generic; using System.IO; using Serilog.Events; using Serilog.Parsing; +using Serilog.Rendering; namespace Serilog.Formatting.Display { @@ -58,42 +58,70 @@ public void Format(LogEvent logEvent, TextWriter output) if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); if (output == null) throw new ArgumentNullException(nameof(output)); - // This could be lazier: the output properties include - // everything from the log event, but often we won't need any more than - // just the standard timestamp/message etc. - var outputProperties = OutputProperties.GetOutputProperties(logEvent); - foreach (var token in _outputTemplate.Tokens) { - var pt = token as PropertyToken; - if (pt == null) + if (token is TextToken tt) { - token.Render(outputProperties, output, _formatProvider); + MessageTemplateRenderer.RenderTextToken(tt, output); continue; } - // First variation from normal rendering - if a property is missing, - // don't render anything (message templates render the raw token here). - LogEventPropertyValue propertyValue; - if (!outputProperties.TryGetValue(pt.PropertyName, out propertyValue)) - continue; - - // Second variation; if the value is a scalar string, use literal - // rendering and support some additional formats: 'u' for uppercase - // and 'w' for lowercase. - var sv = propertyValue as ScalarValue; - if (sv != null && sv.Value is string) + var pt = (PropertyToken)token; + if (pt.PropertyName == OutputProperties.LevelPropertyName) { - var overridden = new Dictionary - { - { pt.PropertyName, new LiteralStringValue((string) sv.Value) } - }; - - token.Render(overridden, output, _formatProvider); + var moniker = LevelOutputFormat.GetLevelMoniker(logEvent.Level, pt.Format); + Padding.Apply(output, moniker, pt.Alignment); + } + else if (pt.PropertyName == OutputProperties.NewLinePropertyName) + { + Padding.Apply(output, Environment.NewLine, pt.Alignment); + } + else if (pt.PropertyName == OutputProperties.ExceptionPropertyName) + { + var exception = logEvent.Exception == null ? "" : logEvent.Exception + Environment.NewLine; + Padding.Apply(output, exception, pt.Alignment); } else { - token.Render(outputProperties, output, _formatProvider); + // In this block, `writer` may be used to buffer output so that + // padding can be applied. + var writer = pt.Alignment.HasValue ? new StringWriter() : output; + + if (pt.PropertyName == OutputProperties.MessagePropertyName) + { + MessageTemplateRenderer.Render(logEvent.MessageTemplate, logEvent.Properties, writer, pt.Format, _formatProvider); + } + else if (pt.PropertyName == OutputProperties.TimestampPropertyName) + { + ScalarValue.Render(logEvent.Timestamp, writer, pt.Format, _formatProvider); + } + else if (pt.PropertyName == OutputProperties.PropertiesPropertyName) + { + PropertiesOutputFormat.Render(logEvent.MessageTemplate, logEvent.Properties, _outputTemplate, writer, _formatProvider); + } + else + { + // If a property is missing, don't render anything (message templates render the raw token here). + LogEventPropertyValue propertyValue; + if (!logEvent.Properties.TryGetValue(pt.PropertyName, out propertyValue)) + continue; + + // If the value is a scalar string, support some additional formats: 'u' for uppercase + // and 'w' for lowercase. + var sv = propertyValue as ScalarValue; + if (sv?.Value is string literalString) + { + var cased = Casing.Format(literalString, pt.Format); + writer.Write(cased); + } + else + { + propertyValue.Render(output, pt.Format, _formatProvider); + } + } + + if (pt.Alignment.HasValue) + Padding.Apply(output, ((StringWriter)writer).ToString(), pt.Alignment); } } } diff --git a/src/Serilog/Formatting/Display/LiteralStringValue.cs b/src/Serilog/Formatting/Display/Obsolete/LiteralStringValue.cs similarity index 86% rename from src/Serilog/Formatting/Display/LiteralStringValue.cs rename to src/Serilog/Formatting/Display/Obsolete/LiteralStringValue.cs index 82f172536..e7bc44023 100644 --- a/src/Serilog/Formatting/Display/LiteralStringValue.cs +++ b/src/Serilog/Formatting/Display/Obsolete/LiteralStringValue.cs @@ -15,19 +15,20 @@ using System; using System.IO; using Serilog.Events; +using Serilog.Rendering; -namespace Serilog.Formatting.Display +namespace Serilog.Formatting.Display.Obsolete { // A special case (non-null) string value for use in output // templates. Does not apply "quoted" formatting by default. + [Obsolete("Not used by the current output formatting implementation.")] class LiteralStringValue : LogEventPropertyValue { readonly string _value; public LiteralStringValue(string value) { - if (value == null) throw new ArgumentNullException(nameof(value)); - _value = value; + _value = value ?? throw new ArgumentNullException(nameof(value)); } public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) diff --git a/src/Serilog/Formatting/Display/Obsolete/LogEventLevelValue.cs b/src/Serilog/Formatting/Display/Obsolete/LogEventLevelValue.cs new file mode 100644 index 000000000..293990f5a --- /dev/null +++ b/src/Serilog/Formatting/Display/Obsolete/LogEventLevelValue.cs @@ -0,0 +1,40 @@ +// Copyright 2013-2015 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.IO; + +using Serilog.Events; + +namespace Serilog.Formatting.Display.Obsolete +{ + [Obsolete("Not used by the current output formatting implementation.")] + class LogEventLevelValue : LogEventPropertyValue + { + readonly LogEventLevel _value; + + public LogEventLevelValue(LogEventLevel value) + { + _value = value; + } + + /// + /// This method will apply only upper or lower case formatting, not fixed width + /// + public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + { + output.Write(LevelOutputFormat.GetLevelMoniker(_value, format)); + } + } +} diff --git a/src/Serilog/Formatting/Display/Obsolete/LogEventPropertiesValue.cs b/src/Serilog/Formatting/Display/Obsolete/LogEventPropertiesValue.cs new file mode 100644 index 000000000..a0b089152 --- /dev/null +++ b/src/Serilog/Formatting/Display/Obsolete/LogEventPropertiesValue.cs @@ -0,0 +1,41 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using Serilog.Events; + +namespace Serilog.Formatting.Display.Obsolete +{ + [Obsolete("Not used by the current output formatting implementation.")] + class LogEventPropertiesValue : LogEventPropertyValue + { + readonly MessageTemplate _template; + readonly IReadOnlyDictionary _properties; + readonly MessageTemplate _outputTemplate; + + public LogEventPropertiesValue(MessageTemplate template, IReadOnlyDictionary properties, MessageTemplate outputTemplate) + { + _template = template; + _properties = properties; + _outputTemplate = outputTemplate; + } + + public override void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null) + { + PropertiesOutputFormat.Render(_template, _properties, _outputTemplate, output, formatProvider); + } + } +} \ No newline at end of file diff --git a/src/Serilog/Formatting/Display/LogEventPropertyMessageValue.cs b/src/Serilog/Formatting/Display/Obsolete/LogEventPropertyMessageValue.cs similarity index 91% rename from src/Serilog/Formatting/Display/LogEventPropertyMessageValue.cs rename to src/Serilog/Formatting/Display/Obsolete/LogEventPropertyMessageValue.cs index f021962cd..9507a7fb2 100644 --- a/src/Serilog/Formatting/Display/LogEventPropertyMessageValue.cs +++ b/src/Serilog/Formatting/Display/Obsolete/LogEventPropertyMessageValue.cs @@ -17,8 +17,9 @@ using System.IO; using Serilog.Events; -namespace Serilog.Formatting.Display +namespace Serilog.Formatting.Display.Obsolete { + [Obsolete("Not used by the current output formatting implementation.")] class LogEventPropertyMessageValue : LogEventPropertyValue { readonly MessageTemplate _template; diff --git a/src/Serilog/Formatting/Display/OutputProperties.cs b/src/Serilog/Formatting/Display/OutputProperties.cs index 33a1e1317..c1b2cf8c8 100644 --- a/src/Serilog/Formatting/Display/OutputProperties.cs +++ b/src/Serilog/Formatting/Display/OutputProperties.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,9 @@ using System.Collections.Generic; using System.Linq; using Serilog.Events; +using Serilog.Formatting.Display.Obsolete; + +#pragma warning disable 618 namespace Serilog.Formatting.Display { @@ -25,6 +28,8 @@ namespace Serilog.Formatting.Display /// public static class OutputProperties { + static readonly LiteralStringValue LiteralNewLine = new LiteralStringValue(Environment.NewLine); + /// /// The message rendered from the log event. /// @@ -50,12 +55,29 @@ public static class OutputProperties /// public const string ExceptionPropertyName = "Exception"; + /// + /// The properties of the log event. + /// + public const string PropertiesPropertyName = "Properties"; + /// /// Create properties from the provided log event. /// /// The log event. /// A dictionary with properties representing the log event. + [Obsolete("These implementation details of output formatting will not be exposed in a future version.")] public static IReadOnlyDictionary GetOutputProperties(LogEvent logEvent) + { + return GetOutputProperties(logEvent, MessageTemplate.Empty); + } + + /// + /// Create properties from the provided log event. + /// + /// The log event. + /// The output template. + /// A dictionary with properties representing the log event. + internal static IReadOnlyDictionary GetOutputProperties(LogEvent logEvent, MessageTemplate outputTemplate) { var result = logEvent.Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); @@ -66,9 +88,10 @@ public static IReadOnlyDictionary GetOutputProper result[MessagePropertyName] = new LogEventPropertyMessageValue(logEvent.MessageTemplate, logEvent.Properties); result[TimestampPropertyName] = new ScalarValue(logEvent.Timestamp); result[LevelPropertyName] = new LogEventLevelValue(logEvent.Level); - result[NewLinePropertyName] = new LiteralStringValue(Environment.NewLine); + result[NewLinePropertyName] = LiteralNewLine; + result[PropertiesPropertyName] = new LogEventPropertiesValue(logEvent.MessageTemplate, logEvent.Properties, outputTemplate); - var exception = logEvent.Exception == null ? "" : (logEvent.Exception + Environment.NewLine); + var exception = logEvent.Exception == null ? "" : logEvent.Exception + Environment.NewLine; result[ExceptionPropertyName] = new LiteralStringValue(exception); return result; diff --git a/src/Serilog/Formatting/Display/Padding.cs b/src/Serilog/Formatting/Display/Padding.cs deleted file mode 100644 index de6744f34..000000000 --- a/src/Serilog/Formatting/Display/Padding.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.IO; - -using Serilog.Parsing; - -namespace Serilog.Formatting.Display -{ - static class Padding - { - /// - /// Writes the provided value to the output, applying direction-based padding when is provided. - /// - public static void Apply(TextWriter output, string value, Alignment? alignment) - { - if (!alignment.HasValue) - { - output.Write(value); - return; - } - - var pad = alignment.Value.Width - value.Length; - - if (alignment.Value.Direction == AlignmentDirection.Right) - output.Write(new string(' ', pad)); - - output.Write(value); - - if (alignment.Value.Direction == AlignmentDirection.Left) - output.Write(new string(' ', pad)); - } - } -} \ No newline at end of file diff --git a/src/Serilog/Formatting/Display/PropertiesOutputFormat.cs b/src/Serilog/Formatting/Display/PropertiesOutputFormat.cs new file mode 100644 index 000000000..ac2b5b807 --- /dev/null +++ b/src/Serilog/Formatting/Display/PropertiesOutputFormat.cs @@ -0,0 +1,70 @@ +// Copyright 2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using Serilog.Events; + +namespace Serilog.Formatting.Display +{ + static class PropertiesOutputFormat + { + public static void Render(MessageTemplate template, IReadOnlyDictionary properties, MessageTemplate outputTemplate, TextWriter output, IFormatProvider formatProvider = null) + { + output.Write('{'); + + var delim = ""; + foreach (var kvp in properties) + { + if (TemplateContainsPropertyName(template, kvp.Key)) + { + continue; + } + + if (TemplateContainsPropertyName(outputTemplate, kvp.Key)) + { + continue; + } + + output.Write(delim); + delim = ", "; + output.Write(kvp.Key); + output.Write(": "); + kvp.Value.Render(output, null, formatProvider); + } + + output.Write('}'); + } + + static bool TemplateContainsPropertyName(MessageTemplate template, string propertyName) + { + if (template.NamedProperties == null) + { + return false; + } + + for (var i = 0; i < template.NamedProperties.Length; i++) + { + var namedProperty = template.NamedProperties[i]; + if (namedProperty.PropertyName == propertyName) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Serilog/Formatting/Json/JsonFormatter.cs b/src/Serilog/Formatting/Json/JsonFormatter.cs index aded1eb29..5e762ad7c 100644 --- a/src/Serilog/Formatting/Json/JsonFormatter.cs +++ b/src/Serilog/Formatting/Json/JsonFormatter.cs @@ -21,6 +21,7 @@ using System.Linq; using Serilog.Events; using Serilog.Parsing; +using Serilog.Rendering; namespace Serilog.Formatting.Json { @@ -207,7 +208,7 @@ protected virtual void WriteRenderingsValues(IGrouping[] WriteJsonProperty("Format", format.Format, ref eldelim, output); var sw = new StringWriter(); - format.Render(properties, sw); + MessageTemplateRenderer.RenderPropertyToken(format, properties, sw, _formatProvider, true, false); WriteJsonProperty("Rendering", sw.ToString(), ref eldelim, output); output.Write("}"); diff --git a/src/Serilog/Formatting/Raw/RawFormatter.cs b/src/Serilog/Formatting/Raw/RawFormatter.cs index 95ad05e3b..6138c190e 100644 --- a/src/Serilog/Formatting/Raw/RawFormatter.cs +++ b/src/Serilog/Formatting/Raw/RawFormatter.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.IO; using Serilog.Events; @@ -20,6 +21,7 @@ namespace Serilog.Formatting.Raw /// /// Formats log events as a raw dump of the message template and properties. /// + [Obsolete("A JSON-based formatter such as `Serilog.Formatting.Compact.CompactJsonFormatter` is recommended for this task.")] public class RawFormatter : ITextFormatter { /// diff --git a/src/Serilog/LoggerConfiguration.cs b/src/Serilog/LoggerConfiguration.cs index bc39b5e4b..f24e2dc6c 100644 --- a/src/Serilog/LoggerConfiguration.cs +++ b/src/Serilog/LoggerConfiguration.cs @@ -15,12 +15,12 @@ using System; using System.Collections.Generic; using System.Linq; +using Serilog.Capturing; using Serilog.Configuration; using Serilog.Core; using Serilog.Core.Enrichers; using Serilog.Core.Sinks; using Serilog.Events; -using Serilog.Parameters; namespace Serilog { diff --git a/src/Serilog/Parsing/MessageTemplateParser.cs b/src/Serilog/Parsing/MessageTemplateParser.cs index 64721a147..7a9e1003c 100644 --- a/src/Serilog/Parsing/MessageTemplateParser.cs +++ b/src/Serilog/Parsing/MessageTemplateParser.cs @@ -95,8 +95,8 @@ static MessageTemplateToken ParsePropertyToken(int startAt, string messageTempla return new TextToken(rawText, first); var propertyName = propertyNameAndDestructuring; - Destructuring destructuring; - if (TryGetDestructuringHint(propertyName[0], out destructuring)) + var destructuring = Destructuring.Default; + if (propertyName.Length != 0 && TryGetDestructuringHint(propertyName[0], out destructuring)) propertyName = propertyName.Substring(1); if (propertyName.Length == 0) diff --git a/src/Serilog/Parsing/MessageTemplateToken.cs b/src/Serilog/Parsing/MessageTemplateToken.cs index 52a3abcb2..a7cc83ba1 100644 --- a/src/Serilog/Parsing/MessageTemplateToken.cs +++ b/src/Serilog/Parsing/MessageTemplateToken.cs @@ -50,6 +50,7 @@ protected MessageTemplateToken(int startIndex) /// Properties that may be represented by the token. /// Output for the rendered string. /// Supplies culture-specific formatting information, or null. + // ReSharper disable once UnusedMemberInSuper.Global public abstract void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null); } } \ No newline at end of file diff --git a/src/Serilog/Parsing/PropertyToken.cs b/src/Serilog/Parsing/PropertyToken.cs index ef4db7c3a..01dcc2109 100644 --- a/src/Serilog/Parsing/PropertyToken.cs +++ b/src/Serilog/Parsing/PropertyToken.cs @@ -18,14 +18,14 @@ using System.Globalization; using System.IO; using Serilog.Events; -using Serilog.Formatting.Display; +using Serilog.Rendering; namespace Serilog.Parsing { /// /// A message template token representing a log event property. /// - public class PropertyToken : MessageTemplateToken + public sealed class PropertyToken : MessageTemplateToken { readonly string _rawText; readonly int? _position; @@ -57,12 +57,10 @@ public PropertyToken(string propertyName, string rawText, string formatObsolete, public PropertyToken(string propertyName, string rawText, string format = null, Alignment? alignment = null, Destructuring destructuring = Destructuring.Default, int startIndex = -1) : base(startIndex) { - if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); - if (rawText == null) throw new ArgumentNullException(nameof(rawText)); - PropertyName = propertyName; + PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); Format = format; Destructuring = destructuring; - _rawText = rawText; + _rawText = rawText ?? throw new ArgumentNullException(nameof(rawText)); Alignment = alignment; int position; @@ -89,30 +87,7 @@ public override void Render(IReadOnlyDictionary p if (properties == null) throw new ArgumentNullException(nameof(properties)); if (output == null) throw new ArgumentNullException(nameof(output)); - LogEventPropertyValue propertyValue; - if (!properties.TryGetValue(PropertyName, out propertyValue)) - { - output.Write(_rawText); - return; - } - - if (!Alignment.HasValue) - { - propertyValue.Render(output, Format, formatProvider); - return; - } - - var valueOutput = new StringWriter(); - propertyValue.Render(valueOutput, Format, formatProvider); - var value = valueOutput.ToString(); - - if (value.Length >= Alignment.Value.Width) - { - output.Write(value); - return; - } - - Padding.Apply(output, value, Alignment.Value); + MessageTemplateRenderer.RenderPropertyToken(this, properties, output, formatProvider, false, false); } /// @@ -140,6 +115,8 @@ public override void Render(IReadOnlyDictionary p /// public bool IsPositional => _position.HasValue; + internal string RawText => _rawText; + /// /// Try to get the integer value represented by the property name. /// diff --git a/src/Serilog/Parsing/TextToken.cs b/src/Serilog/Parsing/TextToken.cs index 9035d57d1..493a1b56d 100644 --- a/src/Serilog/Parsing/TextToken.cs +++ b/src/Serilog/Parsing/TextToken.cs @@ -16,13 +16,14 @@ using System.Collections.Generic; using System.IO; using Serilog.Events; +using Serilog.Rendering; namespace Serilog.Parsing { /// /// A message template token representing literal text. /// - public class TextToken : MessageTemplateToken + public sealed class TextToken : MessageTemplateToken { /// /// Construct a . @@ -32,8 +33,7 @@ public class TextToken : MessageTemplateToken /// public TextToken(string text, int startIndex = -1) : base(startIndex) { - if (text == null) throw new ArgumentNullException(nameof(text)); - Text = text; + Text = text ?? throw new ArgumentNullException(nameof(text)); } /// @@ -50,7 +50,7 @@ public TextToken(string text, int startIndex = -1) : base(startIndex) public override void Render(IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider = null) { if (output == null) throw new ArgumentNullException(nameof(output)); - output.Write(Text); + MessageTemplateRenderer.RenderTextToken(this, output); } /// diff --git a/src/Serilog/Policies/ByteArrayScalarConversionPolicy.cs b/src/Serilog/Policies/ByteArrayScalarConversionPolicy.cs index d0180eaf8..38504f9cc 100644 --- a/src/Serilog/Policies/ByteArrayScalarConversionPolicy.cs +++ b/src/Serilog/Policies/ByteArrayScalarConversionPolicy.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ class ByteArrayScalarConversionPolicy : IScalarConversionPolicy { const int MaximumByteArrayLength = 1024; - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, out ScalarValue result) { var bytes = value as byte[]; if (bytes == null) diff --git a/src/Serilog/Policies/EnumScalarConversionPolicy.cs b/src/Serilog/Policies/EnumScalarConversionPolicy.cs index 2a215cba4..ebb59a7de 100644 --- a/src/Serilog/Policies/EnumScalarConversionPolicy.cs +++ b/src/Serilog/Policies/EnumScalarConversionPolicy.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ namespace Serilog.Policies { class EnumScalarConversionPolicy : IScalarConversionPolicy { - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, out ScalarValue result) { if (value.GetType().GetTypeInfo().IsEnum) { diff --git a/src/Serilog/Policies/NullableScalarConversionPolicy.cs b/src/Serilog/Policies/NullableScalarConversionPolicy.cs deleted file mode 100644 index e4a19f138..000000000 --- a/src/Serilog/Policies/NullableScalarConversionPolicy.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2013-2015 Serilog Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using Serilog.Core; -using Serilog.Events; - -namespace Serilog.Policies -{ - class NullableScalarConversionPolicy : IScalarConversionPolicy - { - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) - { - var type = value.GetType(); - if (!type.IsConstructedGenericType || type.GetGenericTypeDefinition() != typeof(Nullable<>)) - { - result = null; - return false; - } - - var targetType = type.GenericTypeArguments[0]; - - var innerValue = Convert.ChangeType(value, targetType); - result = propertyValueFactory.CreatePropertyValue(innerValue) as ScalarValue; - return result != null; - } - } -} diff --git a/src/Serilog/Policies/SimpleScalarConversionPolicy.cs b/src/Serilog/Policies/SimpleScalarConversionPolicy.cs index 4a87b6d4f..56c599848 100644 --- a/src/Serilog/Policies/SimpleScalarConversionPolicy.cs +++ b/src/Serilog/Policies/SimpleScalarConversionPolicy.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ public SimpleScalarConversionPolicy(IEnumerable scalarTypes) _scalarTypes = new HashSet(scalarTypes); } - public bool TryConvertToScalar(object value, ILogEventPropertyValueFactory propertyValueFactory, out ScalarValue result) + public bool TryConvertToScalar(object value, out ScalarValue result) { if (_scalarTypes.Contains(value.GetType())) { diff --git a/src/Serilog/Formatting/Display/Casing.cs b/src/Serilog/Rendering/Casing.cs similarity index 53% rename from src/Serilog/Formatting/Display/Casing.cs rename to src/Serilog/Rendering/Casing.cs index b024e8b87..62aa022f5 100644 --- a/src/Serilog/Formatting/Display/Casing.cs +++ b/src/Serilog/Rendering/Casing.cs @@ -1,4 +1,18 @@ -namespace Serilog.Formatting.Display +// Copyright 2013-2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace Serilog.Rendering { static class Casing { diff --git a/src/Serilog/Rendering/MessageTemplateRenderer.cs b/src/Serilog/Rendering/MessageTemplateRenderer.cs new file mode 100644 index 000000000..090e39e98 --- /dev/null +++ b/src/Serilog/Rendering/MessageTemplateRenderer.cs @@ -0,0 +1,109 @@ +// Copyright 2013-2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using Serilog.Events; +using Serilog.Formatting.Json; +using Serilog.Parsing; + +namespace Serilog.Rendering +{ + static class MessageTemplateRenderer + { + static JsonValueFormatter JsonValueFormatter = new JsonValueFormatter("$type"); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Render(MessageTemplate messageTemplate, IReadOnlyDictionary properties, TextWriter output, string format = null, IFormatProvider formatProvider = null) + { + bool isLiteral = false, isJson = false; + + if (format != null) + { + for (var i = 0; i < format.Length; ++i) + { + if (format[i] == 'l') + isLiteral = true; + else if (format[i] == 'j') + isJson = true; + } + } + + for (var ti = 0; ti < messageTemplate.TokenArray.Length; ++ti) + { + var token = messageTemplate.TokenArray[ti]; + if (token is TextToken tt) + { + RenderTextToken(tt, output); + } + else + { + var pt = (PropertyToken) token; + RenderPropertyToken(pt, properties, output, formatProvider, isLiteral, isJson); + } + } + } + + public static void RenderTextToken(TextToken tt, TextWriter output) + { + output.Write(tt.Text); + } + + public static void RenderPropertyToken(PropertyToken pt, IReadOnlyDictionary properties, TextWriter output, IFormatProvider formatProvider, bool isLiteral, bool isJson) + { + LogEventPropertyValue propertyValue; + if (!properties.TryGetValue(pt.PropertyName, out propertyValue)) + { + output.Write(pt.RawText); + return; + } + + if (!pt.Alignment.HasValue) + { + RenderValue(propertyValue, isLiteral, isJson, output, pt.Format, formatProvider); + return; + } + + var valueOutput = new StringWriter(); + RenderValue(propertyValue, isLiteral, isJson, valueOutput, pt.Format, formatProvider); + var value = valueOutput.ToString(); + + if (value.Length >= pt.Alignment.Value.Width) + { + output.Write(value); + return; + } + + Padding.Apply(output, value, pt.Alignment.Value); + } + + static void RenderValue(LogEventPropertyValue propertyValue, bool literal, bool json, TextWriter output, string format, IFormatProvider formatProvider) + { + if (literal && propertyValue is ScalarValue sv && sv.Value is string str) + { + output.Write(str); + } + else if (json) + { + JsonValueFormatter.Format(propertyValue, output); + } + else + { + propertyValue.Render(output, format, formatProvider); + } + } + } +} diff --git a/src/Serilog/Rendering/Padding.cs b/src/Serilog/Rendering/Padding.cs new file mode 100644 index 000000000..23ea8206e --- /dev/null +++ b/src/Serilog/Rendering/Padding.cs @@ -0,0 +1,53 @@ +// Copyright 2013-2017 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.IO; +using Serilog.Parsing; + +namespace Serilog.Rendering +{ + static class Padding + { + static readonly char[] PaddingChars = new string(' ', 80).ToCharArray(); + + /// + /// Writes the provided value to the output, applying direction-based padding when is provided. + /// + public static void Apply(TextWriter output, string value, Alignment? alignment) + { + if (!alignment.HasValue || value.Length >= alignment.Value.Width) + { + output.Write(value); + return; + } + + var pad = alignment.Value.Width - value.Length; + + if (alignment.Value.Direction == AlignmentDirection.Left) + output.Write(value); + + if (pad <= PaddingChars.Length) + { + output.Write(PaddingChars, 0, pad); + } + else + { + output.Write(new string(' ', pad)); + } + + if (alignment.Value.Direction == AlignmentDirection.Right) + output.Write(value); + } + } +} \ No newline at end of file diff --git a/src/Serilog/Serilog.csproj b/src/Serilog/Serilog.csproj new file mode 100644 index 000000000..8643248d6 --- /dev/null +++ b/src/Serilog/Serilog.csproj @@ -0,0 +1,76 @@ + + + + Simple .NET logging with fully-structured events + 2.5.0 + Serilog Contributors + net45;net46;netstandard1.0;netstandard1.3 + true + Serilog + ../../assets/Serilog.snk + true + true + Serilog + serilog;logging;semantic;structured + http://serilog.net/images/serilog-nuget.png + http://serilog.net + http://www.apache.org/licenses/LICENSE-2.0 + false + + true + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefineConstants);REMOTING;HASHTABLE + + + + $(DefineConstants);ASYNCLOCAL;HASHTABLE + + + + $(DefineConstants);ASYNCLOCAL;HASHTABLE + + + diff --git a/src/Serilog/Serilog.xproj b/src/Serilog/Serilog.xproj deleted file mode 100644 index 865914c3d..000000000 --- a/src/Serilog/Serilog.xproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 803cd13a-d54b-4cec-a55f-e22ae3d93b3c - Serilog - .\obj - .\bin\ - - 2.0 - - - diff --git a/src/Serilog/project.json b/src/Serilog/project.json deleted file mode 100644 index 4d8c226fb..000000000 --- a/src/Serilog/project.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "version": "2.4.0-*", - - "description": "Simple .NET logging with fully-structured events", - "authors": [ "Serilog Contributors" ], - - "packOptions": { - "tags": [ "serilog", "logging", "semantic", "structured" ], - "projectUrl": "http://serilog.net", - "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", - "iconUrl": "http://serilog.net/images/serilog-nuget.png" - }, - - "buildOptions": { - "keyFile": "../../assets/Serilog.snk", - "xmlDoc": true - }, - - "frameworks": { - "net4.5": { - "buildOptions": { - "define": [ "REMOTING", "HASHTABLE" ] - } - }, - "net4.6": { - "buildOptions": { - "define": [ "ASYNCLOCAL", "HASHTABLE" ] - } - }, - "netstandard1.0": { - "dependencies": { - "Microsoft.CSharp": "4.0.1", - "System.Collections": "4.0.11", - "System.Dynamic.Runtime": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Text.RegularExpressions": "4.1.0", - "System.Threading": "4.0.11" - } - }, - "netstandard1.3": { - "buildOptions": { - "define": [ "ASYNCLOCAL", "HASHTABLE" ] - }, - "dependencies": { - "Microsoft.CSharp": "4.0.1", - "System.Collections": "4.0.11", - "System.Collections.NonGeneric": "4.0.1", - "System.Dynamic.Runtime": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Reflection": "4.1.0", - "System.Reflection.Extensions": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Text.RegularExpressions": "4.1.0", - "System.Threading": "4.0.11" - } - } - } -} diff --git a/test/Serilog.PerformanceTests/AllocationsBenchmark.cs b/test/Serilog.PerformanceTests/AllocationsBenchmark.cs new file mode 100644 index 000000000..1805e01b4 --- /dev/null +++ b/test/Serilog.PerformanceTests/AllocationsBenchmark.cs @@ -0,0 +1,107 @@ +using Serilog.Events; +using Serilog.Parsing; +using Serilog.Core.Enrichers; + +using System; +using System.Linq; +using System.Collections.Generic; + +using BenchmarkDotNet.Attributes; + +namespace Serilog.PerformanceTests +{ + [MemoryDiagnoser] + public class AllocationsBenchmark + { + readonly ILogger _logger; + readonly ILogger _enrichedLogger; + readonly LogEvent _emptyEvent; + readonly object _dictionaryValue; + readonly object _anonymousObject; + readonly object _sequence; + + public AllocationsBenchmark() + { + _logger = new LoggerConfiguration().CreateLogger(); + + _enrichedLogger = _logger.ForContext(new PropertyEnricher("Prop", "Value")); + + _emptyEvent = new LogEvent( + DateTimeOffset.Now, + LogEventLevel.Information, + null, + new MessageTemplate(Enumerable.Empty()), + Enumerable.Empty()); + + _anonymousObject = new + { + Level11 = "Val1", + Level12 = new + { + Level21 = (int?)42, + Level22 = new + { + Level31 = System.Reflection.BindingFlags.FlattenHierarchy, + Level32 = new + { + X = 3, + Y = "4", + Z = (short?)5 + } + } + } + }; + + _dictionaryValue = new Dictionary { + { "Level11", "Val1" }, + { "Level12", new Dictionary() { + { "Level21", (int?)42 }, + { "Level22", new Dictionary() { + { "Level31", System.Reflection.BindingFlags.FlattenHierarchy }, + { "Level32", new { X = 3, Y = "4", Z = (short?)5 } } + } + } + } + } + }; + + _sequence = new List { "1", 2, (int?)3, "4", (short)5 }; + } + + [Benchmark(Baseline = true)] + public void LogEmpty() + { + _logger.Write(_emptyEvent); + } + + [Benchmark] + public void LogEmptyWithEnricher() + { + _enrichedLogger.Write(_emptyEvent); + } + + [Benchmark] + public void LogScalar() + { + _logger.Information("Template: {ScalarValue}", "42"); + } + + [Benchmark] + public void LogDictionary() + { + _logger.Information("Template: {DictionaryValue}", _dictionaryValue); + } + + [Benchmark] + public void LogSequence() + { + _logger.Information("Template: {SequenceValue}", _sequence); + } + + [Benchmark] + public void LogAnonymous() + { + _logger.Information("Template: {@AnonymousObject}.", _anonymousObject); + } + } +} diff --git a/test/Serilog.PerformanceTests/Harness.cs b/test/Serilog.PerformanceTests/Harness.cs index 6b9a0dd1e..dd283c70f 100644 --- a/test/Serilog.PerformanceTests/Harness.cs +++ b/test/Serilog.PerformanceTests/Harness.cs @@ -1,5 +1,5 @@ -// Copyright 2013-2016 Serilog Contributors +// Copyright 2013-2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +20,13 @@ namespace Serilog.PerformanceTests { public class Harness { + // dotnet test -c Release -f net46 --filter "FullyQualifiedName=Serilog.PerformanceTests.Harness.AllocationsBenchmark" + [Fact] + public void AllocationsBenchmark() + { + BenchmarkRunner.Run(); + } + [Fact] public void MessageTemplateCacheBenchmark() { @@ -62,5 +69,17 @@ public void Pipeline() { BenchmarkRunner.Run(); } + + [Fact] + public void OutputTemplateRendering() + { + BenchmarkRunner.Run(); + } + + [Fact] + public void MessageTemplateRenderingBenchmark() + { + BenchmarkRunner.Run(); + } } } \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/LevelControlBenchmark.cs b/test/Serilog.PerformanceTests/LevelControlBenchmark.cs index 899212357..6f29e9883 100644 --- a/test/Serilog.PerformanceTests/LevelControlBenchmark.cs +++ b/test/Serilog.PerformanceTests/LevelControlBenchmark.cs @@ -1,11 +1,7 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Serilog; using Serilog.Core; using Serilog.Events; -using System; using Serilog.PerformanceTests.Support; -using Xunit; namespace Serilog.PerformanceTests { diff --git a/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs b/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs index c353fccfb..e7ea19867 100644 --- a/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs +++ b/test/Serilog.PerformanceTests/LogContextEnrichmentBenchmark.cs @@ -1,10 +1,6 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Serilog; using Serilog.Context; -using System; using Serilog.PerformanceTests.Support; -using Xunit; using Serilog.Events; namespace Serilog.PerformanceTests diff --git a/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs b/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs index d9c3fcfe3..e5ae2fd9b 100644 --- a/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs +++ b/test/Serilog.PerformanceTests/MessageTemplateParsingBenchmark.cs @@ -1,19 +1,12 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Serilog; -using Serilog.Events; using Serilog.Parsing; -using System; -using System.Linq; -using System.Collections; -using System.Collections.Generic; -using Xunit; namespace Serilog.PerformanceTests { /// /// Tests the cost of parsing various message templates. /// + [MemoryDiagnoser] public class MessageTemplateParsingBenchmark { MessageTemplateParser _parser; diff --git a/test/Serilog.PerformanceTests/MessageTemplateRenderingBenchmark.cs b/test/Serilog.PerformanceTests/MessageTemplateRenderingBenchmark.cs new file mode 100644 index 000000000..486b79e8d --- /dev/null +++ b/test/Serilog.PerformanceTests/MessageTemplateRenderingBenchmark.cs @@ -0,0 +1,35 @@ +using BenchmarkDotNet.Attributes; +using System.IO; +using Serilog.Events; +using Serilog.PerformanceTests.Support; + +namespace Serilog.PerformanceTests +{ + /// + /// Determines the cost of rendering a message template. + /// + [MemoryDiagnoser] + public class MessageTemplateRenderingBenchmark + { + static readonly LogEvent NoProperties = + Some.InformationEvent("This template has no properties"); + + static readonly LogEvent VariedProperties = + Some.InformationEvent("Processed {@Position} for {Task} in {Elapsed:000} ms", + new { Latitude = 25, Longitude = 134 }, "Benchmark", 34); + + readonly TextWriter _output = new NullTextWriter(); + + [Benchmark] + public void TemplateWithNoProperties() + { + NoProperties.MessageTemplate.Render(NoProperties.Properties, _output); + } + + [Benchmark] + public void TemplateWithVariedProperties() + { + VariedProperties.MessageTemplate.Render(VariedProperties.Properties, _output); + } + } +} diff --git a/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs b/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs index 537a60371..578fd6de3 100644 --- a/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs +++ b/test/Serilog.PerformanceTests/NestedLoggerLatencyBenchmark.cs @@ -1,9 +1,5 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Serilog; -using System; using Serilog.PerformanceTests.Support; -using Xunit; using Serilog.Events; namespace Serilog.PerformanceTests diff --git a/test/Serilog.PerformanceTests/OutputTemplateRenderingBenchmark.cs b/test/Serilog.PerformanceTests/OutputTemplateRenderingBenchmark.cs new file mode 100644 index 000000000..60310df85 --- /dev/null +++ b/test/Serilog.PerformanceTests/OutputTemplateRenderingBenchmark.cs @@ -0,0 +1,29 @@ +using BenchmarkDotNet.Attributes; +using System.Globalization; +using System.IO; +using Serilog.Events; +using Serilog.Formatting.Display; +using Serilog.PerformanceTests.Support; + +namespace Serilog.PerformanceTests +{ + /// + /// Determines the cost of rendering an event out to one of the typical text targets, + /// like the console or a text file. + /// + [MemoryDiagnoser] + public class OutputTemplateRenderingBenchmark + { + const string DefaultFileOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}"; + static readonly LogEvent HelloWorldEvent = Some.InformationEvent("Hello, {Name}", "World"); + static readonly MessageTemplateTextFormatter Formatter = new MessageTemplateTextFormatter(DefaultFileOutputTemplate, CultureInfo.InvariantCulture); + + readonly TextWriter _output = new NullTextWriter(); + + [Benchmark] + public void FormatToOutput() + { + Formatter.Format(HelloWorldEvent, _output); + } + } +} diff --git a/test/Serilog.PerformanceTests/PipelineBenchmark.cs b/test/Serilog.PerformanceTests/PipelineBenchmark.cs index 6812f2345..784dd97a8 100644 --- a/test/Serilog.PerformanceTests/PipelineBenchmark.cs +++ b/test/Serilog.PerformanceTests/PipelineBenchmark.cs @@ -14,22 +14,15 @@ // limitations under the License. using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Running; -using Serilog; -using Serilog.Events; -using Serilog.Parsing; using System; -using System.Linq; -using System.Collections; -using System.Collections.Generic; using Serilog.PerformanceTests.Support; -using Xunit; namespace Serilog.PerformanceTests { /// /// Tests the cost of writing through the logging pipeline. /// + [MemoryDiagnoser] public class PipelineBenchmark { ILogger _log; diff --git a/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj b/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj new file mode 100644 index 000000000..dcb0d41ae --- /dev/null +++ b/test/Serilog.PerformanceTests/Serilog.PerformanceTests.csproj @@ -0,0 +1,28 @@ + + + netcoreapp1.1;net46 + Serilog.PerformanceTests + ../../assets/Serilog.snk + true + true + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/Serilog.PerformanceTests.xproj b/test/Serilog.PerformanceTests/Serilog.PerformanceTests.xproj deleted file mode 100644 index e84267546..000000000 --- a/test/Serilog.PerformanceTests/Serilog.PerformanceTests.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - d7a37f73-bba3-4dae-9648-1a753a86f968 - Serilog.PerformanceTests - .\obj - .\bin\ - - - 2.0 - - - - - - \ No newline at end of file diff --git a/test/Serilog.PerformanceTests/Support/NullTextWriter.cs b/test/Serilog.PerformanceTests/Support/NullTextWriter.cs new file mode 100644 index 000000000..680eac502 --- /dev/null +++ b/test/Serilog.PerformanceTests/Support/NullTextWriter.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using System.Text; + +namespace Serilog.PerformanceTests.Support +{ + class NullTextWriter : TextWriter + { + public override void Write(char value) + { + } + + public override Encoding Encoding { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + public override void Write(bool value) + { + } + + public override void Write(char[] buffer) + { + } + + public override void Write(char[] buffer, int index, int count) + { + } + + public override void Write(decimal value) + { + } + + public override void Write(double value) + { + } + + public override void Write(int value) + { + } + + public override void Write(long value) + { + } + + public override void Write(object value) + { + } + + public override void Write(float value) + { + } + + public override void Write(string value) + { + } + + public override void Write(string format, object arg0) + { + } + + public override void Write(string format, object arg0, object arg1) + { + } + + public override void Write(string format, object arg0, object arg1, object arg2) + { + } + + public override void Write(string format, params object[] arg) + { + } + + public override void Write(uint value) + { + } + + public override void Write(ulong value) + { + } + + public override string ToString() + { + return String.Empty; + } + + public override void Flush() + { + } + + public override void WriteLine() + { + } + + public override void WriteLine(bool value) + { + } + + public override void WriteLine(char value) + { + } + + public override void WriteLine(char[] buffer) + { + } + + public override void WriteLine(char[] buffer, int index, int count) + { + } + + public override void WriteLine(decimal value) + { + } + + public override void WriteLine(double value) + { + } + + public override void WriteLine(int value) + { + } + + public override void WriteLine(long value) + { + } + + public override void WriteLine(object value) + { + } + + public override void WriteLine(float value) + { + } + + public override void WriteLine(string value) + { + } + + public override void WriteLine(string format, object arg0) + { + } + + public override void WriteLine(string format, object arg0, object arg1) + { + } + + public override void WriteLine(string format, object arg0, object arg1, object arg2) + { + } + + public override void WriteLine(string format, params object[] arg) + { + } + + public override void WriteLine(uint value) + { + } + + public override void WriteLine(ulong value) + { + } + } +} diff --git a/test/Serilog.PerformanceTests/Support/Some.cs b/test/Serilog.PerformanceTests/Support/Some.cs index 07fbf5aab..655eedd7e 100644 --- a/test/Serilog.PerformanceTests/Support/Some.cs +++ b/test/Serilog.PerformanceTests/Support/Some.cs @@ -1,19 +1,17 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; +using System; using Serilog.Events; -using Serilog.Parsing; namespace Serilog.PerformanceTests.Support { static class Some { - public static LogEvent InformationEvent() + public static LogEvent InformationEvent(string messageTemplate = "Hello, world!", params object[] propertyValues) { - return new LogEvent(DateTime.Now, LogEventLevel.Information, - null, new MessageTemplate(Enumerable.Empty()), Enumerable.Empty()); + var logger = new LoggerConfiguration().CreateLogger(); +#pragma warning disable Serilog004 // Constant MessageTemplate verifier + logger.BindMessageTemplate(messageTemplate, propertyValues, out var parsedTemplate, out var boundProperties); +#pragma warning restore Serilog004 // Constant MessageTemplate verifier + return new LogEvent(DateTime.Now, LogEventLevel.Information, null, parsedTemplate, boundProperties); } } diff --git a/test/Serilog.PerformanceTests/project.json b/test/Serilog.PerformanceTests/project.json deleted file mode 100755 index 70bc3da6f..000000000 --- a/test/Serilog.PerformanceTests/project.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "testRunner": "xunit", - - "dependencies": { - "Serilog": { "target": "project" }, - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-rc2-build10025", - "BenchmarkDotNet": "0.9.9" - }, - "buildOptions": { - "keyFile": "../../assets/Serilog.snk" - }, - "frameworks": { - "netcoreapp1.0": { - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.0" - }, - "System.Collections": "4.0.11" - }, - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - }, - "net4.5.2": { - } - } -} diff --git a/test/Serilog.PerformanceTests/xunit.runner.json b/test/Serilog.PerformanceTests/xunit.runner.json new file mode 100644 index 000000000..34b2fe2cd --- /dev/null +++ b/test/Serilog.PerformanceTests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": false +} \ No newline at end of file diff --git a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs b/test/Serilog.Tests/Capturing/PropertyValueConverterTests.cs similarity index 67% rename from test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs rename to test/Serilog.Tests/Capturing/PropertyValueConverterTests.cs index 025f1e979..6534a35ef 100644 --- a/test/Serilog.Tests/Parameters/PropertyValueConverterTests.cs +++ b/test/Serilog.Tests/Capturing/PropertyValueConverterTests.cs @@ -1,20 +1,79 @@ using System; using System.Collections.Generic; using System.Linq; -using Xunit; +using Serilog.Capturing; +using System.Threading.Tasks; +using System.Threading; + + using Serilog.Core; using Serilog.Events; -using Serilog.Parameters; using Serilog.Parsing; using Serilog.Tests.Support; +using Xunit; + +// ReSharper disable UnusedAutoPropertyAccessor.Global, UnusedParameter.Local -namespace Serilog.Tests.Parameters +namespace Serilog.Tests.Capturing { public class PropertyValueConverterTests { readonly PropertyValueConverter _converter = new PropertyValueConverter(10, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false); + [Fact] + public async Task MaximumDepthIsEffectiveAndThreadSafe() + { + var _converter = new PropertyValueConverter(3, 1000, 1000, Enumerable.Empty(), Enumerable.Empty(), false); + + var barrier = new Barrier(participantCount: 3); + + var t1 = + Task.Run(() => DoThreadTest(new { Root = new { B = new { C = new { D = new { E = "F" } } } } }, + result => + { + Assert.Contains("B", result); + Assert.Contains("C", result); + Assert.DoesNotContain("D", result); + Assert.DoesNotContain("E", result); + })); + + var t2 = + Task.Run(() => DoThreadTest(new { Root = new { Y = new { Z = "5" } } }, + result => + { + Assert.Contains("Y", result); + Assert.Contains("Z", result); + })); + + var t3 = + Task.Run(() => DoThreadTest(new { Root = new { M = new { N = new { V = 8 } } } }, + result => + { + Assert.Contains("M", result); + Assert.Contains("N", result); + Assert.DoesNotContain("V", result); + })); + + await Task.WhenAll(t1, t2, t3); + + void DoThreadTest(object logObject, Action assertAction) + { + for (var i = 0; i < 100; ++i) + { + barrier.SignalAndWait(); + + var propValue = _converter.CreatePropertyValue(logObject, true); + + Assert.IsType(propValue); + + var result = ((StructureValue)propValue).Properties.SingleOrDefault(p => p.Name == "Root")?.Value?.ToString(); + + assertAction.Invoke(result); + } + } + } + [Fact] public void UnderDestructuringAByteArrayIsAScalarValue() { @@ -188,7 +247,7 @@ public class BaseWithProps public class DerivedWithOverrides : BaseWithProps { - new public string PropA { get; set; } + public new string PropA { get; set; } public override string PropB { get; set; } public string PropD { get; set; } } @@ -216,7 +275,7 @@ public void NewAndInheritedPropertiesAppearOnlyOnce() class HasIndexer { - public string this[int index] { get { return "Indexer"; } } + public string this[int index] => "Indexer"; } [Fact] @@ -231,7 +290,7 @@ public void IndexerPropertiesAreIgnoredWhenDestructuring() // (reducing garbage). class HasItem { - public string Item { get { return "Item"; } } + public string Item => "Item"; } [Fact] @@ -253,6 +312,55 @@ public void CSharpAnonymousTypesAreRecognizedWhenDestructuring() var structuredValue = (StructureValue)result; Assert.Equal(null, structuredValue.TypeTag); } + + [Fact] + public void ValueTuplesAreRecognizedWhenDestructuring() + { + var o = (1, "A", new[] { "B" }); + var result = _converter.CreatePropertyValue(o); + + var sequenceValue = Assert.IsType(result); + + Assert.Equal(3, sequenceValue.Elements.Count); + Assert.Equal(new ScalarValue(1), sequenceValue.Elements[0]); + Assert.Equal(new ScalarValue("A"), sequenceValue.Elements[1]); + var nested = Assert.IsType(sequenceValue.Elements[2]); + Assert.Equal(1, nested.Elements.Count); + Assert.Equal(new ScalarValue("B"), nested.Elements[0]); + } + + [Fact] + public void AllTupleLengthsUpToSevenAreSupportedForCapturing() + { + var tuples = new object[] + { + ValueTuple.Create(1), + (1, 2), + (1, 2, 3), + (1, 2, 3, 4), + (1, 2, 3, 4, 5), + (1, 2, 3, 4, 5, 6), + (1, 2, 3, 4, 5, 6, 7) + }; + + foreach (var t in tuples) + Assert.IsType(_converter.CreatePropertyValue(t)); + } + + [Fact] + public void EightPlusValueTupleElementsAreIgnoredByCapturing() + { + var scalar = _converter.CreatePropertyValue((1, 2, 3, 4, 5, 6, 7, 8)); + Assert.IsType(scalar); + } + + [Fact] + public void ValueTupleDestructuringIsTransitivelyApplied() + { + var tuple = _converter.CreatePropertyValue(ValueTuple.Create(new {A = 1}), true); + var sequence = Assert.IsType(tuple); + Assert.IsType(sequence.Elements[0]); + } } } diff --git a/test/Serilog.Tests/Context/LogContextTests.cs b/test/Serilog.Tests/Context/LogContextTests.cs index 17987f2ad..1020d439b 100644 --- a/test/Serilog.Tests/Context/LogContextTests.cs +++ b/test/Serilog.Tests/Context/LogContextTests.cs @@ -12,6 +12,7 @@ #endif using System.Threading; using System.Threading.Tasks; +using Serilog.Core; namespace Serilog.Tests.Context { @@ -24,6 +25,81 @@ public LogContextTests() #endif } + [Fact] + public void PushedPropertiesAreAvailableToLoggers() + { + LogEvent lastEvent = null; + + var log = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Sink(new DelegatingSink(e => lastEvent = e)) + .CreateLogger(); + + using (LogContext.PushProperty("A", 1)) + using (LogContext.Push(new PropertyEnricher("B", 2))) + using (LogContext.Push(new PropertyEnricher("C", 3), new PropertyEnricher("D", 4))) // Different overload + { + log.Write(Some.InformationEvent()); + Assert.Equal(1, lastEvent.Properties["A"].LiteralValue()); + Assert.Equal(2, lastEvent.Properties["B"].LiteralValue()); + Assert.Equal(3, lastEvent.Properties["C"].LiteralValue()); + Assert.Equal(4, lastEvent.Properties["D"].LiteralValue()); + } + } + + [Fact] + public void LogContextCanBeCloned() + { + LogEvent lastEvent = null; + + var log = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Sink(new DelegatingSink(e => lastEvent = e)) + .CreateLogger(); + + ILogEventEnricher clonedContext; + using (LogContext.PushProperty("A", 1)) + { + clonedContext = LogContext.Clone(); + } + + using (LogContext.Push(clonedContext)) + { + log.Write(Some.InformationEvent()); + Assert.Equal(1, lastEvent.Properties["A"].LiteralValue()); + } + } + + [Fact] + public void ClonedLogContextCanSharedAcrossThreads() + { + LogEvent lastEvent = null; + + var log = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Sink(new DelegatingSink(e => lastEvent = e)) + .CreateLogger(); + + ILogEventEnricher clonedContext; + using (LogContext.PushProperty("A", 1)) + { + clonedContext = LogContext.Clone(); + } + + var t = new Thread(() => + { + using (LogContext.Push(clonedContext)) + { + log.Write(Some.InformationEvent()); + } + }); + + t.Start(); + t.Join(); + + Assert.Equal(1, lastEvent.Properties["A"].LiteralValue()); + } + [Fact] public void MoreNestedPropertiesOverrideLessNestedOnes() { @@ -63,13 +139,13 @@ public void MultipleNestedPropertiesOverrideLessNestedOnes() .WriteTo.Sink(new DelegatingSink(e => lastEvent = e)) .CreateLogger(); - using (LogContext.PushProperties(new PropertyEnricher("A1", 1), new PropertyEnricher("A2", 2))) + using (LogContext.Push(new PropertyEnricher("A1", 1), new PropertyEnricher("A2", 2))) { log.Write(Some.InformationEvent()); Assert.Equal(1, lastEvent.Properties["A1"].LiteralValue()); Assert.Equal(2, lastEvent.Properties["A2"].LiteralValue()); - using (LogContext.PushProperties(new PropertyEnricher("A1", 10), new PropertyEnricher("A2", 20))) + using (LogContext.Push(new PropertyEnricher("A1", 10), new PropertyEnricher("A2", 20))) { log.Write(Some.InformationEvent()); Assert.Equal(10, lastEvent.Properties["A1"].LiteralValue()); @@ -116,32 +192,13 @@ public async Task ContextPropertiesCrossAsyncCalls() #if APPDOMAIN // Must not actually try to pass context across domains, // since user property types may not be serializable. - [Fact(Skip = "Needs to be updated for dotnet runner.")] + [Fact] public void DoesNotPreventCrossDomainCalls() { - var projectRoot = Environment.CurrentDirectory; - while (!File.Exists(Path.Combine(projectRoot, "global.json"))) - { - projectRoot = Directory.GetParent(projectRoot).FullName; - } - AppDomain domain = null; try { - const string configuration = -#if DEBUG - "Debug"; -#else - "Release"; -#endif - - var domaininfo = new AppDomainSetup - { - ApplicationBase = projectRoot, - PrivateBinPath = @"test\Serilog.Tests\bin\Debug\net452\win7-x64".Replace("Debug", configuration) - }; - var evidence = AppDomain.CurrentDomain.Evidence; - domain = AppDomain.CreateDomain("LogContextTest", evidence, domaininfo); + domain = AppDomain.CreateDomain("LogContextTests", null, AppDomain.CurrentDomain.SetupInformation); var callable = (RemotelyCallable)domain.CreateInstanceAndUnwrap(typeof(RemotelyCallable).Assembly.FullName, typeof(RemotelyCallable).FullName); diff --git a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs index 07c19f093..8e9f05837 100644 --- a/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs +++ b/test/Serilog.Tests/Core/LogEventPropertyCapturingTests.cs @@ -1,10 +1,10 @@ using System; using System.Linq; using System.Collections.Generic; +using Serilog.Capturing; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; -using Serilog.Parameters; using Serilog.Parsing; using Serilog.Tests.Support; using Xunit; diff --git a/test/Serilog.Tests/Core/MessageTemplateTests.cs b/test/Serilog.Tests/Core/MessageTemplateTests.cs index b76185d36..8ae5cdb33 100755 --- a/test/Serilog.Tests/Core/MessageTemplateTests.cs +++ b/test/Serilog.Tests/Core/MessageTemplateTests.cs @@ -3,9 +3,9 @@ using System.IO; using System.Linq; using System.Text; +using Serilog.Capturing; using Xunit; using Serilog.Core; -using Serilog.Parameters; using MessageTemplateParser = Serilog.Parsing.MessageTemplateParser; namespace Serilog.Tests.Core diff --git a/test/Serilog.Tests/Core/SecondaryLoggerSinkTests.cs b/test/Serilog.Tests/Core/SecondaryLoggerSinkTests.cs index 0408235ed..c84007bf7 100644 --- a/test/Serilog.Tests/Core/SecondaryLoggerSinkTests.cs +++ b/test/Serilog.Tests/Core/SecondaryLoggerSinkTests.cs @@ -1,5 +1,4 @@ -using System; -using Serilog.Tests.Support; +using Serilog.Tests.Support; using Xunit; using Serilog.Core; using Serilog.Events; @@ -37,9 +36,10 @@ public void WhenOwnedByCallerSecondaryLoggerIsNotDisposed() .WriteTo.Sink(secondary) .CreateLogger(); - ((IDisposable)new LoggerConfiguration() + new LoggerConfiguration() .WriteTo.Logger(secondaryLogger) - .CreateLogger()).Dispose(); + .CreateLogger() + .Dispose(); Assert.False(secondary.IsDisposed); } @@ -49,9 +49,10 @@ public void WhenOwnedByPrimaryLoggerSecondaryIsDisposed() { var secondary = new DisposeTrackingSink(); - ((IDisposable)new LoggerConfiguration() + new LoggerConfiguration() .WriteTo.Logger(lc => lc.WriteTo.Sink(secondary)) - .CreateLogger()).Dispose(); + .CreateLogger() + .Dispose(); Assert.True(secondary.IsDisposed); } diff --git a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs index 534351075..76d9dfe9f 100644 --- a/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs +++ b/test/Serilog.Tests/Events/LogEventPropertyValueTests.cs @@ -15,10 +15,10 @@ using System; using System.Globalization; using System.Linq; +using Serilog.Capturing; using Xunit; using Serilog.Core; using Serilog.Events; -using Serilog.Parameters; using Serilog.Parsing; using Serilog.Tests.Support; diff --git a/test/Serilog.Tests/Formatting/Display/MessageTemplateTextFormatterTests.cs b/test/Serilog.Tests/Formatting/Display/MessageTemplateTextFormatterTests.cs index b5a24855b..c5e4a3d82 100644 --- a/test/Serilog.Tests/Formatting/Display/MessageTemplateTextFormatterTests.cs +++ b/test/Serilog.Tests/Formatting/Display/MessageTemplateTextFormatterTests.cs @@ -198,5 +198,54 @@ public void AppliesCustomFormatterToEnums() formatter.Format(evt, sw); Assert.Equal("Size Huge", sw.ToString()); } + + [Fact] + public void NonMessagePropertiesAreRendered() + { + var formatter = new MessageTemplateTextFormatter("{Properties}", CultureInfo.InvariantCulture); + var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Foo", 42).Information("Hello from {Bar}!", "bar")); + var sw = new StringWriter(); + formatter.Format(evt, sw); + Assert.Equal("{Foo: 42}", sw.ToString()); + } + + [Fact] + public void DoNotDuplicatePropertiesAlreadyRenderedInOutputTemplate() + { + var formatter = new MessageTemplateTextFormatter("{Foo} {Properties}", CultureInfo.InvariantCulture); + var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Foo", 42).ForContext("Bar", 42).Information("Hello from bar!")); + var sw = new StringWriter(); + formatter.Format(evt, sw); + Assert.Equal("42 {Bar: 42}", sw.ToString()); + } + + [Theory] + [InlineData("", "Hello, \"World\"!")] + [InlineData(":j", "Hello, \"World\"!")] + [InlineData(":l", "Hello, World!")] + [InlineData(":lj", "Hello, World!")] + [InlineData(":jl", "Hello, World!")] + public void AppliesLiteralFormattingToMessageStringsWhenSpecified(string format, string expected) + { + var formatter = new MessageTemplateTextFormatter("{Message" + format + "}", null); + var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello, {Name}!", "World")); + var sw = new StringWriter(); + formatter.Format(evt, sw); + Assert.Equal(expected, sw.ToString()); + } + + [Theory] + [InlineData("", "{ Name: \"World\" }")] + [InlineData(":j", "{\"Name\":\"World\"}")] + [InlineData(":lj", "{\"Name\":\"World\"}")] + [InlineData(":jl", "{\"Name\":\"World\"}")] + public void AppliesJsonFormattingToMessageStructuresWhenSpecified(string format, string expected) + { + var formatter = new MessageTemplateTextFormatter("{Message" + format + "}", null); + var evt = DelegatingSink.GetLogEvent(l => l.Information("{@Obj}", new {Name = "World"})); + var sw = new StringWriter(); + formatter.Format(evt, sw); + Assert.Equal(expected, sw.ToString()); + } } } diff --git a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs index 45e22324a..84aca1af5 100644 --- a/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs +++ b/test/Serilog.Tests/Formatting/Json/JsonValueFormatterTests.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Serilog.Events; using Serilog.Formatting.Json; -using Serilog.Tests.Events; -using Serilog.Tests.Support; using Xunit; namespace Serilog.Tests.Formatting.Json diff --git a/test/Serilog.Tests/LoggerConfigurationTests.cs b/test/Serilog.Tests/LoggerConfigurationTests.cs index f7bc7c94f..b94d28570 100644 --- a/test/Serilog.Tests/LoggerConfigurationTests.cs +++ b/test/Serilog.Tests/LoggerConfigurationTests.cs @@ -1,15 +1,14 @@ using System; -using System.IO; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices.ComTypes; using Xunit; -using Serilog.Configuration; using Serilog.Core; using Serilog.Core.Filters; +using Serilog.Debugging; using Serilog.Events; using Serilog.Tests.Support; +using TestDummies; namespace Serilog.Tests { @@ -118,7 +117,7 @@ public void DestructuringSystemTypeGivesScalarByDefault() .WriteTo.Sink(sink) .CreateLogger(); - var thisType = this.GetType(); + var thisType = GetType(); logger.Information("{@thisType}", thisType); var ev = events.Single(); @@ -169,7 +168,7 @@ public void DestructuringIsPossibleForSystemTypeDerivedProperties() .WriteTo.Sink(sink) .CreateLogger(); - var thisType = this.GetType(); + var thisType = GetType(); logger.Information("{@thisType}", thisType); var ev = events.Single(); @@ -266,10 +265,10 @@ public void MaximumDestructuringDepthIsEffective() } }; - var xs = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumStringLength(3), "@"); + var xs = LogAndGetAsString(x, conf => conf.Destructure.ToMaximumDepth(3), "@"); Assert.Contains("C", xs); - Assert.DoesNotContain(xs, "D"); + Assert.DoesNotContain("D", xs); } [Fact] @@ -493,7 +492,7 @@ public void HigherMinimumLevelOverridesArePropagated() .CreateLogger(); logger.Write(Some.InformationEvent()); - logger.ForContext(Serilog.Core.Constants.SourceContextPropertyName, "Microsoft.AspNet.Something").Write(Some.InformationEvent()); + logger.ForContext(Constants.SourceContextPropertyName, "Microsoft.AspNet.Something").Write(Some.InformationEvent()); logger.ForContext().Write(Some.InformationEvent()); Assert.Equal(2, sink.Events.Count); @@ -511,7 +510,7 @@ public void LowerMinimumLevelOverridesArePropagated() .CreateLogger(); logger.Write(Some.InformationEvent()); - logger.ForContext(Serilog.Core.Constants.SourceContextPropertyName, "Microsoft.AspNet.Something").Write(Some.InformationEvent()); + logger.ForContext(Constants.SourceContextPropertyName, "Microsoft.AspNet.Something").Write(Some.InformationEvent()); logger.ForContext().Write(Some.InformationEvent()); Assert.Equal(1, sink.Events.Count); @@ -521,7 +520,7 @@ public void LowerMinimumLevelOverridesArePropagated() public void ExceptionsThrownBySinksAreNotPropagated() { var logger = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(e => { throw new Exception("Boom!"); })) + .WriteTo.Sink(new DelegatingSink(e => throw new Exception("Boom!"))) .CreateLogger(); logger.Write(Some.InformationEvent()); @@ -577,5 +576,33 @@ public string Property get { throw new Exception("Boom!"); } } } + + [Fact] + public void WrappingDecoratesTheConfiguredSink() + { + var sink = new CollectingSink(); + var logger = new LoggerConfiguration() + .WriteTo.Dummy(w => w.Sink(sink)) + .CreateLogger(); + + logger.Write(Some.InformationEvent()); + + Assert.NotEmpty(DummyWrappingSink.Emitted); + Assert.NotEmpty(sink.Events); + } + + [Fact] + public void WrappingWarnsAboutNonDisposableWrapper() + { + var messages = new List(); + SelfLog.Enable(s => messages.Add(s)); + + new LoggerConfiguration() + .WriteTo.Dummy(w => w.Sink()) + .CreateLogger(); + + SelfLog.Disable(); + Assert.NotEmpty(messages); + } } } diff --git a/test/Serilog.Tests/MethodOverloadConventionTests.cs b/test/Serilog.Tests/MethodOverloadConventionTests.cs index c634f46e0..1c03f55f6 100644 --- a/test/Serilog.Tests/MethodOverloadConventionTests.cs +++ b/test/Serilog.Tests/MethodOverloadConventionTests.cs @@ -10,6 +10,10 @@ using System.Text.RegularExpressions; using Xunit; using Xunit.Sdk; +// ReSharper disable PossibleMultipleEnumeration +// ReSharper disable UnusedMember.Local +// ReSharper disable UnusedParameter.Local +// ReSharper disable AssignNullToNotNullAttribute namespace Serilog.Tests { @@ -97,10 +101,9 @@ public void ValidateWriteEventLogMethods(Type loggerType) Assert.True(writeMethod.IsPublic); Assert.Equal(writeMethod.ReturnType, typeof(void)); - LogEventLevel level = LogEventLevel.Information; + var level = LogEventLevel.Information; CollectingSink sink; - var logger = GetLogger(loggerType, out sink); InvokeMethod(writeMethod, logger, new object[] { Some.LogEvent(DateTimeOffset.Now, level) }); @@ -108,8 +111,8 @@ public void ValidateWriteEventLogMethods(Type loggerType) //handle silent logger special case i.e. no result validation if (loggerType == typeof(SilentLogger)) return; - else - EvaluateSingleResult(level, sink); + + EvaluateSingleResult(level, sink); } [Theory] @@ -158,12 +161,10 @@ public void ValidateForContextMethods(Type loggerType) { report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); } - - continue; } } - Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known method or failed invoke\n" + report.ToString()); + Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known method or failed invoke\n" + report); } } @@ -185,7 +186,7 @@ public void ValidateBindMessageTemplateMethods(Type loggerType) Assert.Equal(messageTemplateAttr.MessageTemplateParameterName, MessageTemplate); var parameters = method.GetParameters(); - int index = 0; + var index = 0; Assert.Equal(parameters[index].Name, "messageTemplate"); Assert.Equal(parameters[index].ParameterType, typeof(string)); @@ -203,7 +204,6 @@ public void ValidateBindMessageTemplateMethods(Type loggerType) Assert.Equal(parameters[index].Name, "boundProperties"); Assert.Equal(parameters[index].ParameterType, typeof(IEnumerable).MakeByRefType()); Assert.True(parameters[index].IsOut); - index++; var logger = GetLogger(loggerType); @@ -242,7 +242,7 @@ public void ValidateBindPropertyMethods(Type loggerType) Assert.True(method.IsPublic); var parameters = method.GetParameters(); - int index = 0; + var index = 0; Assert.Equal(parameters[index].Name, "propertyName"); Assert.Equal(parameters[index].ParameterType, typeof(string)); @@ -343,7 +343,7 @@ void ForContextMethod0(MethodInfo method) { e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } var logger = GetLogger(method.DeclaringType); @@ -375,7 +375,7 @@ void ForContextMethod1(MethodInfo method) { e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } var logger = GetLogger(method.DeclaringType); @@ -396,7 +396,7 @@ void ForContextMethod2(MethodInfo method) var parameters = method.GetParameters(); Assert.Equal(parameters.Length, 3); - int index = 0; + var index = 0; Assert.Equal(parameters[index].Name, "propertyName"); Assert.Equal(parameters[index].ParameterType, typeof(string)); @@ -414,7 +414,7 @@ void ForContextMethod2(MethodInfo method) { e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } var logger = GetLogger(method.DeclaringType); @@ -464,12 +464,12 @@ void ForContextMethod3(MethodInfo method) { e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } var logger = GetLogger(method.DeclaringType); - var enrichedLogger = InvokeMethod(method, logger, null, new Type[] { typeof(object) }); + var enrichedLogger = InvokeMethod(method, logger, null, new[] { typeof(object) }); Assert.NotNull(enrichedLogger); Assert.True(enrichedLogger is ILogger); @@ -493,7 +493,7 @@ void ForContextMethod4(MethodInfo method) { e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } var logger = GetLogger(method.DeclaringType); @@ -566,7 +566,7 @@ void ValidateConventionForMethodSet( { try { - Action invokeTestMethod = null; + Action invokeTestMethod; if (testInvokeResults) invokeTestMethod = InvokeConventionMethodAndTest; @@ -592,19 +592,17 @@ void ValidateConventionForMethodSet( { report.AppendLine($"{testMethod.Name} Invocation Failure on: {method} with: {xunitException.UserMessage}"); } - - continue; } } - Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known convention or failed invoke\n" + report.ToString()); + Assert.True(signatureMatchAndInvokeSuccess, $"{method} did not match any known convention or failed invoke\n" + report); } } // Method0 (string messageTemplate) : void void ValidateMethod0(MethodInfo method, Action invokeMethod) { - VerifyMethodSignature(method, expectedArgCount: 1); + VerifyMethodSignature(method); var parameters = new object[] { "message" }; @@ -616,7 +614,7 @@ void ValidateMethod1(MethodInfo method, Action inv { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 2); - var typeArgs = new Type[] { typeof(string) }; + var typeArgs = new[] { typeof(string) }; var parameters = new object[] { "message", "value0" }; @@ -628,7 +626,7 @@ void ValidateMethod2(MethodInfo method, Action inv { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 3); - var typeArgs = new Type[] { typeof(string), typeof(string) }; + var typeArgs = new[] { typeof(string), typeof(string) }; var parameters = new object[] { @@ -643,7 +641,7 @@ void ValidateMethod3(MethodInfo method, Action inv { VerifyMethodSignature(method, isGeneric: true, expectedArgCount: 4); - var typeArgs = new Type[] { typeof(string), typeof(string), typeof(string) }; + var typeArgs = new[] { typeof(string), typeof(string), typeof(string) }; var parameters = new object[] { @@ -681,7 +679,7 @@ void ValidateMethod6(MethodInfo method, Action inv { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 3); - var typeArgs = new Type[] { typeof(string) }; + var typeArgs = new[] { typeof(string) }; var parameters = new object[] { @@ -696,7 +694,7 @@ void ValidateMethod7(MethodInfo method, Action inv { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 4); - var typeArgs = new Type[] { typeof(string), typeof(string) }; + var typeArgs = new[] { typeof(string), typeof(string) }; var parameters = new object[] { @@ -711,7 +709,7 @@ void ValidateMethod8(MethodInfo method, Action inv { VerifyMethodSignature(method, hasExceptionArg: true, isGeneric: true, expectedArgCount: 5); - var typeArgs = new Type[] { typeof(string), typeof(string), typeof(string) }; + var typeArgs = new[] { typeof(string), typeof(string), typeof(string) }; var parameters = new object[] { @@ -726,7 +724,7 @@ void ValidateMethod9(MethodInfo method, Action inv { VerifyMethodSignature(method, hasExceptionArg: true, expectedArgCount: 3); - object[] parameters = new object[] + var parameters = new object[] { new Exception("test"), "Processed {value0}, {value1}, {value2}", new object[] { "value0", "value1", "value2" } @@ -786,7 +784,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals { var parameters = method.GetParameters(); - int index = 0; + var index = 0; if (method.Name == Write) { @@ -822,7 +820,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals //multiple generic argument convention T0...Tx : T0 propertyValue0... Tx propertyValueX if (genericTypeArgs.Length > 1) { - for (int i = 0; i < genericTypeArgs.Length; i++, index++) + for (var i = 0; i < genericTypeArgs.Length; i++, index++) { Assert.Equal(genericTypeArgs[i].Name, $"T{i}"); @@ -867,7 +865,7 @@ static void VerifyMethodSignature(MethodInfo method, bool hasExceptionArg = fals // mark xunit assertion failures e.Data.Add("IsSignatureAssertionFailure", true); - throw e; + throw; } } @@ -881,13 +879,14 @@ static object InvokeMethod( { if (method.IsGenericMethod) return method.MakeGenericMethod(typeArgs).Invoke(null, parameters); - else - return method.Invoke(null, parameters); + + return method.Invoke(null, parameters); } - else if (method.IsGenericMethod) + + if (method.IsGenericMethod) return method.MakeGenericMethod(typeArgs).Invoke(instance, parameters); - else - return method.Invoke(instance, parameters); + + return method.Invoke(instance, parameters); } static void EvaluateSingleResult(LogEventLevel level, CollectingSink results) @@ -920,7 +919,8 @@ static ILogger GetLogger(Type loggerType, out CollectingSink sink, LogEventLevel .WriteTo.Sink(sink) .CreateLogger(); } - else if (loggerType == typeof(Log)) + + if (loggerType == typeof(Log)) { sink = new CollectingSink(); @@ -933,10 +933,11 @@ static ILogger GetLogger(Type loggerType, out CollectingSink sink, LogEventLevel return null; } - else if (loggerType == typeof(SilentLogger)) + + if (loggerType == typeof(SilentLogger)) return new SilentLogger(); - else - throw new ArgumentException($"Logger Type of {loggerType} is not supported"); + + throw new ArgumentException($"Logger Type of {loggerType} is not supported"); } } } \ No newline at end of file diff --git a/test/Serilog.Tests/Parsing/MessageTemplateParserTests.cs b/test/Serilog.Tests/Parsing/MessageTemplateParserTests.cs index 611e45ad7..fdbe8de05 100644 --- a/test/Serilog.Tests/Parsing/MessageTemplateParserTests.cs +++ b/test/Serilog.Tests/Parsing/MessageTemplateParserTests.cs @@ -164,5 +164,11 @@ public void UnderscoresAreValidInPropertyNames() AssertParsedAs("{_123_Hello}", new PropertyToken("_123_Hello", "{_123_Hello}")); } + [Fact] + public void IndexOutOfRangeExceptionBugHasNotRegressed() + { + var parser = new MessageTemplateParser(); + parser.Parse("{,,}"); + } } } diff --git a/test/Serilog.Tests/Serilog.Tests.csproj b/test/Serilog.Tests/Serilog.Tests.csproj new file mode 100644 index 000000000..1c0981998 --- /dev/null +++ b/test/Serilog.Tests/Serilog.Tests.csproj @@ -0,0 +1,42 @@ + + + netcoreapp1.0;net452;net46 + Serilog.Tests + ../../assets/Serilog.snk + true + true + Serilog.Tests + true + $(PackageTargetFallback);dnxcore50;portable-net45+win8 + + + + + + + + + + + + + + + + + + + + $(DefineConstants);APPDOMAIN;REMOTING;GETCURRENTMETHOD + + + $(DefineConstants);APPDOMAIN;ASYNCLOCAL;GETCURRENTMETHOD + + + + + + + + + \ No newline at end of file diff --git a/test/Serilog.Tests/Serilog.Tests.xproj b/test/Serilog.Tests/Serilog.Tests.xproj deleted file mode 100644 index f620d4de3..000000000 --- a/test/Serilog.Tests/Serilog.Tests.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - 3c2d8e01-5580-426a-bdd9-ec59cd98e618 - Serilog.Tests - .\obj - .\bin\ - - - 2.0 - - - - - - \ No newline at end of file diff --git a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs index 7b57984e8..9725f0374 100644 --- a/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs +++ b/test/Serilog.Tests/Settings/KeyValuePairSettingsTests.cs @@ -6,12 +6,9 @@ using Serilog.Events; using Serilog.Settings.KeyValuePairs; using Serilog.Tests.Support; -using Serilog.Enrichers; using TestDummies; using Serilog.Configuration; using Serilog.Formatting; -using Serilog.Formatting.Json; -using Serilog.Tests.Formatting.Json; namespace Serilog.Tests.Settings { @@ -24,7 +21,7 @@ public void FindsConfigurationAssemblies() // The core Serilog assembly is always considered Assert.Equal(1, configurationAssemblies.Count); - } + } [Fact] public void PropertyEnrichmentIsApplied() @@ -127,30 +124,33 @@ public void AuditSinksAreConfigured() } [Fact] - public void TestMinimumLevelOverrides() { + public void TestMinimumLevelOverrides() + { var settings = new Dictionary + { - ["minimum-level:override:Microsoft"] = "Warning", + ["minimum-level:override:System"] = "Warning", }; LogEvent evt = null; var log = new LoggerConfiguration() - .ReadFrom.KeyValuePairs(settings) - .WriteTo.Sink(new DelegatingSink(e => evt = e)) - .CreateLogger(); + .ReadFrom.KeyValuePairs(settings) + .WriteTo.Sink(new DelegatingSink(e => evt = e)) + .CreateLogger(); - var microsoftLogger = log.ForContext(); - microsoftLogger.Write(Some.InformationEvent()); + var systemLogger = log.ForContext(); + systemLogger.Write(Some.InformationEvent()); Assert.Null(evt); - microsoftLogger.Warning("Bad things"); + systemLogger.Warning("Bad things"); Assert.NotNull(evt); evt = null; log.Write(Some.InformationEvent()); Assert.NotNull(evt); } + } } diff --git a/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs b/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs index 0fca5ec32..3ee1bc118 100644 --- a/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs +++ b/test/Serilog.Tests/Settings/SettingValueConversionsTests.cs @@ -46,7 +46,7 @@ public void ValuesConvertToEnumMembers() [Fact] public void StringValuesConvertToDefaultInstancesIfTargetIsInterface() { - var result = (object)SettingValueConversions.ConvertToType("Serilog.Formatting.Json.JsonFormatter", typeof(ITextFormatter)); + var result = SettingValueConversions.ConvertToType("Serilog.Formatting.Json.JsonFormatter", typeof(ITextFormatter)); Assert.IsType(result); } } diff --git a/test/Serilog.Tests/Support/DisposableLogger.cs b/test/Serilog.Tests/Support/DisposableLogger.cs index 1c46e6132..aa465c1d1 100644 --- a/test/Serilog.Tests/Support/DisposableLogger.cs +++ b/test/Serilog.Tests/Support/DisposableLogger.cs @@ -5,7 +5,7 @@ namespace Serilog.Tests.Support { - public class DisposableLogger : Serilog.ILogger, IDisposable + public class DisposableLogger : ILogger, IDisposable { public bool Disposed { get; set; } diff --git a/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs index e1162ed8f..8b6135bd6 100644 --- a/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs +++ b/test/Serilog.Tests/Support/LogEventPropertyStructuralEqualityComparer.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Serilog.Events; @@ -11,7 +10,7 @@ class LogEventPropertyStructuralEqualityComparer : IEqualityComparer valueEqualityComparer = null) { - this._valueEqualityComparer = + _valueEqualityComparer = valueEqualityComparer ?? new LogEventPropertyValueComparer(EqualityComparer.Default); } diff --git a/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs b/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs index 232b1cf96..b6e57d7f1 100644 --- a/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs +++ b/test/Serilog.Tests/Support/LogEventPropertyValueComparer.cs @@ -11,7 +11,7 @@ class LogEventPropertyValueComparer : IEqualityComparer public LogEventPropertyValueComparer(IEqualityComparer objectEqualityComparer = null) { - this._objectEqualityComparer = objectEqualityComparer ?? EqualityComparer.Default; + _objectEqualityComparer = objectEqualityComparer ?? EqualityComparer.Default; } public bool Equals(LogEventPropertyValue x, LogEventPropertyValue y) diff --git a/test/Serilog.Tests/Support/StringSink.cs b/test/Serilog.Tests/Support/StringSink.cs index e7102af69..5bb088467 100644 --- a/test/Serilog.Tests/Support/StringSink.cs +++ b/test/Serilog.Tests/Support/StringSink.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using Serilog.Core; using Serilog.Events; diff --git a/test/Serilog.Tests/project.json b/test/Serilog.Tests/project.json deleted file mode 100644 index f148cd108..000000000 --- a/test/Serilog.Tests/project.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "testRunner": "xunit", - - "dependencies": { - "Serilog": { "target": "project" }, - "TestDummies": { "target": "project" }, - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-rc2-build10025" - }, - - "buildOptions": { - "keyFile": "../../assets/Serilog.snk" - }, - "frameworks": { - "netcoreapp1.0": { - "define": [ "ASYNCLOCAL" ], - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.0.0" - } - }, - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - }, - "net4.5.2": { - "buildOptions": { - "define": [ "APPDOMAIN", "REMOTING", "GETCURRENTMETHOD" ] - } - }, - "net4.6": { - "buildOptions": { - "define": [ "ASYNCLOCAL", "APPDOMAIN", "GETCURRENTMETHOD" ] - } - } - } -} diff --git a/test/TestDummies/DummyLoggerConfigurationExtensions.cs b/test/TestDummies/DummyLoggerConfigurationExtensions.cs index d49e736c3..a8af6a77d 100644 --- a/test/TestDummies/DummyLoggerConfigurationExtensions.cs +++ b/test/TestDummies/DummyLoggerConfigurationExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Serilog; using Serilog.Events; using Serilog.Formatting; @@ -42,5 +41,15 @@ public static LoggerConfiguration DummyRollingFile( { return loggerSinkConfiguration.Sink(new DummyRollingFileAuditSink(), restrictedToMinimumLevel); } + + public static LoggerConfiguration Dummy( + this LoggerSinkConfiguration loggerSinkConfiguration, + Action wrappedSinkAction) + { + return LoggerSinkConfiguration.Wrap( + loggerSinkConfiguration, + s => new DummyWrappingSink(s), + wrappedSinkAction); + } } } \ No newline at end of file diff --git a/test/TestDummies/DummyThreadIdEnricher.cs b/test/TestDummies/DummyThreadIdEnricher.cs index 142c15282..4ba740eb7 100644 --- a/test/TestDummies/DummyThreadIdEnricher.cs +++ b/test/TestDummies/DummyThreadIdEnricher.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; namespace TestDummies diff --git a/test/TestDummies/DummyWrappingSink.cs b/test/TestDummies/DummyWrappingSink.cs new file mode 100644 index 000000000..9d7f96681 --- /dev/null +++ b/test/TestDummies/DummyWrappingSink.cs @@ -0,0 +1,26 @@ +using System; +using Serilog.Core; +using Serilog.Events; +using System.Collections.Generic; + +namespace TestDummies +{ + public class DummyWrappingSink : ILogEventSink + { + [ThreadStatic] + public static List Emitted = new List(); + + private readonly ILogEventSink _sink; + + public DummyWrappingSink(ILogEventSink sink) + { + _sink = sink; + } + + public void Emit(LogEvent logEvent) + { + Emitted.Add(logEvent); + _sink.Emit(logEvent); + } + } +} \ No newline at end of file diff --git a/test/TestDummies/Properties/AssemblyInfo.cs b/test/TestDummies/Properties/AssemblyInfo.cs index 1f1fc4f85..89032973b 100644 --- a/test/TestDummies/Properties/AssemblyInfo.cs +++ b/test/TestDummies/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/test/TestDummies/TestDummies.csproj b/test/TestDummies/TestDummies.csproj new file mode 100644 index 000000000..5f8ab00dc --- /dev/null +++ b/test/TestDummies/TestDummies.csproj @@ -0,0 +1,24 @@ + + + + net452;netstandard1.3 + TestDummies + ../../assets/Serilog.snk + true + true + TestDummies + false + false + false + + + + + + + + + + + + diff --git a/test/TestDummies/TestDummies.xproj b/test/TestDummies/TestDummies.xproj deleted file mode 100644 index 491d7aa12..000000000 --- a/test/TestDummies/TestDummies.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 2bb12ce5-c867-43bd-ae5d-253fe3248c7f - TestDummies - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/test/TestDummies/project.json b/test/TestDummies/project.json deleted file mode 100644 index 6d498ef45..000000000 --- a/test/TestDummies/project.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "buildOptions": { - "keyFile": "../../assets/Serilog.snk" - }, - - "dependencies": { - "NETStandard.Library": "1.6.0", - "Serilog": { "target": "project" } - }, - - "frameworks": { - "net4.5.2": { }, - "netstandard1.3": {} - } -}