├── .gitattributes ├── .gitignore ├── Build.ps1 ├── LICENSE ├── README.md ├── RunPerfTests.ps1 ├── appveyor.yml ├── assets └── Serilog.snk ├── example └── Sample │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── Sample.csproj ├── serilog-filters-expressions.sln ├── serilog-filters-expressions.sln.DotSettings ├── src └── Serilog.Filters.Expressions │ ├── Filters │ └── Expressions │ │ ├── Ast │ │ ├── FilterArrayExpression.cs │ │ ├── FilterCallExpression.cs │ │ ├── FilterConstantExpression.cs │ │ ├── FilterExpression.cs │ │ ├── FilterExpressionType.cs │ │ ├── FilterLambdaExpression.cs │ │ ├── FilterParameterExpression.cs │ │ ├── FilterPropertyExpression.cs │ │ ├── FilterSubpropertyExpression.cs │ │ ├── FilterTextExpression.cs │ │ ├── FilterTextMatching.cs │ │ ├── FilterWildcard.cs │ │ └── FilterWildcardExpression.cs │ │ ├── Compilation │ │ ├── Arrays │ │ │ └── FilterExpressionConstantArrayEvaluator.cs │ │ ├── CompiledFilterExpression.cs │ │ ├── Costing │ │ │ ├── FilterExpressionCostReordering.cs │ │ │ └── FilterExpressionCosting.cs │ │ ├── FilterExpressionCompiler.cs │ │ ├── In │ │ │ └── FilterExpressionNotInRewriter.cs │ │ ├── Is │ │ │ └── IsOperatorTransformer.cs │ │ ├── Like │ │ │ └── LikeOperatorTransformer.cs │ │ ├── Linq │ │ │ ├── ExpressionConstantMapper.cs │ │ │ ├── LinqExpressionCompiler.cs │ │ │ └── ParameterReplacementVisitor.cs │ │ ├── Properties │ │ │ └── PropertiesObjectAccessorTransformer.cs │ │ ├── Text │ │ │ ├── IndexOfIgnoreCaseCallRewriter.cs │ │ │ ├── MatchingRewriter.cs │ │ │ ├── RegexSecondMatchCallRewriter.cs │ │ │ ├── SymmetricMatchCallRewriter.cs │ │ │ └── TextMatchingTransformer.cs │ │ ├── Transformations │ │ │ ├── FilterExpressionIdentityTransformer.cs │ │ │ ├── FilterExpressionNodeReplacer.cs │ │ │ └── FilterExpressionTransformer`1.cs │ │ └── Wildcards │ │ │ ├── FilterExpressionWildcardSearch.cs │ │ │ └── WildcardComprehensionTransformer.cs │ │ ├── FilterExpressionLanguage.cs │ │ ├── LoggingFilterSwitch.cs │ │ ├── Operators.cs │ │ ├── Parsing │ │ ├── FilterExpressionKeyword.cs │ │ ├── FilterExpressionParser.cs │ │ ├── FilterExpressionTextParsers.cs │ │ ├── FilterExpressionToken.cs │ │ ├── FilterExpressionTokenParsers.cs │ │ ├── FilterExpressionTokenizer.cs │ │ └── ParserExtensions.cs │ │ └── Runtime │ │ ├── AcceptNullAttribute.cs │ │ ├── AcceptUndefinedAttribute.cs │ │ ├── BooleanAttribute.cs │ │ ├── NumericAttribute.cs │ │ ├── NumericComparableAttribute.cs │ │ ├── Representation.cs │ │ ├── RuntimeOperators.cs │ │ └── Undefined.cs │ ├── LoggerEnrichmentConfigurationExtensions.cs │ ├── LoggerFilterConfigurationExtensions.cs │ ├── LoggerSinkConfigurationExtensions.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── Serilog.Filters.Expressions.csproj └── test ├── Serilog.Filters.Expressions.PerformanceTests ├── ComparisonBenchmark.cs ├── Harness.cs ├── Serilog.Filters.Expressions.PerformanceTests.csproj └── Support │ ├── NullSink.cs │ └── Some.cs └── Serilog.Filters.Expressions.Tests ├── ConfigurationTests.cs ├── FilterExpressionCompilerTests.cs ├── FilterExpressionParserTests.cs ├── LoggingFilterSwitchTests.cs ├── PackagingTests.cs ├── Properties └── launchSettings.json ├── Serilog.Filters.Expressions.Tests.csproj └── Support ├── CollectingSink.cs └── Some.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | .idea 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | # NuGet v3's project.json files produces more ignoreable files 155 | *.nuget.props 156 | *.nuget.targets 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | ~$* 182 | *~ 183 | *.dbmdl 184 | *.dbproj.schemaview 185 | *.pfx 186 | *.publishsettings 187 | node_modules/ 188 | orleans.codegen.cs 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | example/Sample/log.txt 239 | BenchmarkDotNet.Artifacts/ 240 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | echo "build: Build started" 2 | 3 | Push-Location $PSScriptRoot 4 | 5 | if(Test-Path .\artifacts) { 6 | echo "build: Cleaning .\artifacts" 7 | Remove-Item .\artifacts -Force -Recurse 8 | } 9 | 10 | & dotnet restore --no-cache 11 | 12 | $branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; 13 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; 14 | $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"] 15 | $commitHash = $(git rev-parse --short HEAD) 16 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] 17 | 18 | echo "build: Package version suffix is $suffix" 19 | echo "build: Build version suffix is $buildSuffix" 20 | 21 | foreach ($src in ls src/*) { 22 | Push-Location $src 23 | 24 | echo "build: Packaging project in $src" 25 | 26 | & dotnet build -c Release --version-suffix=$buildSuffix 27 | 28 | if($suffix) { 29 | & dotnet pack -c Release --include-source --no-build -o ..\..\artifacts --version-suffix=$suffix 30 | } else { 31 | & dotnet pack -c Release --include-source --no-build -o ..\..\artifacts 32 | } 33 | if($LASTEXITCODE -ne 0) { exit 1 } 34 | 35 | Pop-Location 36 | } 37 | 38 | foreach ($test in ls test/*.Tests) { 39 | Push-Location $test 40 | 41 | echo "build: Testing project in $test" 42 | 43 | & dotnet test -c Release 44 | if($LASTEXITCODE -ne 0) { exit 3 } 45 | 46 | Pop-Location 47 | } 48 | 49 | foreach ($test in ls test/*.PerformanceTests) { 50 | Push-Location $test 51 | 52 | echo "build: Building performance test project in $test" 53 | 54 | & dotnet build -c Release 55 | if($LASTEXITCODE -ne 0) { exit 2 } 56 | 57 | Pop-Location 58 | } 59 | 60 | Pop-Location -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serilog.Filters.Expressions [![Build status](https://ci.appveyor.com/api/projects/status/wnh0ig2udlld9oe4?svg=true)](https://ci.appveyor.com/project/serilog/serilog-filters-expressions) [![NuGet Release](https://img.shields.io/nuget/v/Serilog.Filters.Expressions.svg)](https://nuget.org/packages/serilog.filters.expressions) 2 | 3 | Expression-based event filtering for [Serilog](https://serilog.net). 4 | 5 | > **Deprecation notice:** this package has been replaced by [_Serilog.Expressions_](https://github.com/serilog/serilog-expressions), which provides an improved implementation of the functionality that was covered here. 6 | 7 | ```csharp 8 | var expr = "@Level = 'Information' and AppId is not null and Items[?] like 'C%'"; 9 | 10 | Log.Logger = new LoggerConfiguration() 11 | .Enrich.WithProperty("AppId", 10) 12 | .Filter.ByIncludingOnly(expr) 13 | .WriteTo.Console() 14 | .CreateLogger(); 15 | 16 | // Printed 17 | Log.Information("Cart contains {@Items}", new[] { "Tea", "Coffee" }); 18 | Log.Information("Cart contains {@Items}", new[] { "Peanuts", "Chocolate" }); 19 | 20 | // Not printed 21 | Log.Warning("Cart contains {@Items}", new[] { "Tea", "Coffee" }); 22 | Log.Information("Cart contains {@Items}", new[] { "Apricots" }); 23 | 24 | Log.CloseAndFlush(); 25 | ``` 26 | 27 | ### Getting started 28 | 29 | Install _Serilog.Filters.Expressions_ from NuGet: 30 | 31 | ```powershell 32 | Install-Package Serilog.Filters.Expressions 33 | ``` 34 | 35 | Add `Filter.ByIncludingOnly(fiterExpression)` or `Filter.ByExcluding(fiterExpression)` calls to `LoggerConfiguration`. 36 | 37 | ### Syntax 38 | 39 | The syntax is based on SQL, with added support for object structures, arrays, and regular expressions. 40 | 41 | | Category | Examples | 42 | | :--- | :--- | 43 | | **Literals** | `123`, `123.4`, `'Hello'`, `true`, `false`, `null` | 44 | | **Properties** | `A`, `A.B`, `@Level`, `@Timestamp`, `@Exception`, `@Message`, `@MessageTemplate`, `@Properties['A-b-c']` | 45 | | **Comparisons** | `A = B`, `A <> B`, `A > B`, `A >= B`, `A is null`, `A is not null`, `A in [1, 2]` | 46 | | **Text** | `A like 'H%'`, `A not like 'H%'`, `A like 'Hel_o'`, `Contains(A, 'H')`, `StartsWith(A, 'H')`, `EndsWith(A, 'H')`, `IndexOf(A, 'H')`, `Length(A)` | 47 | | **Regular expressions** | `A = /H.*o/`, `Contains(A, /[lL]/)`, other string functions | 48 | | **Collections** | `A[0] = 'Hello'`, `A[?] = 'Hello'` (any), `StartsWith(A[*], 'H')` (all), `Length(A)` | 49 | | **Maths** | `A + 2`, `A - 2`, `A * 2`, `A % 2` | 50 | | **Logic** | `not A`, `A and B`, `A or B` | 51 | | **Grouping** | `A * (B + C)` | 52 | | **Other** | `Has(A)`, `TypeOf(A)` | 53 | 54 | ### XML `` configuration 55 | 56 | Using [_Serilog.Settings.AppSettings_](https://github.com/serilog/serilog-settings-appsettings): 57 | 58 | ```xml 59 | 60 | 61 | ``` 62 | 63 | ### JSON `appSettings.json` configuration 64 | 65 | Using [_Serilog.Settings.Configuration_](https://github.com/serilog/serilog-settings-configuration): 66 | 67 | ```json 68 | { 69 | "Serilog": { 70 | "Using": ["Serilog.Settings.Configuration"], 71 | "Filter": [ 72 | { 73 | "Name": "ByExcluding", 74 | "Args": { 75 | "expression": "EndsWith(RequestPath, '/SomeEndpoint')" 76 | } 77 | } 78 | ] 79 | ``` 80 | -------------------------------------------------------------------------------- /RunPerfTests.ps1: -------------------------------------------------------------------------------- 1 | Push-Location $PSScriptRoot 2 | 3 | ./Build.ps1 4 | 5 | foreach ($test in ls test/*.PerformanceTests) { 6 | Push-Location $test 7 | 8 | echo "perf: Running performance test project in $test" 9 | 10 | & dotnet test -c Release 11 | if($LASTEXITCODE -ne 0) { exit 2 } 12 | 13 | Pop-Location 14 | } 15 | 16 | Pop-Location 17 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | skip_tags: true 3 | image: Visual Studio 2017 4 | build_script: 5 | - ps: ./Build.ps1 6 | test: off 7 | artifacts: 8 | - path: artifacts/Serilog.*.nupkg 9 | deploy: 10 | - provider: NuGet 11 | api_key: 12 | secure: BDDYHSR5hA/CH7pa1e0o8+yPMgT9/DrlLOOGU3uWBufLprPFrzbAKRtq14vPNTHd 13 | skip_symbols: true 14 | on: 15 | branch: /^(master|dev)$/ 16 | - provider: GitHub 17 | auth_token: 18 | secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX 19 | artifact: /Serilog.*\.nupkg/ 20 | tag: v$(appveyor_build_version) 21 | on: 22 | branch: master 23 | -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-archive/serilog-filters-expressions/81fdbe96bcbbafb6917bc4a876bf86fa84181316/assets/Serilog.snk -------------------------------------------------------------------------------- /example/Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | 3 | namespace Sample 4 | { 5 | public class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | var expr = "@Level = 'Information' and AppId is not null and Items[?] like 'C%'"; 10 | 11 | Log.Logger = new LoggerConfiguration() 12 | .Enrich.WithProperty("AppId", 10) 13 | .Filter.ByIncludingOnly(expr) 14 | .WriteTo.LiterateConsole() 15 | .CreateLogger(); 16 | 17 | Log.Information("Cart contains {@Items}", new[] { "Tea", "Coffee" }); 18 | Log.Warning("Cart contains {@Items}", new[] { "Tea", "Coffee" }); 19 | Log.Information("Cart contains {@Items}", new[] { "Apricots" }); 20 | Log.Information("Cart contains {@Items}", new[] { "Peanuts", "Chocolate" }); 21 | 22 | Log.CloseAndFlush(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/Sample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("Sample")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("776eecac-3c50-45ea-847d-0ebe5158e51e")] 20 | -------------------------------------------------------------------------------- /example/Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.0 5 | Sample 6 | Exe 7 | Sample 8 | $(PackageTargetFallback);dnxcore50 9 | false 10 | false 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /serilog-filters-expressions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{91E482DE-E1E7-4CE1-9511-C0AF07F3648A}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{24B1126F-184C-469E-9F06-C1019DEF165A}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitattributes = .gitattributes 11 | .gitignore = .gitignore 12 | appveyor.yml = appveyor.yml 13 | Build.ps1 = Build.ps1 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | RunPerfTests.ps1 = RunPerfTests.ps1 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{BD94A77E-34B1-478E-B921-E87A5F71B574}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B03B3086-D197-4B32-9AE2-8536C345EA2D}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "example\Sample\Sample.csproj", "{776EECAC-3C50-45EA-847D-0EBE5158E51E}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Filters.Expressions", "src\Serilog.Filters.Expressions\Serilog.Filters.Expressions.csproj", "{A420C4E3-3A2D-4369-88EB-77E4DB1D0219}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Filters.Expressions.Tests", "test\Serilog.Filters.Expressions.Tests\Serilog.Filters.Expressions.Tests.csproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Filters.Expressions.PerformanceTests", "test\Serilog.Filters.Expressions.PerformanceTests\Serilog.Filters.Expressions.PerformanceTests.csproj", "{D7A37F73-BBA3-4DAE-9648-1A753A86F968}" 30 | EndProject 31 | Global 32 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 33 | Debug|Any CPU = Debug|Any CPU 34 | Release|Any CPU = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {776EECAC-3C50-45EA-847D-0EBE5158E51E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {776EECAC-3C50-45EA-847D-0EBE5158E51E}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {776EECAC-3C50-45EA-847D-0EBE5158E51E}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {776EECAC-3C50-45EA-847D-0EBE5158E51E}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {A420C4E3-3A2D-4369-88EB-77E4DB1D0219}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {A420C4E3-3A2D-4369-88EB-77E4DB1D0219}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {A420C4E3-3A2D-4369-88EB-77E4DB1D0219}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {A420C4E3-3A2D-4369-88EB-77E4DB1D0219}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU 49 | {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {D7A37F73-BBA3-4DAE-9648-1A753A86F968}.Release|Any CPU.Build.0 = Release|Any CPU 53 | EndGlobalSection 54 | GlobalSection(SolutionProperties) = preSolution 55 | HideSolutionNode = FALSE 56 | EndGlobalSection 57 | GlobalSection(NestedProjects) = preSolution 58 | {776EECAC-3C50-45EA-847D-0EBE5158E51E} = {BD94A77E-34B1-478E-B921-E87A5F71B574} 59 | {A420C4E3-3A2D-4369-88EB-77E4DB1D0219} = {91E482DE-E1E7-4CE1-9511-C0AF07F3648A} 60 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {B03B3086-D197-4B32-9AE2-8536C345EA2D} 61 | {D7A37F73-BBA3-4DAE-9648-1A753A86F968} = {B03B3086-D197-4B32-9AE2-8536C345EA2D} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /serilog-filters-expressions.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterArrayExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Serilog.Filters.Expressions.Ast 5 | { 6 | class FilterArrayExpression : FilterExpression 7 | { 8 | public FilterArrayExpression(FilterExpression[] elements) 9 | { 10 | Elements = elements ?? throw new ArgumentNullException(nameof(elements)); 11 | } 12 | 13 | public FilterExpression[] Elements { get; } 14 | 15 | public override string ToString() 16 | { 17 | return "[" + string.Join(",", Elements.Select(o => o.ToString())) + "]"; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterCallExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Serilog.Filters.Expressions.Ast 5 | { 6 | class FilterCallExpression : FilterExpression 7 | { 8 | public FilterCallExpression(string operatorName, params FilterExpression[] operands) 9 | { 10 | OperatorName = operatorName ?? throw new ArgumentNullException(nameof(operatorName)); 11 | Operands = operands ?? throw new ArgumentNullException(nameof(operands)); 12 | } 13 | 14 | public string OperatorName { get; } 15 | 16 | public FilterExpression[] Operands { get; } 17 | 18 | public override string ToString() 19 | { 20 | if (OperatorName == Operators.OpElementAt && Operands.Length == 2) 21 | { 22 | return Operands[0] + "[" + Operands[1] + "]"; 23 | } 24 | 25 | return OperatorName + "(" + string.Join(",", Operands.Select(o => o.ToString())) + ")"; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterConstantExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Serilog.Filters.Expressions.Ast 6 | { 7 | class FilterConstantExpression : FilterExpression 8 | { 9 | readonly object _constantValue; 10 | 11 | public FilterConstantExpression(object constantValue) 12 | { 13 | _constantValue = constantValue; 14 | } 15 | 16 | public object ConstantValue 17 | { 18 | get { return _constantValue; } 19 | } 20 | 21 | // Syntax here must match query syntax 22 | // or filters won't round-trip. 23 | // 24 | public override string ToString() 25 | { 26 | var s = ConstantValue as string; 27 | if (s != null) 28 | { 29 | return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; 30 | } 31 | 32 | var re = ConstantValue as Regex; 33 | if (re != null) 34 | { 35 | if ((re.Options & RegexOptions.IgnoreCase) != RegexOptions.None) 36 | return $"/{re}/i"; 37 | 38 | return $"/{re}/"; 39 | } 40 | 41 | if (true.Equals(ConstantValue)) 42 | return "true"; 43 | 44 | if (false.Equals(ConstantValue)) 45 | return "false"; 46 | 47 | var formattable = _constantValue as IFormattable; 48 | if (formattable != null) 49 | return formattable.ToString(null, CultureInfo.InvariantCulture); 50 | 51 | return (ConstantValue ?? "null").ToString(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | abstract class FilterExpression 6 | { 7 | readonly FilterExpressionType _type; 8 | 9 | protected FilterExpression() 10 | { 11 | var typeName = GetType().Name.Remove(0, 6).Replace("Expression", ""); 12 | _type = (FilterExpressionType)Enum.Parse(typeof(FilterExpressionType), typeName); 13 | } 14 | 15 | public FilterExpressionType Type { get { return _type; } } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterExpressionType.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Filters.Expressions.Ast 2 | { 3 | enum FilterExpressionType 4 | { 5 | None, 6 | Call, 7 | Constant, 8 | Property, 9 | Text, 10 | Subproperty, 11 | Wildcard, 12 | Parameter, 13 | Lambda, 14 | Array 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterLambdaExpression.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | class FilterLambdaExpression : FilterExpression 6 | { 7 | readonly FilterParameterExpression[] _parameters; 8 | readonly FilterExpression _body; 9 | 10 | public FilterLambdaExpression(FilterParameterExpression[] parameters, FilterExpression body) 11 | { 12 | _parameters = parameters; 13 | _body = body; 14 | } 15 | 16 | public FilterParameterExpression[] Parameters 17 | { 18 | get { return _parameters; } 19 | } 20 | 21 | public FilterExpression Body 22 | { 23 | get { return _body; } 24 | } 25 | 26 | public override string ToString() 27 | { 28 | // There's no parseable syntax for this yet 29 | return "\\(" + string.Join(",", Parameters.Select(p => p.ToString())) + "){" + Body + "}"; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterParameterExpression.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Filters.Expressions.Ast 2 | { 3 | class FilterParameterExpression : FilterExpression 4 | { 5 | readonly string _parameterName; 6 | 7 | public FilterParameterExpression(string parameterName) 8 | { 9 | _parameterName = parameterName; 10 | } 11 | 12 | public string ParameterName 13 | { 14 | get { return _parameterName; } 15 | } 16 | 17 | public override string ToString() 18 | { 19 | // There's no parseable syntax for this yet 20 | return "$$" + ParameterName; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterPropertyExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | class FilterPropertyExpression : FilterExpression 6 | { 7 | readonly string _propertyName; 8 | readonly bool _isBuiltIn; 9 | readonly bool _requiresEscape; 10 | 11 | public FilterPropertyExpression(string propertyName, bool isBuiltIn) 12 | { 13 | if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); 14 | _propertyName = propertyName; 15 | _isBuiltIn = isBuiltIn; 16 | _requiresEscape = !FilterLanguage.IsValidPropertyName(propertyName); 17 | } 18 | 19 | public string PropertyName 20 | { 21 | get { return _propertyName; } 22 | } 23 | 24 | public bool IsBuiltIn 25 | { 26 | get { return _isBuiltIn; } 27 | } 28 | 29 | public override string ToString() 30 | { 31 | if (_requiresEscape) 32 | return $"@Properties['{FilterLanguage.EscapeStringContent(PropertyName)}']"; 33 | 34 | return (_isBuiltIn ? "@" : "") + PropertyName; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterSubpropertyExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | class FilterSubpropertyExpression : FilterExpression 6 | { 7 | readonly string _propertyName; 8 | readonly FilterExpression _receiver; 9 | 10 | public FilterSubpropertyExpression(string propertyName, FilterExpression receiver) 11 | { 12 | if (propertyName == null) throw new ArgumentNullException(nameof(propertyName)); 13 | _propertyName = propertyName; 14 | _receiver = receiver; 15 | } 16 | 17 | public string PropertyName 18 | { 19 | get { return _propertyName; } 20 | } 21 | 22 | public FilterExpression Receiver 23 | { 24 | get { return _receiver; } 25 | } 26 | 27 | public override string ToString() 28 | { 29 | return _receiver + "." + PropertyName; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterTextExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | class FilterTextExpression : FilterExpression 6 | { 7 | readonly string _text; 8 | readonly FilterTextMatching _matching; 9 | 10 | public FilterTextExpression(string text, FilterTextMatching matching) 11 | { 12 | if (text == null) throw new ArgumentNullException(nameof(text)); 13 | _text = text; 14 | _matching = matching; 15 | } 16 | 17 | public string Text 18 | { 19 | get { return _text; } 20 | } 21 | 22 | public FilterTextMatching Matching 23 | { 24 | get { return _matching; } 25 | } 26 | 27 | public override string ToString() 28 | { 29 | // If it's a regular expression, the string is already regex-escaped. 30 | if (Matching == FilterTextMatching.RegularExpression) 31 | return "/" + Text + "/"; 32 | 33 | if (Matching == FilterTextMatching.RegularExpressionInsensitive) 34 | return "/" + Text + "/i"; 35 | 36 | var mm = Matching == FilterTextMatching.Exact ? "@" : ""; 37 | return mm + "\"" + Text 38 | .Replace("\\", "\\\\") 39 | .Replace("\"", "\\\"") 40 | .Replace("\r", "\\r") 41 | .Replace("\n", "\\n") + "\""; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterTextMatching.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Filters.Expressions.Ast 2 | { 3 | enum FilterTextMatching 4 | { 5 | Exact, 6 | Insensitive, 7 | RegularExpression, 8 | RegularExpressionInsensitive 9 | } 10 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterWildcard.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Filters.Expressions.Ast 2 | { 3 | enum FilterWildcard { Undefined, Any, All } 4 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Ast/FilterWildcardExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Ast 4 | { 5 | class FilterWildcardExpression : FilterExpression 6 | { 7 | readonly FilterWildcard _wildcard; 8 | 9 | public FilterWildcardExpression(FilterWildcard wildcard) 10 | { 11 | _wildcard = wildcard; 12 | } 13 | 14 | public FilterWildcard Wildcard 15 | { 16 | get { return _wildcard; } 17 | } 18 | 19 | public override string ToString() 20 | { 21 | switch (Wildcard) 22 | { 23 | case FilterWildcard.Any: 24 | return "?"; 25 | case FilterWildcard.All: 26 | return "*"; 27 | default: 28 | throw new NotSupportedException("Unrecognized wildcard " + Wildcard); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Arrays/FilterExpressionConstantArrayEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Serilog.Events; 3 | using Serilog.Filters.Expressions.Ast; 4 | using Serilog.Filters.Expressions.Compilation.Transformations; 5 | using Serilog.Filters.Expressions.Runtime; 6 | 7 | namespace Serilog.Filters.Expressions.Compilation.Arrays 8 | { 9 | class FilterExpressionConstantArrayEvaluator : FilterExpressionIdentityTransformer 10 | { 11 | public static FilterExpression Evaluate(FilterExpression expression) 12 | { 13 | var evaluator = new FilterExpressionConstantArrayEvaluator(); 14 | return evaluator.Transform(expression); 15 | } 16 | 17 | protected override FilterExpression Transform(FilterArrayExpression ax) 18 | { 19 | // This should probably go depth-first. 20 | 21 | if (ax.Elements.All(el => el is FilterConstantExpression)) 22 | { 23 | return new FilterConstantExpression( 24 | new SequenceValue(ax.Elements 25 | .Cast() 26 | .Select(ce => Representation.Recapture(ce.ConstantValue)))); 27 | } 28 | 29 | return base.Transform(ax); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/CompiledFilterExpression.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | 3 | namespace Serilog.Filters.Expressions.Compilation 4 | { 5 | delegate object CompiledFilterExpression(LogEvent context); 6 | } 7 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Costing/FilterExpressionCostReordering.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Serilog.Filters.Expressions.Compilation.Costing 7 | { 8 | class FilterExpressionCostReordering : FilterExpressionTransformer 9 | { 10 | public static FilterExpression Reorder(FilterExpression expression) 11 | { 12 | var costing = new FilterExpressionCostReordering(); 13 | return costing.Transform(expression).Expression; 14 | } 15 | 16 | protected override FilterExpressionCosting Transform(FilterPropertyExpression px) 17 | { 18 | if (px.PropertyName == "Exception" && px.IsBuiltIn) 19 | return new FilterExpressionCosting(px, 100); 20 | 21 | if (px.PropertyName == "Level" && px.IsBuiltIn) 22 | return new FilterExpressionCosting(px, 5); 23 | 24 | if (px.IsBuiltIn) 25 | return new FilterExpressionCosting(px, 1); 26 | 27 | return new FilterExpressionCosting(px, 10); 28 | } 29 | 30 | protected override FilterExpressionCosting Transform(FilterTextExpression tx) 31 | { 32 | switch (tx.Matching) 33 | { 34 | case FilterTextMatching.Insensitive: 35 | return new FilterExpressionCosting(tx, 2); 36 | case FilterTextMatching.RegularExpression: 37 | case FilterTextMatching.RegularExpressionInsensitive: 38 | return new FilterExpressionCosting(tx, 10000); 39 | } 40 | 41 | return new FilterExpressionCosting(tx, 1); 42 | } 43 | 44 | protected override FilterExpressionCosting Transform(FilterParameterExpression prx) 45 | { 46 | return new FilterExpressionCosting(prx, 0.1); 47 | } 48 | 49 | protected override FilterExpressionCosting Transform(FilterWildcardExpression wx) 50 | { 51 | return new FilterExpressionCosting(wx, 0); 52 | } 53 | 54 | protected override FilterExpressionCosting Transform(FilterLambdaExpression lmx) 55 | { 56 | var body = Transform(lmx.Body); 57 | return new FilterExpressionCosting( 58 | new FilterLambdaExpression(lmx.Parameters, body.Expression), 59 | body.Costing + 0.1); 60 | } 61 | 62 | protected override FilterExpressionCosting Transform(FilterSubpropertyExpression spx) 63 | { 64 | var receiver = Transform(spx.Receiver); 65 | return new FilterExpressionCosting( 66 | new FilterSubpropertyExpression(spx.PropertyName, receiver.Expression), 67 | receiver.Costing + 0.1); 68 | } 69 | 70 | protected override FilterExpressionCosting Transform(FilterConstantExpression cx) 71 | { 72 | return new FilterExpressionCosting(cx, 0.001); 73 | } 74 | 75 | protected override FilterExpressionCosting Transform(FilterCallExpression lx) 76 | { 77 | var operands = lx.Operands.Select(Transform).ToArray(); 78 | var operatorName = lx.OperatorName.ToLowerInvariant(); 79 | 80 | // To-do: the literal operator name comparisons here can be replaced with constants and IsSameOperator(). 81 | if ((operatorName == "and" || operatorName == "or") && operands.Length == 2) 82 | { 83 | var reorderable = new List(); 84 | foreach (var operand in operands) 85 | { 86 | if (operand.ReorderableOperator?.ToLowerInvariant() == operatorName) 87 | { 88 | foreach (var ro in operand.ReorderableOperands) 89 | reorderable.Add(ro); 90 | } 91 | else 92 | { 93 | reorderable.Add(operand); 94 | } 95 | } 96 | 97 | var remaining = new Stack(reorderable.OrderBy(r => r.Costing)); 98 | var top = remaining.Pop(); 99 | var rhsExpr = top.Expression; 100 | var rhsCosting = top.Costing; 101 | 102 | while (remaining.Count != 0) 103 | { 104 | var lhs = remaining.Pop(); 105 | rhsExpr = new FilterCallExpression(lx.OperatorName, lhs.Expression, rhsExpr); 106 | rhsCosting = lhs.Costing + 0.75 * rhsCosting; 107 | } 108 | 109 | return new FilterExpressionCosting( 110 | rhsExpr, 111 | rhsCosting, 112 | lx.OperatorName, 113 | reorderable.ToArray()); 114 | } 115 | 116 | if ((operatorName == "any" || operatorName == "all") && operands.Length == 2) 117 | { 118 | return new FilterExpressionCosting( 119 | new FilterCallExpression(lx.OperatorName, operands[0].Expression, operands[1].Expression), 120 | operands[0].Costing + 0.1 + operands[1].Costing * 7); 121 | } 122 | 123 | if ((operatorName == "startswith" || operatorName == "endswith" || 124 | operatorName == "contains" || operatorName == "indexof" || 125 | operatorName == "equal" || operatorName == "notequal") && operands.Length == 2) 126 | { 127 | return new FilterExpressionCosting( 128 | new FilterCallExpression(lx.OperatorName, operands[0].Expression, operands[1].Expression), 129 | operands[0].Costing + operands[1].Costing + 10); 130 | } 131 | 132 | return new FilterExpressionCosting( 133 | new FilterCallExpression(lx.OperatorName, operands.Select(o => o.Expression).ToArray()), 134 | operands.Sum(o => o.Costing) + 0.1); 135 | } 136 | 137 | protected override FilterExpressionCosting Transform(FilterArrayExpression ax) 138 | { 139 | return new FilterExpressionCosting(ax, 0.2); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Costing/FilterExpressionCosting.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | 3 | namespace Serilog.Filters.Expressions.Compilation.Costing 4 | { 5 | class FilterExpressionCosting 6 | { 7 | public FilterExpression Expression { get; } 8 | public double Costing { get; } 9 | public string ReorderableOperator { get; } 10 | public FilterExpressionCosting[] ReorderableOperands { get; } 11 | 12 | public FilterExpressionCosting(FilterExpression expression, double costing, string reorderableOperator = null, FilterExpressionCosting[] reorderableOperands = null) 13 | { 14 | Expression = expression; 15 | Costing = costing; 16 | ReorderableOperator = reorderableOperator; 17 | ReorderableOperands = reorderableOperands; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/FilterExpressionCompiler.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Filters.Expressions.Ast; 3 | using Serilog.Filters.Expressions.Compilation.Costing; 4 | using Serilog.Filters.Expressions.Compilation.Is; 5 | using Serilog.Filters.Expressions.Compilation.Like; 6 | using Serilog.Filters.Expressions.Compilation.Linq; 7 | using Serilog.Filters.Expressions.Compilation.Text; 8 | using Serilog.Filters.Expressions.Compilation.Wildcards; 9 | using Serilog.Filters.Expressions.Compilation.Properties; 10 | using Serilog.Filters.Expressions.Runtime; 11 | using System; 12 | using Serilog.Filters.Expressions.Compilation.Arrays; 13 | using Serilog.Filters.Expressions.Compilation.In; 14 | 15 | namespace Serilog.Filters.Expressions.Compilation 16 | { 17 | static class FilterExpressionCompiler 18 | { 19 | public static Func CompileAndExpose(FilterExpression expression) 20 | { 21 | var actual = expression; 22 | actual = PropertiesObjectAccessorTransformer.Rewrite(actual); 23 | actual = FilterExpressionConstantArrayEvaluator.Evaluate(actual); 24 | actual = FilterExpressionNotInRewriter.Rewrite(actual); 25 | actual = WildcardComprehensionTransformer.Expand(actual); 26 | actual = LikeOperatorTransformer.Rewrite(actual); 27 | actual = IsOperatorTransformer.Rewrite(actual); 28 | actual = FilterExpressionCostReordering.Reorder(actual); 29 | actual = TextMatchingTransformer.Rewrite(actual); 30 | 31 | var compiled = LinqExpressionCompiler.Compile(actual); 32 | return ctx => 33 | { 34 | var result = compiled(ctx); 35 | return Representation.Expose(result); 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/In/FilterExpressionNotInRewriter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.In 5 | { 6 | class FilterExpressionNotInRewriter : FilterExpressionIdentityTransformer 7 | { 8 | public static FilterExpression Rewrite(FilterExpression expression) 9 | { 10 | var rewriter = new FilterExpressionNotInRewriter(); 11 | return rewriter.Transform(expression); 12 | } 13 | 14 | protected override FilterExpression Transform(FilterCallExpression lx) 15 | { 16 | if (Operators.SameOperator(Operators.IntermediateOpSqlNotIn, lx.OperatorName)) 17 | return new FilterCallExpression(Operators.RuntimeOpStrictNot, 18 | new FilterCallExpression(Operators.RuntimeOpSqlIn, lx.Operands)); 19 | return base.Transform(lx); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Is/IsOperatorTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Is 5 | { 6 | class IsOperatorTransformer : FilterExpressionIdentityTransformer 7 | { 8 | public static FilterExpression Rewrite(FilterExpression expression) 9 | { 10 | return new IsOperatorTransformer().Transform(expression); 11 | } 12 | 13 | protected override FilterExpression Transform(FilterCallExpression lx) 14 | { 15 | if (!Operators.SameOperator(lx.OperatorName.ToLowerInvariant(), Operators.IntermediateOpSqlIs) || lx.Operands.Length != 2) 16 | return base.Transform(lx); 17 | 18 | var nul = lx.Operands[1] as FilterConstantExpression; 19 | if (nul != null) 20 | { 21 | if (nul.ConstantValue != null) 22 | return base.Transform(lx); 23 | 24 | return new FilterCallExpression(Operators.RuntimeOpIsNull, lx.Operands[0]); 25 | } 26 | 27 | var not = lx.Operands[1] as FilterCallExpression; 28 | if (not == null || not.Operands.Length != 1) 29 | return base.Transform(lx); 30 | 31 | nul = not.Operands[0] as FilterConstantExpression; 32 | if (nul == null || nul.ConstantValue != null) 33 | return base.Transform(lx); 34 | 35 | return new FilterCallExpression(Operators.RuntimeOpIsNotNull, lx.Operands[0]); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Like/LikeOperatorTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | using Serilog.Filters.Expressions.Runtime; 4 | using Superpower; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace Serilog.Filters.Expressions.Compilation.Like 9 | { 10 | class LikeOperatorTransformer : FilterExpressionIdentityTransformer 11 | { 12 | public static FilterExpression Rewrite(FilterExpression expression) 13 | { 14 | return new LikeOperatorTransformer().Transform(expression); 15 | } 16 | 17 | static FilterExpression Like(FilterExpression lhs, FilterTextExpression rhs) 18 | { 19 | if (!rhs.Text.Contains('%') && !rhs.Text.Contains('_')) 20 | return new FilterCallExpression(Operators.OpEqual, lhs, new FilterTextExpression(rhs.Text, FilterTextMatching.Insensitive)); 21 | 22 | if (rhs.Text.Length > 1 && !rhs.Text.Contains('_')) 23 | { 24 | var count = rhs.Text.Count(ch => ch == '%'); 25 | 26 | if (count == 1) 27 | { 28 | var idx = rhs.Text.IndexOf('%'); 29 | var rest = rhs.Text.Remove(idx, 1); 30 | 31 | if (idx == 0) 32 | return new FilterCallExpression(Operators.OpEndsWith, lhs, 33 | new FilterTextExpression(rest, FilterTextMatching.Insensitive)); 34 | 35 | if (idx == rhs.Text.Length - 1) 36 | return new FilterCallExpression(Operators.OpStartsWith, lhs, 37 | new FilterTextExpression(rest, FilterTextMatching.Insensitive)); 38 | } 39 | else if (count == 2 && rhs.Text.Length > 2) 40 | { 41 | if (rhs.Text.StartsWith("%") && rhs.Text.EndsWith("%")) 42 | return new FilterCallExpression(Operators.OpContains, lhs, 43 | new FilterTextExpression(rhs.Text.Substring(1, rhs.Text.Length - 2), FilterTextMatching.Insensitive)); 44 | } 45 | } 46 | 47 | var regex = ""; 48 | foreach (var ch in rhs.Text) 49 | { 50 | if (ch == '%') 51 | regex += "(.|\\r|\\n)*"; // ~= RegexOptions.Singleline 52 | else if (ch == '_') 53 | regex += '.'; 54 | else 55 | regex += Regex.Escape(ch.ToString()); 56 | } 57 | 58 | return new FilterCallExpression(Operators.OpEqual, lhs, new FilterTextExpression(regex, FilterTextMatching.RegularExpressionInsensitive)); 59 | } 60 | 61 | protected override FilterExpression Transform(FilterCallExpression lx) 62 | { 63 | var newOperands = lx.Operands.Select(Transform).ToArray(); 64 | 65 | if (Operators.SameOperator(lx.OperatorName, Operators.IntermediateOpSqlLike)) 66 | { 67 | if (newOperands.Length == 2 && 68 | newOperands[1] is FilterTextExpression) 69 | { 70 | return Like(newOperands[0], (FilterTextExpression)newOperands[1]); 71 | } 72 | 73 | return new FilterConstantExpression(Undefined.Value); 74 | } 75 | 76 | if (Operators.SameOperator(lx.OperatorName, Operators.IntermediateOpSqlNotLike)) 77 | { 78 | if (newOperands.Length == 2 && 79 | newOperands[1] is FilterTextExpression) 80 | { 81 | return new FilterCallExpression(Operators.RuntimeOpStrictNot, 82 | Like(newOperands[0], (FilterTextExpression)newOperands[1])); 83 | } 84 | 85 | return new FilterConstantExpression(Undefined.Value); 86 | } 87 | 88 | return new FilterCallExpression(lx.OperatorName, newOperands); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Linq/ExpressionConstantMapper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq.Expressions; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Linq 5 | { 6 | class ExpressionConstantMapper : ExpressionVisitor 7 | { 8 | readonly IDictionary _mapping; 9 | 10 | public ExpressionConstantMapper(IDictionary mapping) 11 | { 12 | _mapping = mapping; 13 | } 14 | 15 | protected override Expression VisitConstant(ConstantExpression node) 16 | { 17 | Expression substitute; 18 | if (node.Value != null && _mapping.TryGetValue(node.Value, out substitute)) 19 | return substitute; 20 | 21 | return base.VisitConstant(node); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Linq/LinqExpressionCompiler.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Filters.Expressions.Ast; 3 | using Serilog.Filters.Expressions.Compilation.Transformations; 4 | using Serilog.Filters.Expressions.Runtime; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Reflection; 10 | 11 | namespace Serilog.Filters.Expressions.Compilation.Linq 12 | { 13 | class LinqExpressionCompiler : FilterExpressionTransformer> 14 | { 15 | static readonly IDictionary OperatorMethods = typeof(RuntimeOperators) 16 | .GetTypeInfo() 17 | .GetMethods(BindingFlags.Static | BindingFlags.Public) 18 | .ToDictionary(m => m.Name, StringComparer.OrdinalIgnoreCase); 19 | 20 | public static CompiledFilterExpression Compile(FilterExpression expression) 21 | { 22 | if (expression == null) throw new ArgumentNullException(nameof(expression)); 23 | var compiler = new LinqExpressionCompiler(); 24 | return compiler.Transform(expression).Compile(); 25 | } 26 | 27 | protected override Expression Transform(FilterCallExpression lx) 28 | { 29 | MethodInfo m; 30 | if (!OperatorMethods.TryGetValue(lx.OperatorName, out m)) 31 | throw new ArgumentException($"The function name `{lx.OperatorName}` was not recognised; to search for text instead, enclose the filter in \"double quotes\"."); 32 | 33 | if (m.GetParameters().Length != lx.Operands.Length) 34 | throw new ArgumentException($"The function `{lx.OperatorName}` requires {m.GetParameters().Length} arguments; to search for text instead, enclose the filter in \"double quotes\"."); 35 | 36 | var acceptUndefined = m.GetCustomAttribute() != null; 37 | var acceptNull = m.GetCustomAttribute() != null; 38 | var numericOnly = m.GetCustomAttribute() != null; 39 | var numericComparable = m.GetCustomAttribute() != null; 40 | var booleanOnly = m.GetCustomAttribute() != null; 41 | var operands = lx.Operands.Select(Transform).ToArray(); 42 | 43 | var returnUndefined = new List>>(); 44 | if (!acceptUndefined) returnUndefined.Add(v => v is Undefined); 45 | if (!acceptNull) returnUndefined.Add(v => v == null); 46 | if (numericOnly) returnUndefined.Add(v => !(v is decimal || v == null || v is Undefined)); 47 | if (numericComparable) returnUndefined.Add(v => !(v is decimal || v == null || v is Undefined)); 48 | if (booleanOnly) returnUndefined.Add(v => !(v is bool || v == null || v is Undefined)); 49 | 50 | var context = Expression.Parameter(typeof(LogEvent)); 51 | 52 | var operandValues = operands.Select(o => Splice(o, context)); 53 | var operandVars = new List(); 54 | var rtn = Expression.Label(typeof(object)); 55 | 56 | var statements = new List(); 57 | var first = true; 58 | foreach (var op in operandValues) 59 | { 60 | var opam = Expression.Variable(typeof(object)); 61 | operandVars.Add(opam); 62 | statements.Add(Expression.Assign(opam, op)); 63 | 64 | if (first && lx.OperatorName.ToLowerInvariant() == "and") 65 | { 66 | Expression> shortCircuitIf = v => !true.Equals(v); 67 | var scc = Splice(shortCircuitIf, opam); 68 | statements.Add(Expression.IfThen(scc, Expression.Return(rtn, Expression.Constant(false, typeof(object))))); 69 | } 70 | 71 | if (first && lx.OperatorName.ToLowerInvariant() == "or") 72 | { 73 | Expression> shortCircuitIf = v => true.Equals(v); 74 | var scc = Splice(shortCircuitIf, opam); 75 | statements.Add(Expression.IfThen(scc, Expression.Return(rtn, Expression.Constant(true, typeof(object))))); 76 | } 77 | 78 | var checks = returnUndefined.Select(fv => Splice(fv, opam)).ToArray(); 79 | foreach (var check in checks) 80 | { 81 | statements.Add(Expression.IfThen(check, Expression.Return(rtn, Expression.Constant(Undefined.Value, typeof(object))))); 82 | } 83 | 84 | first = false; 85 | } 86 | 87 | statements.Add(Expression.Return(rtn, Expression.Call(m, operandVars))); 88 | statements.Add(Expression.Label(rtn, Expression.Constant(Undefined.Value, typeof(object)))); 89 | 90 | return Expression.Lambda( 91 | Expression.Block(typeof(object), operandVars, statements), 92 | context); 93 | } 94 | 95 | protected override Expression Transform(FilterSubpropertyExpression spx) 96 | { 97 | var tgv = typeof(LinqExpressionCompiler).GetTypeInfo().GetMethod(nameof(TryGetStructurePropertyValue), BindingFlags.Static | BindingFlags.Public); 98 | var norm = typeof(Representation).GetTypeInfo().GetMethod(nameof(Representation.Represent), BindingFlags.Static | BindingFlags.Public); 99 | 100 | var recv = Transform(spx.Receiver); 101 | 102 | var context = Expression.Parameter(typeof(LogEvent)); 103 | 104 | var r = Expression.Variable(typeof(object)); 105 | var str = Expression.Variable(typeof(StructureValue)); 106 | var result = Expression.Variable(typeof(LogEventPropertyValue)); 107 | 108 | var sx3 = Expression.Call(tgv, str, Expression.Constant(spx.PropertyName, typeof(string)), result); 109 | 110 | var sx1 = Expression.Condition(sx3, 111 | Expression.Call(norm, result), 112 | Expression.Constant(Undefined.Value, typeof(object))); 113 | 114 | var sx2 = Expression.Block(typeof(object), 115 | Expression.Assign(str, Expression.TypeAs(r, typeof(StructureValue))), 116 | Expression.Condition(Expression.Equal(str, Expression.Constant(null, typeof(StructureValue))), 117 | Expression.Constant(Undefined.Value, typeof(object)), 118 | sx1)); 119 | 120 | var assignR = Expression.Assign(r, Splice(recv, context)); 121 | var getValue = Expression.Condition(Expression.TypeIs(r, typeof(Undefined)), 122 | Expression.Constant(Undefined.Value, typeof(object)), 123 | sx2); 124 | 125 | return Expression.Lambda( 126 | Expression.Block(typeof(object), new[] { r, str, result }, assignR, getValue), 127 | context); 128 | 129 | //return context => 130 | //{ 131 | // var r = recv(context); 132 | // if (r is Undefined) 133 | // return Undefined.Value; 134 | 135 | // var str = r as StructureValue; 136 | // if (str == null) 137 | // return Undefined.Value; 138 | 139 | // LogEventPropertyValue result; 140 | // if (!str.Properties.TryGetValue(spx.PropertyName, out result)) 141 | // return Undefined.Value; 142 | 143 | // return Represent(result); 144 | //}; 145 | } 146 | 147 | static Expression Splice(LambdaExpression lambda, params ParameterExpression[] newParameters) 148 | { 149 | var v = new ParameterReplacementVisitor(lambda.Parameters.ToArray(), newParameters); 150 | return v.Visit(lambda.Body); 151 | } 152 | 153 | protected override Expression Transform(FilterConstantExpression cx) 154 | { 155 | return context => cx.ConstantValue; 156 | } 157 | 158 | protected override Expression Transform(FilterPropertyExpression px) 159 | { 160 | if (px.IsBuiltIn) 161 | { 162 | if (px.PropertyName == "Level") 163 | return context => context.Level.ToString(); 164 | 165 | if (px.PropertyName == "Message") 166 | return context => NormalizeBaseDocumentProperty(context.RenderMessage(null)); 167 | 168 | if (px.PropertyName == "Exception") 169 | return context => NormalizeBaseDocumentProperty(context.Exception == null ? null : context.Exception.ToString()); 170 | 171 | if (px.PropertyName == "Timestamp") 172 | return context => context.Timestamp.ToString("o"); 173 | 174 | if (px.PropertyName == "MessageTemplate") 175 | return context => NormalizeBaseDocumentProperty(context.MessageTemplate.Text); 176 | 177 | if (px.PropertyName == "Properties") 178 | return context => context.Properties; 179 | 180 | return context => Undefined.Value; 181 | } 182 | 183 | var propertyName = px.PropertyName; 184 | 185 | return context => GetPropertyValue(context, propertyName); 186 | } 187 | 188 | static object GetPropertyValue(LogEvent context, string propertyName) 189 | { 190 | LogEventPropertyValue value; 191 | if (!context.Properties.TryGetValue(propertyName, out value)) 192 | return Undefined.Value; 193 | 194 | return Representation.Represent(value); 195 | } 196 | 197 | public static bool TryGetStructurePropertyValue(StructureValue sv, string name, out LogEventPropertyValue value) 198 | { 199 | foreach (var prop in sv.Properties) 200 | { 201 | if (prop.Name == name) 202 | { 203 | value = prop.Value; 204 | return true; 205 | } 206 | } 207 | 208 | value = null; 209 | return false; 210 | } 211 | 212 | static object NormalizeBaseDocumentProperty(string rawValue) 213 | { 214 | // If a property like @Exception is null, it's not present at all, thus Undefined 215 | if (rawValue == null) 216 | return Undefined.Value; 217 | 218 | return rawValue; 219 | } 220 | 221 | protected override Expression Transform(FilterTextExpression tx) 222 | { 223 | throw new InvalidOperationException("FilterTextExpression must be transformed prior to compilation."); 224 | } 225 | 226 | protected override Expression Transform(FilterLambdaExpression lmx) 227 | { 228 | var context = Expression.Parameter(typeof(LogEvent)); 229 | var parms = lmx.Parameters.Select(px => Tuple.Create(px, Expression.Parameter(typeof(object), px.ParameterName))).ToList(); 230 | var body = Splice(Transform(lmx.Body), context); 231 | var paramSwitcher = new ExpressionConstantMapper(parms.ToDictionary(px => (object)px.Item1, px => (Expression)px.Item2)); 232 | var rewritten = paramSwitcher.Visit(body); 233 | 234 | Type delegateType; 235 | if (lmx.Parameters.Length == 1) 236 | delegateType = typeof(Func); 237 | else if (lmx.Parameters.Length == 2) 238 | delegateType = typeof(Func); 239 | else 240 | throw new NotSupportedException("Unsupported lambda signature"); 241 | 242 | var lambda = Expression.Lambda(delegateType, rewritten, parms.Select(px => px.Item2).ToArray()); 243 | 244 | return Expression.Lambda(lambda, new[] { context }); 245 | } 246 | 247 | protected override Expression Transform(FilterParameterExpression prx) 248 | { 249 | // Will be within a lambda, which will subsequently sub-in the actual value 250 | var context = Expression.Parameter(typeof(LogEvent)); 251 | var constant = Expression.Constant(prx, typeof(object)); 252 | return Expression.Lambda(constant, new[] { context }); 253 | } 254 | 255 | protected override Expression Transform(FilterWildcardExpression wx) 256 | { 257 | return context => Undefined.Value; 258 | } 259 | 260 | protected override Expression Transform(FilterArrayExpression ax) 261 | { 262 | var context = Expression.Parameter(typeof(LogEvent)); 263 | var elements = ax.Elements.Select(Transform).Select(ex => Splice(ex, context)).ToArray(); 264 | var arr = Expression.NewArrayInit(typeof(object), elements); 265 | var sv = Expression.Call(OperatorMethods[Operators.RuntimeOpNewSequence], arr); 266 | return Expression.Lambda(Expression.Convert(sv, typeof(object)), context); 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Linq 5 | { 6 | class ParameterReplacementVisitor : ExpressionVisitor 7 | { 8 | readonly ParameterExpression[] _from, _to; 9 | 10 | public ParameterReplacementVisitor(ParameterExpression[] from, ParameterExpression[] to) 11 | { 12 | if (from == null) throw new ArgumentNullException(nameof(@from)); 13 | if (to == null) throw new ArgumentNullException(nameof(to)); 14 | if (from.Length != to.Length) throw new InvalidOperationException("Mismatched parameter lists"); 15 | _from = from; 16 | _to = to; 17 | } 18 | 19 | protected override Expression VisitParameter(ParameterExpression node) 20 | { 21 | for (var i = 0; i < _from.Length; i++) 22 | { 23 | if (node == _from[i]) return _to[i]; 24 | } 25 | return node; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Properties/PropertiesObjectAccessorTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Properties 5 | { 6 | class PropertiesObjectAccessorTransformer : FilterExpressionIdentityTransformer 7 | { 8 | public static FilterExpression Rewrite(FilterExpression actual) 9 | { 10 | return new PropertiesObjectAccessorTransformer().Transform(actual); 11 | } 12 | 13 | protected override FilterExpression Transform(FilterCallExpression lx) 14 | { 15 | if (!Operators.SameOperator(lx.OperatorName, Operators.OpElementAt) || lx.Operands.Length != 2) 16 | return base.Transform(lx); 17 | 18 | var p = lx.Operands[0] as FilterPropertyExpression; 19 | var n = lx.Operands[1] as FilterTextExpression; 20 | 21 | if (p == null || n == null || !p.IsBuiltIn || p.PropertyName != "Properties" || 22 | n.Matching == FilterTextMatching.RegularExpression || n.Matching == FilterTextMatching.RegularExpressionInsensitive) 23 | return base.Transform(lx); 24 | 25 | return new FilterPropertyExpression(n.Text, false); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Text/IndexOfIgnoreCaseCallRewriter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | 3 | namespace Serilog.Filters.Expressions.Compilation.Text 4 | { 5 | class IndexOfIgnoreCaseCallRewriter : MatchingRewriter 6 | { 7 | public override FilterExpression Rewrite(FilterCallExpression lx, TextMatchingTransformer rewriteArm) 8 | { 9 | if (lx.Operands.Length != 2) 10 | return base.Rewrite(lx, rewriteArm); 11 | 12 | // Just a rename to match the runtime function. 13 | return new FilterCallExpression( 14 | Operators.RuntimeOpIndexOfIgnoreCase, 15 | rewriteArm.Transform(lx.Operands[0]), 16 | rewriteArm.Transform(lx.Operands[1])); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Text/MatchingRewriter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Superpower; 3 | using System.Linq; 4 | 5 | namespace Serilog.Filters.Expressions.Compilation.Text 6 | { 7 | abstract class MatchingRewriter 8 | { 9 | public virtual FilterExpression Rewrite(FilterCallExpression lx, 10 | TextMatchingTransformer rewriteArm) 11 | { 12 | return new FilterCallExpression(lx.OperatorName, lx.Operands.Select(rewriteArm.Transform).ToArray()); 13 | } 14 | 15 | protected static bool UseCaseInsensitiveRegexMatching(FilterTextExpression optTxt, FilterTextMatching matching) 16 | { 17 | return !(matching == FilterTextMatching.RegularExpression || 18 | optTxt?.Matching == FilterTextMatching.Exact); 19 | 20 | } 21 | 22 | protected static bool UseCaseInsensitiveTextMatching(FilterTextExpression tx0, FilterTextExpression tx1) 23 | { 24 | if (tx0 == null && tx1 == null) 25 | return false; 26 | 27 | return !(tx0?.Matching == FilterTextMatching.Exact || tx1?.Matching == FilterTextMatching.Exact); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Text/RegexSecondMatchCallRewriter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Text 5 | { 6 | class RegexSecondMatchCallRewriter : MatchingRewriter 7 | { 8 | public override FilterExpression Rewrite(FilterCallExpression lx, TextMatchingTransformer rewriteArm) 9 | { 10 | if (lx.Operands.Length != 2) 11 | return base.Rewrite(lx, rewriteArm); 12 | 13 | var tx0 = lx.Operands[0] as FilterTextExpression; 14 | var tx1 = lx.Operands[1] as FilterTextExpression; 15 | 16 | if (tx1?.Matching == FilterTextMatching.RegularExpression || 17 | tx1?.Matching == FilterTextMatching.RegularExpressionInsensitive) 18 | { 19 | var ci = RegexOptions.None; 20 | if (UseCaseInsensitiveRegexMatching(tx0, tx1.Matching)) 21 | ci = RegexOptions.IgnoreCase; 22 | 23 | var refind = new Regex(tx1.Text, ci | RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.Multiline); 24 | return new FilterCallExpression(Operators.ToRuntimePattern(lx.OperatorName), 25 | rewriteArm.Transform(lx.Operands[0]), 26 | new FilterConstantExpression(refind)); 27 | } 28 | 29 | return new FilterCallExpression( 30 | UseCaseInsensitiveTextMatching(tx0, tx1) ? Operators.ToRuntimeIgnoreCase(lx.OperatorName) : lx.OperatorName, 31 | rewriteArm.Transform(lx.Operands[0]), 32 | rewriteArm.Transform(lx.Operands[1])); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Text/SymmetricMatchCallRewriter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Text 5 | { 6 | class SymmetricMatchCallRewriter : MatchingRewriter 7 | { 8 | public override FilterExpression Rewrite(FilterCallExpression lx, TextMatchingTransformer rewriteArm) 9 | { 10 | if (lx.Operands.Length != 2) 11 | return base.Rewrite(lx, rewriteArm); 12 | 13 | var tx0 = lx.Operands[0] as FilterTextExpression; 14 | var tx1 = lx.Operands[1] as FilterTextExpression; 15 | 16 | if (tx0?.Matching == FilterTextMatching.RegularExpression || 17 | tx0?.Matching == FilterTextMatching.RegularExpressionInsensitive || 18 | tx1?.Matching == FilterTextMatching.RegularExpression || 19 | tx1?.Matching == FilterTextMatching.RegularExpressionInsensitive) 20 | { 21 | if (tx1?.Matching != FilterTextMatching.RegularExpression && 22 | tx1?.Matching != FilterTextMatching.RegularExpressionInsensitive) 23 | { 24 | // Make sure the regex is always second. 25 | return Rewrite(new FilterCallExpression(lx.OperatorName, lx.Operands[1], lx.Operands[0]), rewriteArm); 26 | } 27 | 28 | var ci = RegexOptions.None; 29 | if (UseCaseInsensitiveRegexMatching(tx0, tx1.Matching)) 30 | ci |= RegexOptions.IgnoreCase; 31 | 32 | var rewhole = new Regex("^" + tx1.Text + "$", ci | RegexOptions.ExplicitCapture); 33 | return new FilterCallExpression(Operators.ToRuntimePattern(lx.OperatorName), 34 | rewriteArm.Transform(lx.Operands[0]), 35 | new FilterConstantExpression(rewhole)); 36 | } 37 | 38 | return new FilterCallExpression( 39 | UseCaseInsensitiveTextMatching(tx0, tx1) ? Operators.ToRuntimeIgnoreCase(lx.OperatorName) : lx.OperatorName, 40 | rewriteArm.Transform(lx.Operands[0]), 41 | rewriteArm.Transform(lx.Operands[1])); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Text/TextMatchingTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | using System.Collections.Generic; 4 | 5 | namespace Serilog.Filters.Expressions.Compilation.Text 6 | { 7 | class TextMatchingTransformer : FilterExpressionIdentityTransformer 8 | { 9 | static readonly Dictionary Rewriters = new Dictionary(Operators.OperatorComparer) 10 | { 11 | [Operators.OpContains] = new RegexSecondMatchCallRewriter(), 12 | [Operators.OpIndexOf] = new RegexSecondMatchCallRewriter(), 13 | [Operators.OpIndexOfIgnoreCase] = new IndexOfIgnoreCaseCallRewriter(), 14 | [Operators.OpStartsWith] = new RegexSecondMatchCallRewriter(), 15 | [Operators.OpEndsWith] = new RegexSecondMatchCallRewriter(), 16 | [Operators.OpEqual] = new SymmetricMatchCallRewriter(), 17 | [Operators.OpNotEqual] = new SymmetricMatchCallRewriter() 18 | }; 19 | 20 | public static FilterExpression Rewrite(FilterExpression expression) 21 | { 22 | var transformer = new TextMatchingTransformer(); 23 | return transformer.Transform(expression); 24 | } 25 | 26 | protected override FilterExpression Transform(FilterCallExpression lx) 27 | { 28 | MatchingRewriter comparison; 29 | if (!Rewriters.TryGetValue(lx.OperatorName, out comparison)) 30 | return base.Transform(lx); 31 | 32 | return comparison.Rewrite(lx, this); 33 | } 34 | 35 | protected override FilterExpression Transform(FilterTextExpression tx) 36 | { 37 | // Since at this point the value is not being used in any matching-compatible 38 | // operation, it doesn't matter what the matching mode is. 39 | return new FilterConstantExpression(tx.Text); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Transformations/FilterExpressionIdentityTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Superpower; 3 | using System.Linq; 4 | 5 | namespace Serilog.Filters.Expressions.Compilation.Transformations 6 | { 7 | class FilterExpressionIdentityTransformer : FilterExpressionTransformer 8 | { 9 | protected override FilterExpression Transform(FilterCallExpression lx) 10 | { 11 | return new FilterCallExpression(lx.OperatorName, lx.Operands.Select(Transform).ToArray()); 12 | } 13 | 14 | protected override FilterExpression Transform(FilterConstantExpression cx) 15 | { 16 | return cx; 17 | } 18 | 19 | protected override FilterExpression Transform(FilterPropertyExpression px) 20 | { 21 | return px; 22 | } 23 | 24 | protected override FilterExpression Transform(FilterSubpropertyExpression spx) 25 | { 26 | return new FilterSubpropertyExpression(spx.PropertyName, Transform(spx.Receiver)); 27 | } 28 | 29 | protected override FilterExpression Transform(FilterTextExpression tx) 30 | { 31 | return tx; 32 | } 33 | 34 | protected override FilterExpression Transform(FilterLambdaExpression lmx) 35 | { 36 | // By default we maintain the parameters available in the body 37 | return new FilterLambdaExpression(lmx.Parameters, Transform(lmx.Body)); 38 | } 39 | 40 | // Only touches uses of the parameters, not decls 41 | protected override FilterExpression Transform(FilterParameterExpression prx) 42 | { 43 | return prx; 44 | } 45 | 46 | protected override FilterExpression Transform(FilterWildcardExpression wx) 47 | { 48 | return wx; 49 | } 50 | 51 | protected override FilterExpression Transform(FilterArrayExpression ax) 52 | { 53 | return new FilterArrayExpression(ax.Elements.Select(Transform).ToArray()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Transformations/FilterExpressionNodeReplacer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | 3 | namespace Serilog.Filters.Expressions.Compilation.Transformations 4 | { 5 | class FilterExpressionNodeReplacer : FilterExpressionIdentityTransformer 6 | { 7 | readonly FilterExpression _source; 8 | readonly FilterExpression _dest; 9 | 10 | public static FilterExpression Replace(FilterExpression expr, FilterExpression source, FilterExpression dest) 11 | { 12 | var replacer = new FilterExpressionNodeReplacer(source, dest); 13 | return replacer.Transform(expr); 14 | } 15 | 16 | FilterExpressionNodeReplacer(FilterExpression source, FilterExpression dest) 17 | { 18 | _source = source; 19 | _dest = dest; 20 | } 21 | 22 | protected override FilterExpression Transform(FilterCallExpression lx) 23 | { 24 | if (lx == _source) 25 | return _dest; 26 | 27 | return base.Transform(lx); 28 | } 29 | 30 | protected override FilterExpression Transform(FilterConstantExpression cx) 31 | { 32 | if (cx == _source) 33 | return _dest; 34 | 35 | return base.Transform(cx); 36 | } 37 | 38 | protected override FilterExpression Transform(FilterPropertyExpression px) 39 | { 40 | if (px == _source) 41 | return _dest; 42 | 43 | return base.Transform(px); 44 | } 45 | 46 | protected override FilterExpression Transform(FilterSubpropertyExpression spx) 47 | { 48 | if (spx == _source) 49 | return _dest; 50 | 51 | return base.Transform(spx); 52 | } 53 | 54 | protected override FilterExpression Transform(FilterTextExpression tx) 55 | { 56 | if (tx == _source) 57 | return _dest; 58 | 59 | return base.Transform(tx); 60 | } 61 | 62 | protected override FilterExpression Transform(FilterLambdaExpression lmx) 63 | { 64 | if (lmx == _source) 65 | return _dest; 66 | 67 | return base.Transform(lmx); 68 | } 69 | 70 | protected override FilterExpression Transform(FilterParameterExpression prx) 71 | { 72 | if (prx == _source) 73 | return _dest; 74 | 75 | return base.Transform(prx); 76 | } 77 | 78 | protected override FilterExpression Transform(FilterWildcardExpression wx) 79 | { 80 | if (wx == _source) 81 | return _dest; 82 | 83 | return base.Transform(wx); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using System; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Transformations 5 | { 6 | abstract class FilterExpressionTransformer 7 | { 8 | public TResult Transform(FilterExpression expression) 9 | { 10 | switch (expression.Type) 11 | { 12 | case FilterExpressionType.Call: 13 | return Transform((FilterCallExpression)expression); 14 | case FilterExpressionType.Constant: 15 | return Transform((FilterConstantExpression)expression); 16 | case FilterExpressionType.Subproperty: 17 | return Transform((FilterSubpropertyExpression)expression); 18 | case FilterExpressionType.Property: 19 | return Transform((FilterPropertyExpression)expression); 20 | case FilterExpressionType.Text: 21 | return Transform((FilterTextExpression)expression); 22 | case FilterExpressionType.Lambda: 23 | return Transform((FilterLambdaExpression)expression); 24 | case FilterExpressionType.Parameter: 25 | return Transform((FilterParameterExpression)expression); 26 | case FilterExpressionType.Wildcard: 27 | return Transform((FilterWildcardExpression)expression); 28 | case FilterExpressionType.Array: 29 | return Transform((FilterArrayExpression)expression); 30 | default: 31 | throw new ArgumentException(expression.Type + " is not a valid expression type"); 32 | } 33 | } 34 | 35 | protected abstract TResult Transform(FilterCallExpression lx); 36 | protected abstract TResult Transform(FilterConstantExpression cx); 37 | protected abstract TResult Transform(FilterPropertyExpression px); 38 | protected abstract TResult Transform(FilterSubpropertyExpression spx); 39 | protected abstract TResult Transform(FilterTextExpression tx); 40 | protected abstract TResult Transform(FilterLambdaExpression lmx); 41 | protected abstract TResult Transform(FilterParameterExpression prx); 42 | protected abstract TResult Transform(FilterWildcardExpression wx); 43 | protected abstract TResult Transform(FilterArrayExpression ax); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Wildcards/FilterExpressionWildcardSearch.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | using Superpower; 4 | using System.Linq; 5 | 6 | namespace Serilog.Filters.Expressions.Compilation.Wildcards 7 | { 8 | class FilterExpressionWildcardSearch : FilterExpressionTransformer 9 | { 10 | public static FilterCallExpression FindElementAtWildcard(FilterExpression fx) 11 | { 12 | var search = new FilterExpressionWildcardSearch(); 13 | return search.Transform(fx); 14 | } 15 | 16 | protected override FilterCallExpression Transform(FilterCallExpression lx) 17 | { 18 | if (!Operators.SameOperator(lx.OperatorName, Operators.OpElementAt) || lx.Operands.Length != 2) 19 | return lx.Operands.Select(Transform).FirstOrDefault(e => e != null); 20 | 21 | if (lx.Operands[1] is FilterWildcardExpression) 22 | return lx; 23 | 24 | return Transform(lx.Operands[0]); 25 | } 26 | 27 | protected override FilterCallExpression Transform(FilterConstantExpression cx) 28 | { 29 | return null; 30 | } 31 | 32 | protected override FilterCallExpression Transform(FilterPropertyExpression px) 33 | { 34 | return null; 35 | } 36 | 37 | protected override FilterCallExpression Transform(FilterSubpropertyExpression spx) 38 | { 39 | return Transform(spx.Receiver); 40 | } 41 | 42 | protected override FilterCallExpression Transform(FilterTextExpression tx) 43 | { 44 | return null; 45 | } 46 | 47 | protected override FilterCallExpression Transform(FilterLambdaExpression lmx) 48 | { 49 | return null; 50 | } 51 | 52 | protected override FilterCallExpression Transform(FilterParameterExpression prx) 53 | { 54 | return null; 55 | } 56 | 57 | protected override FilterCallExpression Transform(FilterWildcardExpression wx) 58 | { 59 | // Must be RHS of ElementAt() 60 | return null; 61 | } 62 | 63 | protected override FilterCallExpression Transform(FilterArrayExpression ax) 64 | { 65 | return null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Serilog.Filters.Expressions.Compilation.Transformations; 3 | 4 | namespace Serilog.Filters.Expressions.Compilation.Wildcards 5 | { 6 | class WildcardComprehensionTransformer : FilterExpressionIdentityTransformer 7 | { 8 | int _nextParameter = 0; 9 | 10 | public static FilterExpression Expand(FilterExpression root) 11 | { 12 | var wc = new WildcardComprehensionTransformer(); 13 | return wc.Transform(root); 14 | } 15 | 16 | protected override FilterExpression Transform(FilterCallExpression lx) 17 | { 18 | if (!Operators.WildcardComparators.Contains(lx.OperatorName) || lx.Operands.Length != 2) 19 | return base.Transform(lx); 20 | 21 | var lhsIs = FilterExpressionWildcardSearch.FindElementAtWildcard(lx.Operands[0]); 22 | var rhsIs = FilterExpressionWildcardSearch.FindElementAtWildcard(lx.Operands[1]); 23 | if (lhsIs != null && rhsIs != null || lhsIs == null && rhsIs == null) 24 | return base.Transform(lx); // N/A, or invalid 25 | 26 | var wcpath = lhsIs != null ? lx.Operands[0] : lx.Operands[1]; 27 | var comparand = lhsIs != null ? lx.Operands[1] : lx.Operands[0]; 28 | var elmat = lhsIs ?? rhsIs; 29 | 30 | var parm = new FilterParameterExpression("p" + _nextParameter++); 31 | var nestedComparand = FilterExpressionNodeReplacer.Replace(wcpath, elmat, parm); 32 | 33 | var coll = elmat.Operands[0]; 34 | var wc = ((FilterWildcardExpression)elmat.Operands[1]).Wildcard; 35 | 36 | var comparisonArgs = lhsIs != null ? new[] { nestedComparand, comparand } : new[] { comparand, nestedComparand }; 37 | var body = new FilterCallExpression(lx.OperatorName, comparisonArgs); 38 | var lambda = new FilterLambdaExpression(new[] { parm }, body); 39 | 40 | var op = Operators.ToRuntimeWildcardOperator(wc); 41 | var call = new FilterCallExpression(op, new[] { coll, lambda }); 42 | return Transform(call); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/FilterExpressionLanguage.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Filters.Expressions.Compilation; 3 | using System; 4 | using System.Linq; 5 | using Serilog.Filters.Expressions.Parsing; 6 | 7 | namespace Serilog.Filters.Expressions 8 | { 9 | /// 10 | /// Helper methods to assist with construction of well-formed filters. 11 | /// 12 | public static class FilterLanguage 13 | { 14 | /// 15 | /// Create a log event filter based on the provided expression. 16 | /// 17 | /// A filter expression. 18 | /// A function that evaluates the expression in the context of the log event. 19 | public static Func CreateFilter(string expression) 20 | { 21 | if (!TryCreateFilter(expression, out var filter, out var error)) 22 | throw new ArgumentException(error); 23 | 24 | return filter; 25 | } 26 | 27 | /// 28 | /// Create a log event filter based on the provided expression. 29 | /// 30 | /// A filter expression. 31 | /// A function that evaluates the expression in the context of the log event. 32 | /// The reported error, if compilation was unsuccessful. 33 | /// True if the filter could be created; otherwise, false. 34 | public static bool TryCreateFilter(string expression, out Func filter, out string error) 35 | { 36 | if (!FilterExpressionParser.TryParse(expression, out var root, out error)) 37 | { 38 | filter = null; 39 | return false; 40 | } 41 | 42 | filter = FilterExpressionCompiler.CompileAndExpose(root); 43 | error = null; 44 | return true; 45 | } 46 | 47 | /// 48 | /// Escape a value that is to appear in a `like` expression. 49 | /// 50 | /// The text to escape. 51 | /// The text with any special values escaped. Will need to be passed through 52 | /// if it is being embedded directly into a filter expression. 53 | // ReSharper disable once UnusedMember.Global 54 | public static string EscapeLikeExpressionContent(string text) 55 | { 56 | if (text == null) throw new ArgumentNullException(nameof(text)); 57 | return EscapeStringContent(text) 58 | .Replace("%", "%%") 59 | .Replace("_", "__"); 60 | } 61 | 62 | /// 63 | /// Escape a fragment of text that will appear within a string. 64 | /// 65 | /// The text to escape. 66 | /// The text with any special values escaped. 67 | public static string EscapeStringContent(string text) 68 | { 69 | if (text == null) throw new ArgumentNullException(nameof(text)); 70 | return text.Replace("'", "''"); 71 | } 72 | 73 | /// 74 | /// Determine if the specified text is a valid property name. 75 | /// 76 | /// The text to check. 77 | /// True if the text can be used verbatim as a property name. 78 | public static bool IsValidPropertyName(string propertyName) 79 | { 80 | return propertyName.Length != 0 && 81 | !char.IsDigit(propertyName[0]) && 82 | propertyName.All(ch => char.IsLetter(ch) || char.IsDigit(ch) || ch == '_'); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/LoggingFilterSwitch.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2015 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Serilog.Core; 16 | using Serilog.Events; 17 | using System; 18 | 19 | namespace Serilog.Filters.Expressions 20 | { 21 | /// 22 | /// A log event filter that can be modified at runtime. 23 | /// 24 | public class LoggingFilterSwitch : ILogEventFilter 25 | { 26 | // Reference assignments are atomic. While this class makes 27 | // no attempt to synchronize Expression, ToString(), and IsIncluded(), 28 | // for any observer, this at least ensures they won't be permanently out-of-sync for 29 | // all observers. 30 | volatile Tuple> _filter; 31 | 32 | /// 33 | /// Construct a , optionally initialized 34 | /// with the . 35 | /// 36 | /// A filter expression against which log events will be tested. 37 | /// Only expressions that evaluate to true are included 38 | /// by the filter. A null expression will accept all 39 | /// events. 40 | public LoggingFilterSwitch(string expression = null) 41 | { 42 | Expression = expression; 43 | } 44 | 45 | /// 46 | /// A filter expression against which log events will be tested. 47 | /// Only expressions that evaluate to true are included 48 | /// by the filter. A null expression will accept all 49 | /// events. 50 | /// 51 | public string Expression 52 | { 53 | get 54 | { 55 | var filter = _filter; 56 | return filter?.Item1; 57 | } 58 | set 59 | { 60 | if (value == null) 61 | { 62 | _filter = null; 63 | } 64 | else 65 | { 66 | _filter = new Tuple>( 67 | value, 68 | FilterLanguage.CreateFilter(value)); 69 | } 70 | } 71 | } 72 | 73 | /// 74 | public bool IsEnabled(LogEvent logEvent) 75 | { 76 | if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); 77 | 78 | var filter = _filter; 79 | 80 | if (filter == null) 81 | return true; 82 | 83 | return true.Equals(filter.Item2(logEvent)); 84 | } 85 | 86 | /// 87 | public override string ToString() 88 | { 89 | var filter = _filter; 90 | return filter?.Item1 ?? ""; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Operators.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Serilog.Filters.Expressions 6 | { 7 | static class Operators 8 | { 9 | public static StringComparer OperatorComparer { get; } = StringComparer.OrdinalIgnoreCase; 10 | 11 | // Core filter language 12 | // Op* means usable in expressions _and_ runtime executable. 13 | // RuntimeOp* means runtime only. 14 | 15 | public const string OpAdd = "Add"; 16 | public const string OpSubtract = "Subtract"; 17 | public const string OpMultiply = "Multiply"; 18 | public const string OpDivide = "Divide"; 19 | public const string OpModulo = "Modulo"; 20 | public const string OpPower = "Power"; 21 | public const string OpRound = "Round"; 22 | public const string OpAnd = "And"; 23 | public const string OpOr = "Or"; 24 | public const string OpLessThanOrEqual = "LessThanOrEqual"; 25 | public const string OpLessThan = "LessThan"; 26 | public const string OpGreaterThan = "GreaterThan"; 27 | public const string OpGreaterThanOrEqual = "GreaterThanOrEqual"; 28 | public const string OpEqual = "Equal"; 29 | public const string RuntimeOpEqualIgnoreCase = "_Internal_EqualIgnoreCase"; 30 | public const string RuntimeOpEqualPattern = "_Internal_EqualPattern"; 31 | public const string OpNotEqual = "NotEqual"; 32 | public const string RuntimeOpNotEqualIgnoreCase = "_Internal_NotEqualIgnoreCase"; 33 | public const string RuntimeOpNotEqualPattern = "_Internal_NotEqualPattern"; 34 | public const string OpNegate = "Negate"; 35 | public const string OpNot = "Not"; 36 | public const string OpContains = "Contains"; 37 | public const string RuntimeOpContainsIgnoreCase = "_Internal_ContainsIgnoreCase"; 38 | public const string RuntimeOpContainsPattern = "_Internal_ContainsPattern"; 39 | public const string OpIndexOf = "IndexOf"; 40 | public const string RuntimeOpIndexOfIgnoreCase = "_Internal_IndexOfIgnoreCase"; 41 | public const string RuntimeOpIndexOfPattern = "_Internal_IndexOfPattern"; 42 | public const string OpLength = "Length"; 43 | public const string OpStartsWith = "StartsWith"; 44 | public const string RuntimeOpStartsWithIgnoreCase = "_Internal_StartsWithIgnoreCase"; 45 | public const string RuntimeOpStartsWithPattern = "_Internal_StartsWithPattern"; 46 | public const string OpEndsWith = "EndsWith"; 47 | public const string RuntimeOpEndsWithIgnoreCase = "_Internal_EndsWithIgnoreCase"; 48 | public const string RuntimeOpEndsWithPattern = "_Internal_EndsWithPattern"; 49 | public const string OpHas = "Has"; 50 | public const string OpArrived = "Arrived"; 51 | public const string OpDateTime = "DateTime"; 52 | public const string OpTimeSpan = "TimeSpan"; 53 | public const string OpTimeOfDay = "TimeOfDay"; 54 | public const string OpElementAt = "ElementAt"; 55 | public const string RuntimeOpAny = "_Internal_Any"; 56 | public const string RuntimeOpAll = "_Internal_All"; 57 | public const string OpTypeOf = "TypeOf"; 58 | public const string OpTotalMilliseconds = "TotalMilliseconds"; 59 | public const string RuntimeOpIsNull = "_Internal_IsNull"; 60 | public const string RuntimeOpIsNotNull = "_Internal_IsNotNull"; 61 | public const string OpCoalesce = "Coalesce"; 62 | public const string IntermediateOpSqlLike = "_Internal_Like"; 63 | public const string IntermediateOpSqlNotLike = "_Internal_NotLike"; 64 | public const string IntermediateOpSqlIs = "_Internal_Is"; 65 | public const string RuntimeOpSqlIn = "_Internal_In"; 66 | public const string IntermediateOpSqlNotIn = "_Internal_NotIn"; 67 | public const string RuntimeOpStrictNot = "_Internal_StrictNot"; 68 | public const string OpSubstring = "Substring"; 69 | public const string RuntimeOpNewSequence = "_Internal_NewSequence"; 70 | 71 | // Breaks the symmetry because there's no other way to express this in SQL. 72 | public const string OpIndexOfIgnoreCase = "IndexOfIgnoreCase"; 73 | 74 | public static readonly HashSet WildcardComparators = new HashSet(OperatorComparer) 75 | { 76 | OpContains, 77 | OpStartsWith, 78 | OpEndsWith, 79 | OpNotEqual, 80 | OpEqual, 81 | OpLessThan, 82 | OpLessThanOrEqual, 83 | OpGreaterThan, 84 | OpGreaterThanOrEqual, 85 | IntermediateOpSqlLike, 86 | IntermediateOpSqlNotLike, 87 | RuntimeOpSqlIn, 88 | IntermediateOpSqlNotIn, 89 | IntermediateOpSqlIs 90 | }; 91 | 92 | public static readonly HashSet LogicalOperators = new HashSet(OperatorComparer) 93 | { 94 | OpAnd, 95 | OpOr, 96 | OpNot 97 | }; 98 | 99 | public static readonly HashSet UserSyntaxRootOperators = new HashSet(LogicalOperators, OperatorComparer) 100 | { 101 | OpHas, 102 | OpContains, 103 | OpStartsWith, 104 | OpEndsWith, 105 | OpNotEqual, 106 | OpEqual, 107 | OpLessThan, 108 | OpLessThanOrEqual, 109 | OpGreaterThan, 110 | OpGreaterThanOrEqual, 111 | IntermediateOpSqlLike, 112 | IntermediateOpSqlNotLike, 113 | RuntimeOpSqlIn, 114 | IntermediateOpSqlNotIn, 115 | IntermediateOpSqlIs 116 | }; 117 | 118 | public static bool SameOperator(string op1, string op2) 119 | { 120 | if (op1 == null) throw new ArgumentNullException(nameof(op1)); 121 | if (op2 == null) throw new ArgumentNullException(nameof(op2)); 122 | 123 | return OperatorComparer.Equals(op1, op2); 124 | } 125 | 126 | public static string ToRuntimeIgnoreCase(string op) 127 | { 128 | return $"_Internal_{op}IgnoreCase"; 129 | } 130 | 131 | public static string ToRuntimePattern(string op) 132 | { 133 | return $"_Internal_{op}Pattern"; 134 | } 135 | 136 | public static string ToRuntimeWildcardOperator(FilterWildcard wildcard) 137 | { 138 | return "_Internal_" + wildcard; // "Any"/"All" 139 | } 140 | 141 | public static bool IsRuntimeVariant(string op, string variant) 142 | { 143 | return SameOperator(op, variant) || 144 | SameOperator(ToRuntimeIgnoreCase(op), variant) || 145 | SameOperator(ToRuntimePattern(op), variant); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionKeyword.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Parsing 4 | { 5 | struct FilterExpressionKeyword 6 | { 7 | public string Text { get; } 8 | public FilterExpressionToken Token { get; } 9 | 10 | public FilterExpressionKeyword(string text, FilterExpressionToken token) 11 | { 12 | if (text == null) throw new ArgumentNullException(nameof(text)); 13 | Text = text; 14 | Token = token; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Filters.Expressions.Ast; 3 | 4 | namespace Serilog.Filters.Expressions.Parsing 5 | { 6 | static class FilterExpressionParser 7 | { 8 | public static FilterExpression Parse(string filterExpression) 9 | { 10 | if (!TryParse(filterExpression, out var root, out var error)) 11 | throw new ArgumentException(error); 12 | 13 | return root; 14 | } 15 | 16 | public static bool TryParse(string filterExpression, out FilterExpression root, out string error) 17 | { 18 | if (filterExpression == null) throw new ArgumentNullException(nameof(filterExpression)); 19 | 20 | var tokenList = FilterExpressionTokenizer.Instance.TryTokenize(filterExpression); 21 | if (!tokenList.HasValue) 22 | { 23 | error = tokenList.ToString(); 24 | root = null; 25 | return false; 26 | } 27 | 28 | var result = FilterExpressionTokenParsers.TryParse(tokenList.Value); 29 | if (!result.HasValue) 30 | { 31 | error = result.ToString(); 32 | root = null; 33 | return false; 34 | } 35 | 36 | root = result.Value; 37 | error = null; 38 | return true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionTextParsers.cs: -------------------------------------------------------------------------------- 1 | using Superpower; 2 | using Superpower.Model; 3 | using Superpower.Parsers; 4 | using System.Linq; 5 | 6 | namespace Serilog.Filters.Expressions.Parsing 7 | { 8 | static class FilterExpressionTextParsers 9 | { 10 | static readonly TextParser LessOrEqual = Span.EqualTo("<=").Value(FilterExpressionToken.LessThanOrEqual); 11 | static readonly TextParser GreaterOrEqual = Span.EqualTo(">=").Value(FilterExpressionToken.GreaterThanOrEqual); 12 | static readonly TextParser NotEqual = Span.EqualTo("<>").Value(FilterExpressionToken.NotEqual); 13 | 14 | public static TextParser CompoundOperator = GreaterOrEqual.Or(LessOrEqual.Try().Or(NotEqual)); 15 | 16 | public static TextParser HexInteger = 17 | Span.EqualTo("0x") 18 | .IgnoreThen(Character.Digit.Or(Character.Matching(ch => ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F', "a-f")) 19 | .Named("hex digit") 20 | .AtLeastOnce()) 21 | .Select(chrs => new string(chrs)); 22 | 23 | public static TextParser SqlStringContentChar = 24 | Span.EqualTo("''").Value('\'').Try().Or(Character.ExceptIn('\'', '\r', '\n')); 25 | 26 | public static TextParser SqlString = 27 | Character.EqualTo('\'') 28 | .IgnoreThen(SqlStringContentChar.Many()) 29 | .Then(s => Character.EqualTo('\'').Value(new string(s))); 30 | 31 | public static TextParser RegularExpressionContentChar { get; } = 32 | Span.EqualTo(@"\/").Value('/').Try().Or(Character.Except('/')); 33 | 34 | public static TextParser RegularExpression = 35 | Character.EqualTo('/') 36 | .IgnoreThen(RegularExpressionContentChar.Many()) 37 | .IgnoreThen(Character.EqualTo('/')) 38 | .Value(Unit.Value); 39 | 40 | public static TextParser Real = 41 | Numerics.Integer 42 | .Then(n => Character.EqualTo('.').IgnoreThen(Numerics.Integer).OptionalOrDefault() 43 | .Select(f => f == TextSpan.None ? n : new TextSpan(n.Source, n.Position, n.Length + f.Length + 1))); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionToken.cs: -------------------------------------------------------------------------------- 1 | using Superpower.Display; 2 | using Superpower.Parsers; 3 | 4 | namespace Serilog.Filters.Expressions.Parsing 5 | { 6 | enum FilterExpressionToken 7 | { 8 | None, 9 | 10 | Identifier, 11 | 12 | [Token(Description = "built-in identifier")] 13 | BuiltInIdentifier, 14 | 15 | String, 16 | 17 | [Token(Description = "regular expression")] 18 | RegularExpression, 19 | 20 | Number, 21 | 22 | [Token(Description = "hexadecimal number")] 23 | HexNumber, 24 | 25 | [Token(Example = ",")] 26 | Comma, 27 | 28 | [Token(Example = ".")] 29 | Period, 30 | 31 | [Token(Example = "[")] 32 | LBracket, 33 | 34 | [Token(Example = "]")] 35 | RBracket, 36 | 37 | [Token(Example = "(")] 38 | LParen, 39 | 40 | [Token(Example = ")")] 41 | RParen, 42 | 43 | [Token(Example = "?")] 44 | QuestionMark, 45 | 46 | [Token(Category = "operator", Example = "+")] 47 | Plus, 48 | 49 | [Token(Category = "operator", Example = "-")] 50 | Minus, 51 | 52 | [Token(Example = "*")] 53 | Asterisk, 54 | 55 | [Token(Category = "operator", Example = "/")] 56 | ForwardSlash, 57 | 58 | [Token(Category = "operator", Example = "%")] 59 | Percent, 60 | 61 | [Token(Category = "operator", Example = "^")] 62 | Caret, 63 | 64 | [Token(Category = "operator", Example = "<")] 65 | LessThan, 66 | 67 | [Token(Category = "operator", Example = "<=")] 68 | LessThanOrEqual, 69 | 70 | [Token(Category = "operator", Example = ">")] 71 | GreaterThan, 72 | 73 | [Token(Category = "operator", Example = ">=")] 74 | GreaterThanOrEqual, 75 | 76 | [Token(Category = "operator", Example = "=")] 77 | Equal, 78 | 79 | [Token(Category = "operator", Example = "<>")] 80 | NotEqual, 81 | 82 | [Token(Category = "keyword", Example = "and")] 83 | And, 84 | 85 | [Token(Category = "keyword", Example = "in")] 86 | In, 87 | 88 | [Token(Category = "keyword", Example = "is")] 89 | Is, 90 | 91 | [Token(Category = "keyword", Example = "like")] 92 | Like, 93 | 94 | [Token(Category = "keyword", Example = "not")] 95 | Not, 96 | 97 | [Token(Category = "keyword", Example = "or")] 98 | Or, 99 | 100 | [Token(Category = "keyword", Example = "true")] 101 | True, 102 | 103 | [Token(Category = "keyword", Example = "false")] 104 | False, 105 | 106 | [Token(Category = "keyword", Example = "null")] 107 | Null 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionTokenParsers.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Ast; 2 | using Superpower; 3 | using Superpower.Parsers; 4 | using System; 5 | using System.Globalization; 6 | using System.Linq; 7 | using Superpower.Model; 8 | 9 | namespace Serilog.Filters.Expressions.Parsing 10 | { 11 | static class FilterExpressionTokenParsers 12 | { 13 | public static TokenListParserResult TryParse( 14 | TokenList input) 15 | { 16 | return Expr.AtEnd().TryParse(input); 17 | } 18 | 19 | static readonly TokenListParser Add = Token.EqualTo(FilterExpressionToken.Plus).Value(Operators.OpAdd); 20 | static readonly TokenListParser Subtract = Token.EqualTo(FilterExpressionToken.Minus).Value(Operators.OpSubtract); 21 | static readonly TokenListParser Multiply = Token.EqualTo(FilterExpressionToken.Asterisk).Value(Operators.OpMultiply); 22 | static readonly TokenListParser Divide = Token.EqualTo(FilterExpressionToken.ForwardSlash).Value(Operators.OpDivide); 23 | static readonly TokenListParser Modulo = Token.EqualTo(FilterExpressionToken.Percent).Value(Operators.OpModulo); 24 | static readonly TokenListParser Power = Token.EqualTo(FilterExpressionToken.Caret).Value(Operators.OpPower); 25 | static readonly TokenListParser And = Token.EqualTo(FilterExpressionToken.And).Value(Operators.OpAnd); 26 | static readonly TokenListParser Or = Token.EqualTo(FilterExpressionToken.Or).Value(Operators.OpOr); 27 | static readonly TokenListParser Lte = Token.EqualTo(FilterExpressionToken.LessThanOrEqual).Value(Operators.OpLessThanOrEqual); 28 | static readonly TokenListParser Lt = Token.EqualTo(FilterExpressionToken.LessThan).Value(Operators.OpLessThan); 29 | static readonly TokenListParser Gt = Token.EqualTo(FilterExpressionToken.GreaterThan).Value(Operators.OpGreaterThan); 30 | static readonly TokenListParser Gte = Token.EqualTo(FilterExpressionToken.GreaterThanOrEqual).Value(Operators.OpGreaterThanOrEqual); 31 | static readonly TokenListParser Eq = Token.EqualTo(FilterExpressionToken.Equal).Value(Operators.OpEqual); 32 | static readonly TokenListParser Neq = Token.EqualTo(FilterExpressionToken.NotEqual).Value(Operators.OpNotEqual); 33 | static readonly TokenListParser Negate = Token.EqualTo(FilterExpressionToken.Minus).Value(Operators.OpNegate); 34 | static readonly TokenListParser Not = Token.EqualTo(FilterExpressionToken.Not).Value(Operators.OpNot); 35 | static readonly TokenListParser Like = Token.EqualTo(FilterExpressionToken.Like).Value(Operators.IntermediateOpSqlLike); 36 | static readonly TokenListParser NotLike = Not.IgnoreThen(Like).Value(Operators.IntermediateOpSqlNotLike); 37 | static readonly TokenListParser In = Token.EqualTo(FilterExpressionToken.In).Value(Operators.RuntimeOpSqlIn); 38 | static readonly TokenListParser NotIn = Not.IgnoreThen(In).Value(Operators.IntermediateOpSqlNotIn); 39 | static readonly TokenListParser Is = Token.EqualTo(FilterExpressionToken.Is).Value(Operators.IntermediateOpSqlIs); 40 | 41 | static readonly TokenListParser> PropertyPathStep = 42 | Token.EqualTo(FilterExpressionToken.Period) 43 | .IgnoreThen(Token.EqualTo(FilterExpressionToken.Identifier)) 44 | .Then(n => Parse.Return>(r => new FilterSubpropertyExpression(n.ToStringValue(), r))); 45 | 46 | static readonly TokenListParser Wildcard = 47 | Token.EqualTo(FilterExpressionToken.QuestionMark).Value((FilterExpression)new FilterWildcardExpression(FilterWildcard.Any)) 48 | .Or(Token.EqualTo(FilterExpressionToken.Asterisk).Value((FilterExpression)new FilterWildcardExpression(FilterWildcard.All))); 49 | 50 | static readonly TokenListParser> PropertyPathIndexerStep = 51 | from open in Token.EqualTo(FilterExpressionToken.LBracket) 52 | from indexer in Wildcard.Or(Parse.Ref(() => Expr)) 53 | from close in Token.EqualTo(FilterExpressionToken.RBracket) 54 | select new Func(r => new FilterCallExpression("ElementAt", r, indexer)); 55 | 56 | static readonly TokenListParser Function = 57 | (from name in Token.EqualTo(FilterExpressionToken.Identifier) 58 | from lparen in Token.EqualTo(FilterExpressionToken.LParen) 59 | from expr in Parse.Ref(() => Expr).ManyDelimitedBy(Token.EqualTo(FilterExpressionToken.Comma)) 60 | from rparen in Token.EqualTo(FilterExpressionToken.RParen) 61 | select (FilterExpression)new FilterCallExpression(name.ToStringValue(), expr)).Named("function"); 62 | 63 | static readonly TokenListParser ArrayLiteral = 64 | (from lbracket in Token.EqualTo(FilterExpressionToken.LBracket) 65 | from expr in Parse.Ref(() => Expr).ManyDelimitedBy(Token.EqualTo(FilterExpressionToken.Comma)) 66 | from rbracket in Token.EqualTo(FilterExpressionToken.RBracket) 67 | select (FilterExpression)new FilterArrayExpression(expr)).Named("array"); 68 | 69 | static readonly TokenListParser RootProperty = 70 | Token.EqualTo(FilterExpressionToken.BuiltInIdentifier).Select(b => (FilterExpression)new FilterPropertyExpression(b.ToStringValue().Substring(1), true)) 71 | .Or(Token.EqualTo(FilterExpressionToken.Identifier).Select(t => (FilterExpression)new FilterPropertyExpression(t.ToStringValue(), false))); 72 | 73 | static readonly TokenListParser PropertyPath = 74 | (from notfunction in Parse.Not(Token.EqualTo(FilterExpressionToken.Identifier).IgnoreThen(Token.EqualTo(FilterExpressionToken.LParen))) 75 | from root in RootProperty 76 | from path in PropertyPathStep.Or(PropertyPathIndexerStep).Many() 77 | select path.Aggregate(root, (o, f) => f(o))).Named("property"); 78 | 79 | static readonly TokenListParser RegularExpression = 80 | Token.EqualTo(FilterExpressionToken.RegularExpression) 81 | .Select(r => 82 | { 83 | var value = r.ToStringValue(); 84 | return (FilterExpression)new FilterTextExpression(value.Substring(1, value.Length - 2), FilterTextMatching.RegularExpression); 85 | }); 86 | 87 | static readonly TokenListParser SqlString = 88 | Token.EqualTo(FilterExpressionToken.String) 89 | .Apply(FilterExpressionTextParsers.SqlString) 90 | .Select(s => (FilterExpression)new FilterTextExpression(s, FilterTextMatching.Exact)); 91 | 92 | static readonly TokenListParser HexNumber = 93 | Token.EqualTo(FilterExpressionToken.HexNumber) 94 | .Apply(FilterExpressionTextParsers.HexInteger) 95 | .SelectCatch(n => ulong.Parse(n, NumberStyles.HexNumber, CultureInfo.InvariantCulture), "the numeric literal is too large") 96 | .Select(u => (FilterExpression)new FilterConstantExpression((decimal)u)); 97 | 98 | static readonly TokenListParser Number = 99 | Token.EqualTo(FilterExpressionToken.Number) 100 | .Apply(FilterExpressionTextParsers.Real) 101 | .SelectCatch(n => decimal.Parse(n.ToStringValue(), CultureInfo.InvariantCulture), "the numeric literal is too large") 102 | .Select(d => (FilterExpression)new FilterConstantExpression(d)); 103 | 104 | static readonly TokenListParser Literal = 105 | SqlString 106 | .Or(RegularExpression) 107 | .Or(Number) 108 | .Or(HexNumber) 109 | .Or(Token.EqualTo(FilterExpressionToken.True).Value((FilterExpression)new FilterConstantExpression(true))) 110 | .Or(Token.EqualTo(FilterExpressionToken.False).Value((FilterExpression)new FilterConstantExpression(false))) 111 | .Or(Token.EqualTo(FilterExpressionToken.Null).Value((FilterExpression)new FilterConstantExpression(null))) 112 | .Named("literal"); 113 | 114 | static readonly TokenListParser Item = Literal.Or(PropertyPath).Or(Function).Or(ArrayLiteral); 115 | 116 | static readonly TokenListParser Factor = 117 | (from lparen in Token.EqualTo(FilterExpressionToken.LParen) 118 | from expr in Parse.Ref(() => Expr) 119 | from rparen in Token.EqualTo(FilterExpressionToken.RParen) 120 | select expr) 121 | .Or(Item); 122 | 123 | static readonly TokenListParser Operand = 124 | (from op in Negate.Or(Not) 125 | from factor in Factor 126 | select MakeUnary(op, factor)).Or(Factor).Named("expression"); 127 | 128 | static readonly TokenListParser InnerTerm = Parse.Chain(Power, Operand, MakeBinary); 129 | 130 | static readonly TokenListParser Term = Parse.Chain(Multiply.Or(Divide).Or(Modulo), InnerTerm, MakeBinary); 131 | 132 | static readonly TokenListParser Comparand = Parse.Chain(Add.Or(Subtract), Term, MakeBinary); 133 | 134 | static readonly TokenListParser Comparison = Parse.Chain(Is.Or(NotLike.Try().Or(Like)).Or(NotIn.Try().Or(In)).Or(Lte.Or(Neq).Or(Lt)).Or(Gte.Or(Gt)).Or(Eq), Comparand, MakeBinary); 135 | 136 | static readonly TokenListParser Conjunction = Parse.Chain(And, Comparison, MakeBinary); 137 | 138 | static readonly TokenListParser Disjunction = Parse.Chain(Or, Conjunction, MakeBinary); 139 | 140 | static readonly TokenListParser Expr = Disjunction; 141 | 142 | static FilterExpression MakeBinary(string operatorName, FilterExpression leftOperand, FilterExpression rightOperand) 143 | { 144 | return new FilterCallExpression(operatorName, leftOperand, rightOperand); 145 | } 146 | 147 | static FilterExpression MakeUnary(string operatorName, FilterExpression operand) 148 | { 149 | return new FilterCallExpression(operatorName, operand); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/FilterExpressionTokenizer.cs: -------------------------------------------------------------------------------- 1 | using Superpower; 2 | using Superpower.Model; 3 | using System.Collections.Generic; 4 | 5 | namespace Serilog.Filters.Expressions.Parsing 6 | { 7 | class FilterExpressionTokenizer : Tokenizer 8 | { 9 | static readonly FilterExpressionToken[] SimpleOps = new FilterExpressionToken[128]; 10 | 11 | static readonly HashSet PreRegexTokens = new HashSet 12 | { 13 | FilterExpressionToken.And, 14 | FilterExpressionToken.Or, 15 | FilterExpressionToken.Not, 16 | FilterExpressionToken.LParen, 17 | FilterExpressionToken.LBracket, 18 | FilterExpressionToken.Comma, 19 | FilterExpressionToken.Equal, 20 | FilterExpressionToken.NotEqual, 21 | FilterExpressionToken.Like, 22 | FilterExpressionToken.GreaterThan, 23 | FilterExpressionToken.GreaterThanOrEqual, 24 | FilterExpressionToken.LessThan, 25 | FilterExpressionToken.LessThanOrEqual, 26 | FilterExpressionToken.In, 27 | FilterExpressionToken.Is 28 | }; 29 | 30 | static readonly FilterExpressionKeyword[] Keywords = 31 | { 32 | new FilterExpressionKeyword("and", FilterExpressionToken.And), 33 | new FilterExpressionKeyword("in", FilterExpressionToken.In), 34 | new FilterExpressionKeyword("is", FilterExpressionToken.Is), 35 | new FilterExpressionKeyword("like", FilterExpressionToken.Like), 36 | new FilterExpressionKeyword("not", FilterExpressionToken.Not), 37 | new FilterExpressionKeyword("or", FilterExpressionToken.Or), 38 | new FilterExpressionKeyword("true", FilterExpressionToken.True), 39 | new FilterExpressionKeyword("false", FilterExpressionToken.False), 40 | new FilterExpressionKeyword("null", FilterExpressionToken.Null) 41 | }; 42 | 43 | static FilterExpressionTokenizer() 44 | { 45 | SimpleOps['+'] = FilterExpressionToken.Plus; 46 | SimpleOps['-'] = FilterExpressionToken.Minus; 47 | SimpleOps['*'] = FilterExpressionToken.Asterisk; 48 | SimpleOps['/'] = FilterExpressionToken.ForwardSlash; 49 | SimpleOps['%'] = FilterExpressionToken.Percent; 50 | SimpleOps['^'] = FilterExpressionToken.Caret; 51 | SimpleOps['<'] = FilterExpressionToken.LessThan; 52 | SimpleOps['>'] = FilterExpressionToken.GreaterThan; 53 | SimpleOps['='] = FilterExpressionToken.Equal; 54 | SimpleOps[','] = FilterExpressionToken.Comma; 55 | SimpleOps['.'] = FilterExpressionToken.Period; 56 | SimpleOps['('] = FilterExpressionToken.LParen; 57 | SimpleOps[')'] = FilterExpressionToken.RParen; 58 | SimpleOps['['] = FilterExpressionToken.LBracket; 59 | SimpleOps[']'] = FilterExpressionToken.RBracket; 60 | SimpleOps['*'] = FilterExpressionToken.Asterisk; 61 | SimpleOps['?'] = FilterExpressionToken.QuestionMark; 62 | } 63 | 64 | protected override IEnumerable> Tokenize( 65 | TextSpan stringSpan, 66 | TokenizationState tokenizationState) 67 | { 68 | var next = SkipWhiteSpace(stringSpan); 69 | if (!next.HasValue) 70 | yield break; 71 | 72 | do 73 | { 74 | if (char.IsDigit(next.Value)) 75 | { 76 | var hex = FilterExpressionTextParsers.HexInteger(next.Location); 77 | if (hex.HasValue) 78 | { 79 | next = hex.Remainder.ConsumeChar(); 80 | yield return Result.Value(FilterExpressionToken.HexNumber, hex.Location, hex.Remainder); 81 | } 82 | else 83 | { 84 | var real = FilterExpressionTextParsers.Real(next.Location); 85 | if (!real.HasValue) 86 | yield return Result.CastEmpty(real); 87 | else 88 | yield return Result.Value(FilterExpressionToken.Number, real.Location, real.Remainder); 89 | 90 | next = real.Remainder.ConsumeChar(); 91 | } 92 | 93 | if (!IsDelimiter(next)) 94 | { 95 | yield return Result.Empty(next.Location, new[] { "digit" }); 96 | } 97 | } 98 | else if (next.Value == '\'') 99 | { 100 | var str = FilterExpressionTextParsers.SqlString(next.Location); 101 | if (!str.HasValue) 102 | yield return Result.CastEmpty(str); 103 | 104 | next = str.Remainder.ConsumeChar(); 105 | 106 | yield return Result.Value(FilterExpressionToken.String, str.Location, str.Remainder); 107 | } 108 | else if (next.Value == '@') 109 | { 110 | var beginIdentifier = next.Location; 111 | var startOfName = next.Remainder; 112 | do 113 | { 114 | next = next.Remainder.ConsumeChar(); 115 | } 116 | while (next.HasValue && char.IsLetterOrDigit(next.Value)); 117 | 118 | if (next.Remainder == startOfName) 119 | { 120 | yield return Result.Empty(startOfName, new[] { "built-in identifier name" }); 121 | } 122 | else 123 | { 124 | yield return Result.Value(FilterExpressionToken.BuiltInIdentifier, beginIdentifier, next.Location); 125 | } 126 | } 127 | else if (char.IsLetter(next.Value) || next.Value == '_') 128 | { 129 | var beginIdentifier = next.Location; 130 | do 131 | { 132 | next = next.Remainder.ConsumeChar(); 133 | } 134 | while (next.HasValue && (char.IsLetterOrDigit(next.Value) || next.Value == '_')); 135 | 136 | FilterExpressionToken keyword; 137 | if (TryGetKeyword(beginIdentifier.Until(next.Location), out keyword)) 138 | { 139 | yield return Result.Value(keyword, beginIdentifier, next.Location); 140 | } 141 | else 142 | { 143 | yield return Result.Value(FilterExpressionToken.Identifier, beginIdentifier, next.Location); 144 | } 145 | } 146 | else if (next.Value == '/' && 147 | (!tokenizationState.Previous.HasValue || 148 | PreRegexTokens.Contains(tokenizationState.Previous.Value.Kind))) 149 | { 150 | var regex = FilterExpressionTextParsers.RegularExpression(next.Location); 151 | if (!regex.HasValue) 152 | yield return Result.CastEmpty(regex); 153 | 154 | yield return Result.Value(FilterExpressionToken.RegularExpression, next.Location, regex.Remainder); 155 | next = regex.Remainder.ConsumeChar(); 156 | } 157 | else 158 | { 159 | var compoundOp = FilterExpressionTextParsers.CompoundOperator(next.Location); 160 | if (compoundOp.HasValue) 161 | { 162 | yield return Result.Value(compoundOp.Value, compoundOp.Location, compoundOp.Remainder); 163 | next = compoundOp.Remainder.ConsumeChar(); 164 | } 165 | else if (next.Value < SimpleOps.Length && SimpleOps[next.Value] != FilterExpressionToken.None) 166 | { 167 | yield return Result.Value(SimpleOps[next.Value], next.Location, next.Remainder); 168 | next = next.Remainder.ConsumeChar(); 169 | } 170 | else 171 | { 172 | yield return Result.Empty(next.Location); 173 | next = next.Remainder.ConsumeChar(); 174 | } 175 | } 176 | 177 | next = SkipWhiteSpace(next.Location); 178 | } while (next.HasValue); 179 | } 180 | 181 | static bool IsDelimiter(Result next) 182 | { 183 | return !next.HasValue || 184 | char.IsWhiteSpace(next.Value) || 185 | next.Value < SimpleOps.Length && SimpleOps[next.Value] != FilterExpressionToken.None; 186 | } 187 | 188 | static bool TryGetKeyword(TextSpan span, out FilterExpressionToken keyword) 189 | { 190 | foreach (var kw in Keywords) 191 | { 192 | if (span.EqualsValueIgnoreCase(kw.Text)) 193 | { 194 | keyword = kw.Token; 195 | return true; 196 | } 197 | } 198 | 199 | keyword = FilterExpressionToken.None; 200 | return false; 201 | } 202 | 203 | public static FilterExpressionTokenizer Instance { get; } = new FilterExpressionTokenizer(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Parsing/ParserExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Superpower; 3 | using Superpower.Model; 4 | 5 | namespace Serilog.Filters.Expressions.Parsing 6 | { 7 | static partial class ParserExtensions 8 | { 9 | public static TextParser SelectCatch(this TextParser parser, Func trySelector, string errorMessage) 10 | { 11 | if (parser == null) throw new ArgumentNullException(nameof(parser)); 12 | if (trySelector == null) throw new ArgumentNullException(nameof(trySelector)); 13 | if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage)); 14 | 15 | return input => 16 | { 17 | var t = parser(input); 18 | if (!t.HasValue) 19 | return Superpower.Model.Result.CastEmpty(t); 20 | 21 | try 22 | { 23 | var u = trySelector(t.Value); 24 | return Superpower.Model.Result.Value(u, input, t.Remainder); 25 | } 26 | catch 27 | { 28 | return Superpower.Model.Result.Empty(input, errorMessage); 29 | } 30 | }; 31 | } 32 | 33 | public static TokenListParser SelectCatch(this TokenListParser parser, Func trySelector, string errorMessage) 34 | { 35 | if (parser == null) throw new ArgumentNullException(nameof(parser)); 36 | if (trySelector == null) throw new ArgumentNullException(nameof(trySelector)); 37 | if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage)); 38 | 39 | return input => 40 | { 41 | var t = parser(input); 42 | if (!t.HasValue) 43 | return TokenListParserResult.CastEmpty(t); 44 | 45 | try 46 | { 47 | var u = trySelector(t.Value); 48 | return TokenListParserResult.Value(u, input, t.Remainder); 49 | } 50 | catch 51 | { 52 | return TokenListParserResult.Empty(input, errorMessage); 53 | } 54 | }; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/AcceptNullAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Runtime 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | class AcceptNullAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/AcceptUndefinedAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Runtime 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | class AcceptUndefinedAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/BooleanAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Runtime 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | class BooleanAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/NumericAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Runtime 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | class NumericAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/NumericComparableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Filters.Expressions.Runtime 4 | { 5 | [AttributeUsage(AttributeTargets.Method)] 6 | class NumericComparableAttribute : Attribute 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/Representation.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace Serilog.Filters.Expressions.Runtime 6 | { 7 | static class Representation 8 | { 9 | static readonly Type[] NumericTypes = new[] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), 10 | typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double) }; 11 | 12 | static readonly Type[] AllowedTypes = new[] { typeof(string), typeof(bool), typeof(TimeSpan), typeof(DateTime), 13 | typeof(DateTimeOffset) }; 14 | 15 | // Convert scalars into a small set of primitive types; leave everything else unchanged. This 16 | // makes it easier to flow values through operations. 17 | public static object Represent(LogEventPropertyValue value) 18 | { 19 | var sv = value as ScalarValue; 20 | if (sv != null) 21 | { 22 | if (sv.Value == null) 23 | return null; 24 | 25 | if (Array.IndexOf(AllowedTypes, sv.Value.GetType()) != -1) 26 | return sv.Value; 27 | 28 | if (Array.IndexOf(NumericTypes, sv.Value.GetType()) != -1) 29 | return Convert.ChangeType(sv.Value, typeof(decimal)); 30 | 31 | return sv.Value.ToString(); 32 | } 33 | 34 | return value; 35 | } 36 | 37 | public static object Expose(object internalValue) 38 | { 39 | if (internalValue is Undefined) 40 | return null; 41 | 42 | if (internalValue is ScalarValue) 43 | throw new InvalidOperationException("A `ScalarValue` should have been converted within the filtering function, but was returned as a result."); 44 | 45 | var sequence = internalValue as SequenceValue; 46 | if (sequence != null) 47 | return sequence.Elements.Select(ExposeOrRepresent).ToArray(); 48 | 49 | var structure = internalValue as StructureValue; 50 | if (structure != null) 51 | { 52 | var r = structure.Properties.ToDictionary(p => p.Name, p => ExposeOrRepresent(p.Value)); 53 | if (structure.TypeTag != null) 54 | r["$type"] = structure.TypeTag; 55 | return r; 56 | } 57 | 58 | var dictionary = internalValue as DictionaryValue; 59 | if (dictionary != null) 60 | { 61 | return dictionary.Elements.ToDictionary(p => ExposeOrRepresent(p.Key), p => ExposeOrRepresent(p.Value)); 62 | } 63 | 64 | return internalValue; 65 | } 66 | 67 | static object ExposeOrRepresent(object internalValue) 68 | { 69 | if (internalValue is Undefined) 70 | return null; 71 | 72 | if (internalValue is ScalarValue sv) 73 | return Represent(sv); 74 | 75 | return Expose(internalValue); 76 | } 77 | 78 | public static LogEventPropertyValue Recapture(object value) 79 | { 80 | return value is LogEventPropertyValue lepv ? lepv : new ScalarValue(value); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/RuntimeOperators.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Filters.Expressions.Compilation.Linq; 3 | using System; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace Serilog.Filters.Expressions.Runtime 10 | { 11 | static class RuntimeOperators 12 | { 13 | [Numeric] 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static object Add(object left, object right) 16 | { 17 | return (decimal)left + (decimal)right; 18 | } 19 | 20 | [Numeric] 21 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 22 | public static object Subtract(object left, object right) 23 | { 24 | return (decimal)left - (decimal)right; 25 | } 26 | 27 | [Numeric] 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | public static object Multiply(object left, object right) 30 | { 31 | return (decimal)left * (decimal)right; 32 | } 33 | 34 | [Numeric] 35 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 36 | public static object Divide(object left, object right) 37 | { 38 | if ((decimal)right == 0) return Undefined.Value; 39 | return (decimal)left / (decimal)right; 40 | } 41 | 42 | [Numeric] 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static object Modulo(object left, object right) 45 | { 46 | if ((decimal)right == 0) return Undefined.Value; 47 | return (decimal)left % (decimal)right; 48 | } 49 | 50 | [Numeric] 51 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 52 | public static object Power(object left, object right) 53 | { 54 | return (decimal)Math.Pow((double)(decimal)left, (double)(decimal)right); 55 | } 56 | 57 | [Boolean] 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | public static object And(object left, object right) 60 | { 61 | return true.Equals(left) && true.Equals(right); 62 | } 63 | 64 | [Boolean, AcceptUndefined, AcceptNull] 65 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 66 | public static object Or(object left, object right) 67 | { 68 | return true.Equals(left) || true.Equals(right); 69 | } 70 | 71 | [NumericComparable] 72 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 73 | public static object LessThanOrEqual(object left, object right) 74 | { 75 | return ((decimal)left) <= ((decimal)right); 76 | } 77 | 78 | [NumericComparable] 79 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 80 | public static object LessThan(object left, object right) 81 | { 82 | return ((decimal)left) < ((decimal)right); 83 | } 84 | 85 | [NumericComparable] 86 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 87 | public static object GreaterThan(object left, object right) 88 | { 89 | return ((decimal)left) > ((decimal)right); 90 | } 91 | 92 | [NumericComparable] 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 | public static object GreaterThanOrEqual(object left, object right) 95 | { 96 | return (decimal)left >= (decimal)right; 97 | } 98 | 99 | [AcceptNull] 100 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 101 | public static object Equal(object left, object right) 102 | { 103 | return UnboxedEqualHelper(left, right); 104 | } 105 | 106 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 | static bool UnboxedEqualHelper(object left, object right) 108 | { 109 | return left?.Equals(right) ?? right == null; 110 | } 111 | 112 | [AcceptNull] 113 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 114 | public static object _Internal_In(object item, object collection) 115 | { 116 | if (collection is SequenceValue arr) 117 | { 118 | for (var i = 0; i < arr.Elements.Count; ++i) 119 | if (UnboxedEqualHelper(Representation.Represent(arr.Elements[i]), item)) 120 | return true; 121 | 122 | return false; 123 | } 124 | 125 | return Undefined.Value; 126 | } 127 | 128 | [AcceptNull] 129 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 130 | public static object _Internal_EqualIgnoreCase(object left, object right) 131 | { 132 | if (left == null && right == null) 133 | return true; 134 | 135 | var ls = left as string; 136 | var rs = right as string; 137 | if (ls == null || rs == null) 138 | return Undefined.Value; 139 | 140 | return CompareInfo.Compare(ls, rs, CompareOptions.OrdinalIgnoreCase) == 0; 141 | } 142 | 143 | [AcceptNull] 144 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 145 | public static object _Internal_EqualPattern(object left, object right) 146 | { 147 | if (left == null && right == null) 148 | return true; 149 | 150 | var ls = left as string; 151 | var rs = right as Regex; 152 | if (ls == null || rs == null) 153 | return Undefined.Value; 154 | 155 | return rs.IsMatch(ls); 156 | } 157 | 158 | [AcceptNull] 159 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 160 | public static object NotEqual(object left, object right) 161 | { 162 | return !UnboxedEqualHelper(left, right); 163 | } 164 | 165 | [AcceptNull] 166 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 167 | public static object _Internal_NotEqualIgnoreCase(object left, object right) 168 | { 169 | var r = _Internal_EqualIgnoreCase(left, right); 170 | if (ReferenceEquals(r, Undefined.Value)) return r; 171 | return !(bool)r; 172 | } 173 | 174 | [AcceptNull] 175 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 176 | public static object _Internal_NotEqualPattern(object left, object right) 177 | { 178 | var r = _Internal_EqualPattern(left, right); 179 | if (ReferenceEquals(r, Undefined.Value)) return r; 180 | return !(bool)r; 181 | } 182 | 183 | [Numeric] 184 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 185 | public static object Negate(object operand) 186 | { 187 | return -((decimal)operand); 188 | } 189 | 190 | [Boolean, AcceptUndefined] 191 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 192 | public static object Not(object operand) 193 | { 194 | if (operand is Undefined) 195 | return true; 196 | 197 | return !((bool)operand); 198 | } 199 | 200 | [Boolean] 201 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 202 | public static object _Internal_StrictNot(object operand) 203 | { 204 | return !((bool)operand); 205 | } 206 | 207 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 208 | public static object Contains(object corpus, object pattern) 209 | { 210 | var ctx = corpus as string; 211 | var ptx = pattern as string; 212 | if (ctx == null || ptx == null) 213 | return Undefined.Value; 214 | return ctx.Contains(ptx); 215 | } 216 | 217 | static readonly CompareInfo CompareInfo = CultureInfo.InvariantCulture.CompareInfo; 218 | 219 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 220 | public static object _Internal_ContainsIgnoreCase(object corpus, object pattern) 221 | { 222 | var ctx = corpus as string; 223 | var ptx = pattern as string; 224 | if (ctx == null || ptx == null) 225 | return Undefined.Value; 226 | return CompareInfo.IndexOf(ctx, ptx, CompareOptions.OrdinalIgnoreCase) >= 0; 227 | } 228 | 229 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 230 | public static object _Internal_ContainsPattern(object corpus, object pattern) 231 | { 232 | var ctx = corpus as string; 233 | var ptx = pattern as Regex; 234 | if (ctx == null || ptx == null) 235 | return Undefined.Value; 236 | return ptx.IsMatch(ctx); 237 | } 238 | 239 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 240 | public static object IndexOf(object corpus, object pattern) 241 | { 242 | var ctx = corpus as string; 243 | var ptx = pattern as string; 244 | if (ctx == null || ptx == null) 245 | return Undefined.Value; 246 | return (decimal)ctx.IndexOf(ptx, StringComparison.Ordinal); 247 | } 248 | 249 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 250 | public static object _Internal_IndexOfIgnoreCase(object corpus, object pattern) 251 | { 252 | var ctx = corpus as string; 253 | var ptx = pattern as string; 254 | if (ctx == null || ptx == null) 255 | return Undefined.Value; 256 | return (decimal)ctx.IndexOf(ptx, StringComparison.OrdinalIgnoreCase); 257 | } 258 | 259 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 260 | public static object _Internal_IndexOfPattern(object corpus, object pattern) 261 | { 262 | var ctx = corpus as string; 263 | var ptx = pattern as Regex; 264 | if (ctx == null || ptx == null) 265 | return Undefined.Value; 266 | 267 | var m = ptx.Match(ctx); 268 | if (!m.Success) 269 | return -1m; 270 | 271 | return (decimal)m.Index; 272 | 273 | } 274 | 275 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 276 | public static object Length(object arg) 277 | { 278 | if (arg is string str) 279 | return (decimal)str.Length; 280 | 281 | if (arg is SequenceValue seq) 282 | return (decimal) seq.Elements.Count; 283 | 284 | return Undefined.Value; 285 | } 286 | 287 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 288 | public static object StartsWith(object corpus, object pattern) 289 | { 290 | var ctx = corpus as string; 291 | var ptx = pattern as string; 292 | if (ctx == null || ptx == null) 293 | return Undefined.Value; 294 | return ctx.StartsWith(ptx, StringComparison.Ordinal); 295 | } 296 | 297 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 298 | public static object _Internal_StartsWithIgnoreCase(object corpus, object pattern) 299 | { 300 | var ctx = corpus as string; 301 | var ptx = pattern as string; 302 | if (ctx == null || ptx == null) 303 | return Undefined.Value; 304 | return ctx.StartsWith(ptx, StringComparison.OrdinalIgnoreCase); 305 | } 306 | 307 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 308 | public static object _Internal_StartsWithPattern(object corpus, object pattern) 309 | { 310 | var ctx = corpus as string; 311 | var ptx = pattern as Regex; 312 | if (ctx == null || ptx == null) 313 | return Undefined.Value; 314 | var m = ptx.Match(ctx); 315 | return m.Success && m.Index == 0; 316 | } 317 | 318 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 319 | public static object EndsWith(object corpus, object pattern) 320 | { 321 | var ctx = corpus as string; 322 | var ptx = pattern as string; 323 | if (ctx == null || ptx == null) 324 | return Undefined.Value; 325 | return ctx.EndsWith(ptx, StringComparison.Ordinal); 326 | } 327 | 328 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 329 | public static object _Internal_EndsWithIgnoreCase(object corpus, object pattern) 330 | { 331 | var ctx = corpus as string; 332 | var ptx = pattern as string; 333 | if (ctx == null || ptx == null) 334 | return Undefined.Value; 335 | return ctx.EndsWith(ptx, StringComparison.OrdinalIgnoreCase); 336 | } 337 | 338 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 339 | public static object _Internal_EndsWithPattern(object corpus, object pattern) 340 | { 341 | var ctx = corpus as string; 342 | var ptx = pattern as Regex; 343 | if (ctx == null || ptx == null) 344 | return Undefined.Value; 345 | var matches = ptx.Matches(ctx); 346 | if (matches.Count == 0) 347 | return false; 348 | var m = matches[matches.Count - 1]; 349 | return m.Index + m.Length == ctx.Length; 350 | } 351 | 352 | [AcceptUndefined, AcceptNull] 353 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 354 | public static object Has(object value) 355 | { 356 | return !(value is Undefined); 357 | } 358 | 359 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 360 | public static object ElementAt(object items, object index) 361 | { 362 | if (items is SequenceValue arr) 363 | { 364 | if (!(index is decimal)) 365 | return Undefined.Value; 366 | 367 | var dec = (decimal)index; 368 | if (dec != Math.Floor(dec)) 369 | return Undefined.Value; 370 | 371 | var idx = (int)dec; 372 | if (idx >= arr.Elements.Count) 373 | return Undefined.Value; 374 | 375 | return Representation.Represent(arr.Elements.ElementAt(idx)); 376 | } 377 | 378 | if (items is StructureValue dict) 379 | { 380 | var s = index as string; 381 | if (s == null) 382 | return Undefined.Value; 383 | 384 | LogEventPropertyValue value; 385 | if (!LinqExpressionCompiler.TryGetStructurePropertyValue(dict, s, out value)) 386 | return Undefined.Value; 387 | 388 | return Representation.Represent(value); 389 | } 390 | 391 | // Case for DictionaryValue is missing, here. 392 | 393 | return Undefined.Value; 394 | } 395 | 396 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 397 | public static object _Internal_Any(object items, object predicate) 398 | { 399 | if (!(predicate is Func pred)) 400 | return Undefined.Value; 401 | 402 | if (items is SequenceValue arr) 403 | { 404 | return arr.Elements.Any(e => true.Equals(pred(Representation.Represent(e)))); 405 | } 406 | 407 | if (items is StructureValue structure) 408 | { 409 | return structure.Properties.Any(e => true.Equals(pred(Representation.Represent(e.Value)))); 410 | } 411 | 412 | return Undefined.Value; 413 | } 414 | 415 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 416 | public static object _Internal_All(object items, object predicate) 417 | { 418 | var pred = predicate as Func; 419 | if (pred == null) 420 | return Undefined.Value; 421 | 422 | SequenceValue arr = items as SequenceValue; 423 | if (arr != null) 424 | { 425 | return arr.Elements.All(e => true.Equals(pred(Representation.Represent(e)))); 426 | } 427 | 428 | var structure = items as StructureValue; 429 | if (structure != null) 430 | { 431 | return structure.Properties.All(e => true.Equals(pred(Representation.Represent(e.Value)))); 432 | } 433 | 434 | return Undefined.Value; 435 | } 436 | 437 | [AcceptNull, AcceptUndefined] 438 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 439 | public static object TypeOf(object value) 440 | { 441 | if (value is DictionaryValue) 442 | return "object"; // Follow the JSON system here 443 | 444 | if (value is StructureValue structure) 445 | return structure.TypeTag ?? "object"; 446 | 447 | if (value is SequenceValue) 448 | return "array"; 449 | 450 | if (value is string) 451 | return "string"; 452 | 453 | if (value is decimal) 454 | return "number"; 455 | 456 | if (value is bool) 457 | return "boolean"; 458 | 459 | if (value is Undefined) 460 | return "undefined"; 461 | 462 | if (value == null) 463 | return "null"; 464 | 465 | return Undefined.Value; 466 | } 467 | 468 | [AcceptUndefined, AcceptNull] 469 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 470 | public static object _Internal_IsNull(object value) 471 | { 472 | return value is Undefined || value == null; 473 | } 474 | 475 | [AcceptUndefined, AcceptNull] 476 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 477 | public static object _Internal_IsNotNull(object value) 478 | { 479 | return value != null && !(value is Undefined); 480 | } 481 | 482 | [AcceptUndefined, AcceptNull] 483 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 484 | public static object Coalesce(object v1, object v2) 485 | { 486 | if (v1 != null && !(v1 is Undefined)) 487 | return v1; 488 | 489 | return v2; 490 | } 491 | 492 | [AcceptNull] 493 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 494 | public static object Substring(object sval, object startIndex, object length) 495 | { 496 | var str = sval as string; 497 | if (str == null || !(startIndex is decimal) || length != null && !(length is decimal)) 498 | return Undefined.Value; 499 | 500 | var si = (decimal)startIndex; 501 | 502 | if (si < 0 || si >= str.Length || (int)si != si) 503 | return Undefined.Value; 504 | 505 | if (length == null) 506 | return str.Substring((int)si); 507 | 508 | var len = (decimal)length; 509 | if ((int)len != len) 510 | return Undefined.Value; 511 | 512 | if (len + si > str.Length) 513 | return str.Substring((int)si); 514 | 515 | return str.Substring((int)si, (int)len); 516 | } 517 | 518 | // This helper method is not called as an operator; rather, we use it 519 | // to avoid uglier LINQ expression code to create array literals in 520 | // `LinqExpressionCompiler`. 521 | public static SequenceValue _Internal_NewSequence(object[] arr) 522 | { 523 | if (arr == null) throw new ArgumentNullException(nameof(arr)); 524 | return new SequenceValue(arr.Select(Representation.Recapture)); 525 | } 526 | } 527 | } 528 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Filters/Expressions/Runtime/Undefined.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Filters.Expressions.Runtime 2 | { 3 | sealed class Undefined 4 | { 5 | public static readonly Undefined Value = new Undefined(); 6 | 7 | private Undefined() { } 8 | 9 | public override int GetHashCode() 10 | { 11 | return 0; 12 | } 13 | 14 | public override bool Equals(object obj) 15 | { 16 | return false; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/LoggerEnrichmentConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using Serilog.Configuration; 17 | using Serilog.Filters.Expressions; 18 | 19 | namespace Serilog 20 | { 21 | /// 22 | /// Extends logger enrichment configuration with methods for filtering with expressions. 23 | /// 24 | public static class LoggerEnrichmentConfigurationExtensions 25 | { 26 | /// 27 | /// Write to a sink only when evaluates to true. 28 | /// 29 | /// Enrichment configuration. 30 | /// An expression that evaluates to true when the supplied 31 | /// should be enriched. 32 | /// An action that configures the wrapped enricher. 33 | /// Configuration object allowing method chaining. 34 | /// The underlying . 35 | public static LoggerConfiguration When( 36 | this LoggerEnrichmentConfiguration loggerEnrichmentConfiguration, 37 | string expression, 38 | Action configureEnricher) 39 | { 40 | if (loggerEnrichmentConfiguration == null) throw new ArgumentNullException(nameof(loggerEnrichmentConfiguration)); 41 | if (expression == null) throw new ArgumentNullException(nameof(expression)); 42 | if (configureEnricher == null) throw new ArgumentNullException(nameof(configureEnricher)); 43 | 44 | var compiled = FilterLanguage.CreateFilter(expression); 45 | return loggerEnrichmentConfiguration.When(e => true.Equals(compiled(e)), configureEnricher); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/LoggerFilterConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | using Serilog.Filters.Expressions; 3 | using System; 4 | 5 | namespace Serilog 6 | { 7 | /// 8 | /// Extends logger filter configuration with methods for filtering with expressions. 9 | /// 10 | public static class LoggerFilterConfigurationExtensions 11 | { 12 | /// 13 | /// Include only log events that match the provided expression. 14 | /// 15 | /// Filter configuration. 16 | /// The expression to apply. 17 | /// The underlying . 18 | public static LoggerConfiguration ByIncludingOnly(this LoggerFilterConfiguration loggerFilterConfiguration, string expression) 19 | { 20 | if (loggerFilterConfiguration == null) throw new ArgumentNullException(nameof(loggerFilterConfiguration)); 21 | if (expression == null) throw new ArgumentNullException(nameof(expression)); 22 | 23 | var compiled = FilterLanguage.CreateFilter(expression); 24 | return loggerFilterConfiguration.ByIncludingOnly(e => true.Equals(compiled(e))); 25 | } 26 | 27 | /// 28 | /// Exclude log events that match the provided expression. 29 | /// 30 | /// Filter configuration. 31 | /// The expression to apply. 32 | /// The underlying . 33 | public static LoggerConfiguration ByExcluding(this LoggerFilterConfiguration loggerFilterConfiguration, string expression) 34 | { 35 | if (loggerFilterConfiguration == null) throw new ArgumentNullException(nameof(loggerFilterConfiguration)); 36 | if (expression == null) throw new ArgumentNullException(nameof(expression)); 37 | 38 | var compiled = FilterLanguage.CreateFilter(expression); 39 | return loggerFilterConfiguration.ByExcluding(e => true.Equals(compiled(e))); 40 | } 41 | 42 | /// 43 | /// Use a to dynamically control filtering. 44 | /// 45 | /// Filter configuration. 46 | /// A that can be used to dynamically control 47 | /// log filtering. 48 | /// The underlying . 49 | public static LoggerConfiguration ControlledBy(this LoggerFilterConfiguration loggerFilterConfiguration, LoggingFilterSwitch @switch) 50 | { 51 | if (loggerFilterConfiguration == null) throw new ArgumentNullException(nameof(loggerFilterConfiguration)); 52 | if (@switch == null) throw new ArgumentNullException(nameof(@switch)); 53 | 54 | return loggerFilterConfiguration.With(@switch); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/LoggerSinkConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using Serilog.Configuration; 17 | using Serilog.Filters.Expressions; 18 | 19 | namespace Serilog 20 | { 21 | /// 22 | /// Extends logger sink configuration with methods for filtering with expressions. 23 | /// 24 | public static class LoggerSinkConfigurationExtensions 25 | { 26 | /// 27 | /// Write to a sink only when evaluates to true. 28 | /// 29 | /// Sink configuration. 30 | /// An expression that evaluates to true when the 31 | /// supplied 32 | /// should be written to the configured sink. 33 | /// An action that configures the wrapped sink. 34 | /// Configuration object allowing method chaining. 35 | /// The underlying . 36 | public static LoggerConfiguration Conditional( 37 | this LoggerSinkConfiguration loggerSinkConfiguration, 38 | string expression, 39 | Action configureSink) 40 | { 41 | if (loggerSinkConfiguration == null) throw new ArgumentNullException(nameof(loggerSinkConfiguration)); 42 | if (expression == null) throw new ArgumentNullException(nameof(expression)); 43 | if (configureSink == null) throw new ArgumentNullException(nameof(configureSink)); 44 | 45 | var compiled = FilterLanguage.CreateFilter(expression); 46 | return loggerSinkConfiguration.Conditional(e => true.Equals(compiled(e)), configureSink); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: CLSCompliant(true)] 5 | 6 | [assembly: InternalsVisibleTo("Serilog.Filters.Expressions.Tests, PublicKey=" + 7 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" + 8 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" + 9 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" + 10 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" + 11 | "b19485ec")] 12 | -------------------------------------------------------------------------------- /src/Serilog.Filters.Expressions/Serilog.Filters.Expressions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Expression-based event filtering for Serilog. 5 | 2.1.1 6 | Serilog Contributors 7 | net4.5;netstandard1.5;netstandard2.0 8 | true 9 | true 10 | Serilog.Filters.Expressions 11 | Serilog 12 | ../../assets/Serilog.snk 13 | true 14 | true 15 | Serilog.Filters.Expressions 16 | serilog 17 | https://serilog.net/images/serilog-extension-nuget.png 18 | https://github.com/serilog/serilog-filters-expressions 19 | https://www.apache.org/licenses/LICENSE-2.0 20 | https://github.com/serilog/serilog-filters-expressions 21 | git 22 | True 23 | 1.6.1 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.PerformanceTests/ComparisonBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Running; 3 | using Serilog.Events; 4 | using Serilog.Filters.Expressions.PerformanceTests.Support; 5 | using System; 6 | using Xunit; 7 | 8 | namespace Serilog.Filters.Expressions.PerformanceTests 9 | { 10 | /// 11 | /// Tests the performance of various filtering mechanisms. 12 | /// 13 | public class ComparisonBenchmark 14 | { 15 | Func _trivialFilter, _handwrittenFilter, _expressionFilter; 16 | readonly LogEvent _event = Some.InformationEvent("{A}", 3); 17 | 18 | public ComparisonBenchmark() 19 | { 20 | // Just the delegate invocation overhead 21 | _trivialFilter = evt => true; 22 | 23 | // `A == 3`, the old way 24 | _handwrittenFilter = evt => 25 | { 26 | LogEventPropertyValue a; 27 | if (evt.Properties.TryGetValue("A", out a) && 28 | a is ScalarValue && 29 | ((ScalarValue)a).Value is int) 30 | { 31 | return (int)((ScalarValue)a).Value == 3; 32 | } 33 | 34 | return false; 35 | }; 36 | 37 | // The code we're interested in; the `true.Equals()` overhead is normally added when 38 | // this is used with Serilog 39 | var compiled = FilterLanguage.CreateFilter("A = 3"); 40 | _expressionFilter = evt => true.Equals(compiled(evt)); 41 | 42 | Assert.True(_trivialFilter(_event) && _handwrittenFilter(_event) && _expressionFilter(_event)); 43 | } 44 | 45 | [Benchmark] 46 | public void TrivialFilter() 47 | { 48 | _trivialFilter(_event); 49 | } 50 | 51 | [Benchmark(Baseline = true)] 52 | public void HandwrittenFilter() 53 | { 54 | _handwrittenFilter(_event); 55 | } 56 | 57 | [Benchmark] 58 | public void ExpressionFilter() 59 | { 60 | _expressionFilter(_event); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.PerformanceTests/Harness.cs: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2013-2016 Serilog Contributors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | using BenchmarkDotNet.Running; 17 | using Serilog.Filters.Expressions.PerformanceTests; 18 | using Xunit; 19 | 20 | namespace Serilog.PerformanceTests 21 | { 22 | public class Harness 23 | { 24 | [Fact] 25 | public void ComparisonBenchmark() 26 | { 27 | BenchmarkRunner.Run(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.PerformanceTests/Serilog.Filters.Expressions.PerformanceTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0;net4.6.2 5 | Serilog.Filters.Expressions.PerformanceTests 6 | ../../assets/Serilog.snk 7 | true 8 | true 9 | Serilog.Filters.Expressions.PerformanceTests 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.PerformanceTests/Support/NullSink.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | 4 | namespace Serilog.Filters.Expressions.PerformanceTests.Support 5 | { 6 | public class NullSink : ILogEventSink 7 | { 8 | public void Emit(LogEvent logEvent) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.PerformanceTests/Support/Some.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Events; 3 | using Xunit.Sdk; 4 | using System.Collections.Generic; 5 | 6 | namespace Serilog.Filters.Expressions.PerformanceTests.Support 7 | { 8 | static class Some 9 | { 10 | public static LogEvent InformationEvent(string messageTemplate = "Hello, world!", params object[] propertyValues) 11 | { 12 | return LogEvent(LogEventLevel.Information, messageTemplate, propertyValues); 13 | } 14 | 15 | public static LogEvent WarningEvent(string messageTemplate = "Hello, world!", params object[] propertyValues) 16 | { 17 | return LogEvent(LogEventLevel.Warning, messageTemplate, propertyValues); 18 | } 19 | 20 | public static LogEvent LogEvent(LogEventLevel level, string messageTemplate = "Hello, world!", params object[] propertyValues) 21 | { 22 | var log = new LoggerConfiguration().CreateLogger(); 23 | MessageTemplate template; 24 | IEnumerable properties; 25 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 26 | if (!log.BindMessageTemplate(messageTemplate, propertyValues, out template, out properties)) 27 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 28 | { 29 | throw new XunitException("Template could not be bound."); 30 | } 31 | return new LogEvent(DateTimeOffset.Now, level, null, template, properties); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/ConfigurationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Serilog.Filters.Expressions.Tests.Support; 3 | using Xunit; 4 | 5 | namespace Serilog.Filters.Expressions.Tests 6 | { 7 | public class ConfigurationTests 8 | { 9 | [Fact] 10 | public void ExpressionsControlConditionalSinks() 11 | { 12 | var sink = new CollectingSink(); 13 | var logger = new LoggerConfiguration() 14 | .WriteTo.Conditional("A = 1 or A = 2", wt => wt.Sink(sink)) 15 | .CreateLogger(); 16 | 17 | foreach (var a in Enumerable.Range(0, 5)) 18 | logger.Information("{A}", a); 19 | 20 | Assert.Equal(2, sink.Events.Count); 21 | } 22 | 23 | [Fact] 24 | public void ExpressionsControlConditionalEnrichment() 25 | { 26 | var sink = new CollectingSink(); 27 | var logger = new LoggerConfiguration() 28 | .Enrich.When("A = 1 or A = 2", e => e.WithProperty("B", 1)) 29 | .WriteTo.Sink(sink) 30 | .CreateLogger(); 31 | 32 | foreach (var a in Enumerable.Range(0, 5)) 33 | logger.Information("{A}", a); 34 | 35 | Assert.Equal(2, sink.Events.Count(e => e.Properties.ContainsKey("B"))); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/FilterExpressionCompilerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Serilog.Events; 3 | using Serilog.Filters.Expressions.Tests.Support; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | // ReSharper disable CoVariantArrayConversion 8 | 9 | namespace Serilog.Filters.Expressions.Tests 10 | { 11 | public class FilterExpressionCompilerTests 12 | { 13 | [Fact] 14 | public void FilterExpressionsEvaluateStringEquality() 15 | { 16 | AssertFiltering("Fruit = 'Apple'", 17 | Some.InformationEvent("Snacking on {Fruit}", "Apple"), 18 | Some.InformationEvent(), 19 | Some.InformationEvent("Snacking on {Fruit}", "Acerola")); 20 | } 21 | 22 | [Fact] 23 | public void ComparisonsAreCaseSensitive() 24 | { 25 | AssertFiltering("Fruit = 'Apple'", 26 | Some.InformationEvent("Snacking on {Fruit}", "Apple"), 27 | Some.InformationEvent("Snacking on {Fruit}", "APPLE")); 28 | } 29 | 30 | [Fact] 31 | public void FilterExpressionsEvaluateStringContent() 32 | { 33 | AssertFiltering("Fruit like '%pp%'", 34 | Some.InformationEvent("Snacking on {Fruit}", "Apple"), 35 | Some.InformationEvent("Snacking on {Fruit}", "Acerola")); 36 | } 37 | 38 | [Fact] 39 | public void FilterExpressionsEvaluateStringPrefix() 40 | { 41 | AssertFiltering("Fruit like 'Ap%'", 42 | Some.InformationEvent("Snacking on {Fruit}", "Apple"), 43 | Some.InformationEvent("Snacking on {Fruit}", "Acerola")); 44 | } 45 | 46 | [Fact] 47 | public void FilterExpressionsEvaluateStringSuffix() 48 | { 49 | AssertFiltering("Fruit like '%le'", 50 | Some.InformationEvent("Snacking on {Fruit}", "Apple"), 51 | Some.InformationEvent("Snacking on {Fruit}", "Acerola")); 52 | } 53 | 54 | [Fact] 55 | public void LikeIsCaseInsensitive() 56 | { 57 | AssertFiltering("Fruit like 'apple'", 58 | Some.InformationEvent("Snacking on {Fruit}", "Apple")); 59 | } 60 | 61 | [Fact] 62 | public void FilterExpressionsEvaluateNumericComparisons() 63 | { 64 | AssertFiltering("Volume > 11", 65 | Some.InformationEvent("Adding {Volume} L", 11.5), 66 | Some.InformationEvent("Adding {Volume} L", 11)); 67 | } 68 | 69 | [Fact] 70 | public void FilterExpressionsEvaluateWildcardsOnCollectionItems() 71 | { 72 | AssertFiltering("Items[?] like 'C%'", 73 | Some.InformationEvent("Cart contains {@Items}", new[] { new[] { "Tea", "Coffee" } }), // Test helper doesn't correct this case 74 | Some.InformationEvent("Cart contains {@Items}", new[] { new[] { "Apricots" } })); 75 | } 76 | 77 | [Fact] 78 | public void FilterExpressionsEvaluateBuiltInProperties() 79 | { 80 | AssertFiltering("@Level = 'Information'", 81 | Some.InformationEvent(), 82 | Some.WarningEvent()); 83 | } 84 | 85 | [Fact] 86 | public void FilterExpressionsEvaluateExistentials() 87 | { 88 | AssertFiltering("AppId is not null", 89 | Some.InformationEvent("{AppId}", 10), 90 | Some.InformationEvent("{AppId}", null), 91 | Some.InformationEvent()); 92 | } 93 | 94 | [Fact] 95 | public void FilterExpressionsLogicalOperations() 96 | { 97 | AssertFiltering("A and B", 98 | Some.InformationEvent("{A} {B}", true, true), 99 | Some.InformationEvent("{A} {B}", true, false), 100 | Some.InformationEvent()); 101 | } 102 | 103 | [Fact] 104 | public void FilterExpressionsEvaluateSubproperties() 105 | { 106 | AssertFiltering("Cart.Total > 10", 107 | Some.InformationEvent("Checking out {@Cart}", new { Total = 20 }), 108 | Some.InformationEvent("Checking out {@Cart}", new { Total = 5 })); 109 | } 110 | 111 | 112 | [Fact] 113 | public void SequenceLengthCanBeDetermined() 114 | { 115 | AssertFiltering("length(Items) > 1", 116 | Some.InformationEvent("Checking out {Items}", new object[] { new[] { "pears", "apples" }}), 117 | Some.InformationEvent("Checking out {Items}", new object[] { new[] { "pears" }})); 118 | } 119 | 120 | [Fact] 121 | public void InMatchesLiterals() 122 | { 123 | AssertFiltering("@Level in ['Warning', 'Error']", 124 | Some.LogEvent(LogEventLevel.Error, "Hello"), 125 | Some.InformationEvent("Hello")); 126 | } 127 | 128 | [Fact] 129 | public void InExaminesSequenceValues() 130 | { 131 | AssertFiltering("5 not in Numbers", 132 | Some.InformationEvent("{Numbers}", new object[] {new []{1, 2, 3}}), 133 | Some.InformationEvent("{Numbers}", new object[] { new [] { 1, 5, 3 }}), 134 | Some.InformationEvent()); 135 | } 136 | 137 | static void AssertFiltering(string expression, LogEvent match, params LogEvent[] noMatches) 138 | { 139 | var sink = new CollectingSink(); 140 | 141 | var log = new LoggerConfiguration() 142 | .Filter.ByIncludingOnly(expression) 143 | .WriteTo.Sink(sink) 144 | .CreateLogger(); 145 | 146 | foreach (var noMatch in noMatches) 147 | log.Write(noMatch); 148 | 149 | log.Write(match); 150 | 151 | Assert.Single(sink.Events); 152 | Assert.Same(match, sink.Events.Single()); 153 | } 154 | 155 | [Fact] 156 | public void StructuresAreExposedAsDictionaries() 157 | { 158 | var evt = Some.InformationEvent("{@Person}", new { Name = "nblumhardt" }); 159 | var expr = FilterLanguage.CreateFilter("Person"); 160 | var val = expr(evt); 161 | var dict = Assert.IsType>(val); 162 | Assert.Equal("nblumhardt", dict["Name"]); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/FilterExpressionParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Filters.Expressions.Parsing; 3 | using Xunit; 4 | 5 | namespace Serilog.Filters.Expressions.Tests 6 | { 7 | public class FilterExpressionParserTests 8 | { 9 | [Theory] 10 | [InlineData("contains(@Message,'some text')", "contains(@Message,@\"some text\")")] 11 | [InlineData("AProperty", null)] 12 | [InlineData("AProperty = 'some text'", "Equal(AProperty,@\"some text\")")] 13 | [InlineData("AProperty = 42", "Equal(AProperty,42)")] 14 | [InlineData("@Properties['0'] = 42", "Equal(@Properties[@\"0\"],42)")] 15 | [InlineData("AProperty = null", "Equal(AProperty,null)")] 16 | [InlineData("AProperty <> 42", "NotEqual(AProperty,42)")] 17 | [InlineData("has(AProperty)", null)] 18 | [InlineData("not A", "Not(A)")] 19 | [InlineData("Not(1 = 2)", "Not(Equal(1,2))")] 20 | [InlineData("AProperty = 3 and Another < 12", "And(Equal(AProperty,3),LessThan(Another,12))")] 21 | [InlineData("@Timestamp", null)] 22 | [InlineData("1 + (2 + 3) * 4", "Add(1,Multiply(Add(2,3),4))")] 23 | [InlineData("AProperty.Another = 3", "Equal(AProperty.Another,3)")] 24 | [InlineData("AProperty /2/3 = 7", "Equal(Divide(Divide(AProperty,2),3),7)")] 25 | [InlineData(@"A = /a.*\/b/", @"Equal(A,/a.*\/b/)")] 26 | [InlineData("contains(AProperty, /[^0-9]/)", "contains(AProperty,/[^0-9]/)")] 27 | [InlineData("AProperty[0] = 1", "Equal(AProperty[0],1)")] 28 | [InlineData("AProperty[0] = note", "Equal(AProperty[0],note)")] // Faking the 'not' 29 | [InlineData("equal(AProperty[0].Description,1)", null)] 30 | [InlineData("equal(AProperty[?].Description,1)", null)] 31 | [InlineData("equal(AProperty[*].Description,1)", null)] 32 | [InlineData("equal(AProperty[ * ].Description,1)", "equal(AProperty[*].Description,1)")] 33 | [InlineData("AProperty like '%foo'", "_Internal_Like(AProperty,@\"%foo\")")] 34 | [InlineData("AProperty not like '%foo'", "_Internal_NotLike(AProperty,@\"%foo\")")] 35 | [InlineData("A is null", "_Internal_Is(A,null)")] 36 | [InlineData("A IS NOT NULL", "_Internal_Is(A,Not(null))")] 37 | [InlineData("A is not null or B", "Or(_Internal_Is(A,Not(null)),B)")] 38 | [InlineData("@EventType = 0xC0ffee", "Equal(@EventType,12648430)")] 39 | [InlineData("@Level in ['Error', 'Warning']", "_Internal_In(@Level,[@\"Error\",@\"Warning\"])")] 40 | [InlineData("5 not in [1, 2]", "_Internal_NotIn(5,[1,2])")] 41 | [InlineData("1+1", "Add(1,1)")] 42 | public void ValidSyntaxIsAccepted(string input, string expected = null) 43 | { 44 | var roundTrip = FilterExpressionParser.Parse(input).ToString(); 45 | Assert.Equal(expected ?? input, roundTrip); 46 | } 47 | 48 | [Theory] 49 | [InlineData(" ")] 50 | [InlineData("\"Hello!\"")] 51 | [InlineData("@\"Hello!\"")] 52 | [InlineData("$FE6789E6")] 53 | [InlineData("AProperty == 'some text'")] 54 | [InlineData("AProperty != 42")] 55 | [InlineData("!(1 == 2)")] 56 | [InlineData("AProperty = 3 && Another < 12")] 57 | [InlineData("A = 99999999999999999999999999999999999999999999")] 58 | [InlineData("A = 0x99999999999999999999999999999999999999999999")] 59 | public void InvalidSyntaxIsRejected(string input) 60 | { 61 | Assert.Throws(() => FilterExpressionParser.Parse(input)); 62 | } 63 | 64 | [Theory] 65 | [InlineData("A = 'b", "Syntax error: unexpected end of input, expected `'`.")] 66 | [InlineData("A or B) and C", "Syntax error (line 1, column 7): unexpected `)`.")] 67 | [InlineData("A lik3 C", "Syntax error (line 1, column 3): unexpected identifier `lik3`.")] 68 | [InlineData("A > 1234f", "Syntax error (line 1, column 9): unexpected `f`, expected digit.")] 69 | public void PreciseErrorsAreReported(string input, string expectedMessage) 70 | { 71 | var ex = Assert.Throws(() => FilterExpressionParser.Parse(input)); 72 | Assert.Equal(expectedMessage, ex.Message); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/LoggingFilterSwitchTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Filters.Expressions.Tests.Support; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace Serilog.Filters.Expressions.Tests 6 | { 7 | public class LoggingFilterSwitchTests 8 | { 9 | [Fact] 10 | public void WhenTheFilterExpressionIsModifiedTheFilterChanges() 11 | { 12 | var @switch = new LoggingFilterSwitch(); 13 | var sink = new CollectingSink(); 14 | 15 | var log = new LoggerConfiguration() 16 | .Filter.ControlledBy(@switch) 17 | .WriteTo.Sink(sink) 18 | .CreateLogger(); 19 | 20 | var v11 = Some.InformationEvent("Adding {Volume} L", 11); 21 | 22 | log.Write(v11); 23 | Assert.Same(v11, sink.SingleEvent); 24 | sink.Events.Clear(); 25 | 26 | @switch.Expression = "Volume > 12"; 27 | 28 | log.Write(v11); 29 | Assert.Empty(sink.Events); 30 | 31 | @switch.Expression = "Volume > 10"; 32 | 33 | log.Write(v11); 34 | Assert.Same(v11, sink.SingleEvent); 35 | sink.Events.Clear(); 36 | 37 | @switch.Expression = null; 38 | 39 | log.Write(v11); 40 | Assert.Same(v11, sink.SingleEvent); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/PackagingTests.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Xunit; 3 | 4 | namespace Serilog.Filters.Expressions.Tests 5 | { 6 | public class PackagingTests 7 | { 8 | [Fact] 9 | public void AssemblyVersionIsSet() 10 | { 11 | var version = typeof(LoggerFilterConfigurationExtensions).GetTypeInfo().Assembly.GetName().Version; 12 | Assert.Equal("2", version.ToString(1)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "test": { 4 | "commandName": "test" 5 | }, 6 | "test-dnxcore50": { 7 | "commandName": "test", 8 | "sdkVersion": "dnx-coreclr-win-x86.1.0.0-rc1-final" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/Serilog.Filters.Expressions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0;net4.6.2 4 | true 5 | Serilog.Filters.Expressions.Tests 6 | ../../assets/Serilog.snk 7 | true 8 | true 9 | Serilog.Filters.Expressions.Tests 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/Support/CollectingSink.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Serilog.Filters.Expressions.Tests.Support 7 | { 8 | class CollectingSink : ILogEventSink 9 | { 10 | readonly List _events = new List(); 11 | 12 | public List Events { get { return _events; } } 13 | 14 | public LogEvent SingleEvent { get { return _events.Single(); } } 15 | 16 | public void Emit(LogEvent logEvent) 17 | { 18 | _events.Add(logEvent); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Serilog.Filters.Expressions.Tests/Support/Some.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Serilog.Events; 4 | using Xunit.Sdk; 5 | 6 | namespace Serilog.Filters.Expressions.Tests.Support 7 | { 8 | static class Some 9 | { 10 | public static LogEvent InformationEvent(string messageTemplate = "Hello, world!", params object[] propertyValues) 11 | { 12 | return LogEvent(LogEventLevel.Information, messageTemplate, propertyValues); 13 | } 14 | 15 | public static LogEvent WarningEvent(string messageTemplate = "Hello, world!", params object[] propertyValues) 16 | { 17 | return LogEvent(LogEventLevel.Warning, messageTemplate, propertyValues); 18 | } 19 | 20 | public static LogEvent LogEvent(LogEventLevel level, string messageTemplate = "Hello, world!", params object[] propertyValues) 21 | { 22 | var log = new LoggerConfiguration().CreateLogger(); 23 | MessageTemplate template; 24 | IEnumerable properties; 25 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 26 | if (!log.BindMessageTemplate(messageTemplate, propertyValues, out template, out properties)) 27 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 28 | { 29 | throw new XunitException("Template could not be bound."); 30 | } 31 | return new LogEvent(DateTimeOffset.Now, level, null, template, properties); 32 | } 33 | } 34 | } 35 | --------------------------------------------------------------------------------