├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── GitVersionConfig.yaml ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── build.bat ├── build.cake ├── build.ps1 └── src ├── ConsoleAppExampleForBlogPost ├── ConsoleAppExampleForBlogPost.csproj ├── FooClient.cs ├── Program.cs └── StubErroringDelegatingHandler.cs ├── Polly.Contrib.LoggingPolicy.Specs ├── AsyncLoggingPolicySpecs.cs ├── AsyncLoggingPolicyTResultSpecs.cs ├── LoggingPolicySpecs.cs ├── LoggingPolicyTResultSpecs.cs ├── Polly.Contrib.LoggingPolicy.Specs.csproj └── StubLogger.cs ├── Polly.Contrib.LoggingPolicy.nuspec ├── Polly.Contrib.LoggingPolicy.sln ├── Polly.Contrib.LoggingPolicy.snk └── Polly.Contrib.LoggingPolicy ├── AsyncLoggingEngine.cs ├── AsyncLoggingPolicy.cs ├── AsyncLoggingPolicySyntax.cs ├── AsyncLoggingPolicyTResult.cs ├── AsyncLoggingPolicyTResultSyntax.cs ├── ContextExtensions.cs ├── ILoggingPolicy.cs ├── LoggingEngine.cs ├── LoggingPolicy.cs ├── LoggingPolicySyntax.cs ├── LoggingPolicyTResult.cs ├── LoggingPolicyTResultSyntax.cs └── Polly.Contrib.LoggingPolicy.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | .vs/ 16 | .vscode/ 17 | 18 | # Build results 19 | [Dd]ebug/ 20 | [Rr]elease/ 21 | x64/ 22 | *_i.c 23 | *_p.c 24 | *.ilk 25 | *.meta 26 | *.obj 27 | *.pch 28 | *.pdb 29 | *.pgc 30 | *.pgd 31 | *.rsp 32 | *.sbr 33 | *.tlb 34 | *.tli 35 | *.tlh 36 | *.tmp 37 | *.log 38 | *.vspscc 39 | *.vssscc 40 | .builds 41 | 42 | # Visual C++ cache files 43 | ipch/ 44 | *.aps 45 | *.ncb 46 | *.opensdf 47 | *.sdf 48 | 49 | # Visual Studio profiler 50 | *.psess 51 | *.vsp 52 | *.vspx 53 | 54 | # Guidance Automation Toolkit 55 | *.gpState 56 | 57 | # ReSharper is a .NET coding add-in 58 | _ReSharper* 59 | 60 | # NCrunch 61 | *.ncrunch* 62 | .*crunch*.local.xml 63 | 64 | # GhostDoc 65 | *.GhostDoc.xml 66 | 67 | # Installshield output folder 68 | [Ee]xpress 69 | 70 | # DocProject is a documentation generator add-in 71 | DocProject/buildhelp/ 72 | DocProject/Help/*.HxT 73 | DocProject/Help/*.HxC 74 | DocProject/Help/*.hhc 75 | DocProject/Help/*.hhk 76 | DocProject/Help/*.hhp 77 | DocProject/Help/Html2 78 | DocProject/Help/html 79 | 80 | # Click-Once directory 81 | publish 82 | 83 | # Publish Web Output 84 | *.Publish.xml 85 | 86 | # NuGet Packages Directory 87 | packages 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | sql 100 | TestResults 101 | [Tt]est[Rr]esult* 102 | *.Cache 103 | ClientBin 104 | [Ss]tyle[Cc]op.* 105 | ~$* 106 | *.dbmdl 107 | Generated_Code #added for RIA/Silverlight projects 108 | 109 | # Backup & report files from converting an old project file to a newer 110 | # Visual Studio version. Backup files are not needed, because we have git ;-) 111 | _UpgradeReport_Files/ 112 | Backup*/ 113 | UpgradeLog*.XML 114 | 115 | artifacts 116 | build 117 | tools 118 | 119 | *.lock.json 120 | *.nuget.targets 121 | *.nuget.props -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Polly.Contrib.LoggingPolicy changelog 2 | 3 | ## 0.1.0 4 | - First version -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | We ask our contributors to abide by the [Code of Conduct of the .NET Foundation](https://www.dotnetfoundation.org/code-of-conduct). 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Polly.Contrib hosts contributions to the Polly project by the community. 2 | 3 | See the main readme for how to contribute to Polly.Contrib. -------------------------------------------------------------------------------- /GitVersionConfig.yaml: -------------------------------------------------------------------------------- 1 | next-version: 0.1.0 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | New BSD License 2 | = 3 | Copyright (c) 2019, AppvNext and contributors 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of App vNext nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polly.Contrib.LoggingPolicy 2 | 3 | This repo contains a custom [Polly](https://github.com/App-vNext/Polly) policy to log exceptions or handled results, then rethrow. 4 | 5 | For more background on Polly see the [main Polly repo](https://github.com/App-vNext/Polly). 6 | 7 | ## Usage 8 | 9 | #### Asynchronous executions 10 | 11 | Define an `Action> logAction` to log exceptions or results. The `Context` input parameter allows filtering based on Polly's in-built [execution metadata](https://github.com/App-vNext/Polly/wiki/Keys-And-Context-Data) or context you pass in to the execution. 12 | 13 | Action> logAction = (logger, context, outcome) => 14 | { 15 | logger.LogError("The call resulted in outcome: {happened}", outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString()); 16 | } 17 | 18 | Define a `Func loggerProvider` to select an `ILogger` based on `Context`. This can use the extension method `Context.GetLogger()` defined on `Polly.Context`, if execution dispatch uses the `Context.WithLogger(ILogger)` overload, as demonstrated in the ConsoleApp example. 19 | 20 | Configure the policy: 21 | 22 | var loggingPolicy = Policy 23 | .Handle() 24 | .OrResult((int)response.StatusCode >= 500 || response.StatusCode == HttpStatusCode.RequestTimeout) 25 | .AsyncLog(ctx => ctx.GetLogger(), logAction); 26 | 27 | The policy would typically be combined with other policies in a PolicyWrap - see below. 28 | 29 | #### Synchronous executions 30 | 31 | Define a `logAction` and `loggerProvider` as above. 32 | 33 | Configure the policy, for example: 34 | 35 | var loggingPolicy = Policy 36 | .Handle() 37 | .Log(ctx => ctx.GetLogger(), logAction); 38 | 39 | ### Using LoggingPolicy in PolicyWrap 40 | 41 | The `LoggingPolicy` can be used in any position in a [`PolicyWrap`](https://github.com/App-vNext/Polly/wiki/PolicyWrap). 42 | 43 | It will log the faults it is configured to handle, based on faults bubbled outwards from the next-inner level. 44 | 45 | + A LoggingPolicy used **outermost** (configured first) in a PolicyWrap will log any eventual failure, after all policies of the PolicyWrap have exited. 46 | + A LoggingPolicy used **innermost** (configured last) in a PolicyWrap will log any failure from the underlying delegate executed through the PolicyWrap. For instance, if the PolicyWrap includes retries, a logging policy configured inside (after, in HttpClientFactory sequence) the retry policy, will log any exception/fault thrown by each try. 47 | 48 | The sole purpose of the policy is to log. After logging, it bubbles faults further outwards to be handled by any policies further out or bubbled back to the caller. 49 | 50 | ### Blog post example 51 | 52 | The policy is an example for the blog [Custom policies Part III: Authoring a reactive custom policy](http://www.thepollyproject.org/2019/02/13/authoring-a-reactive-polly-policy-custom-policies-part-iii-2/). 53 | 54 | The policy in this repo differs in small ways from the blog post, as this repo offers LoggingPolicy in all four combinations: 55 | 56 | + `LoggingPolicy` (synchronous non-generic) 57 | + `LoggingPolicy` (synchronous generic) 58 | + `AsyncLoggingPolicy` (asynchronous non-generic) 59 | + `AsyncLoggingPolicy` (asynchronous generic) 60 | 61 | ## Interested in developing your own custom policies? 62 | 63 | See our blog series: 64 | 65 | + [Part I: Introducing custom Polly policies and the Polly.Contrib](http://www.thepollyproject.org/2019/02/13/introducing-custom-polly-policies-and-polly-contrib-custom-policies-part-i/) 66 | + [Part II: Authoring a non-reactive custom policy](http://www.thepollyproject.org/2019/02/13/authoring-a-proactive-polly-policy-custom-policies-part-ii/) (a policy which acts on all executions) 67 | + [Part III: Authoring a reactive custom policy](http://www.thepollyproject.org/2019/02/13/authoring-a-reactive-polly-policy-custom-policies-part-iii-2/) (a policy which react to faults) 68 | + [Part IV: Custom policies for all execution types](http://www.thepollyproject.org/2019/02/13/custom-policies-for-all-execution-types-custom-policies-part-iv/): sync and async, generic and non-generic 69 | 70 | And see the templates for developing custom policies: [Polly.Contrib.CustomPolicyTemplates](https://github.com/Polly-Contrib/Polly.Contrib.CustomPolicyTemplates). 71 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: Visual Studio 2017 2 | 3 | # Build script 4 | build_script: 5 | - ps: .\build.ps1 6 | 7 | # Tests 8 | test: off 9 | 10 | artifacts: 11 | - path: artifacts\nuget-package\*.nupkg 12 | 13 | environment: 14 | # Skip dotnet package caching on build servers 15 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PUSHD %~dp0 3 | PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "& './build.ps1'" 4 | 5 | IF %errorlevel% neq 0 PAUSE 6 | 7 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // ARGUMENTS 3 | /////////////////////////////////////////////////////////////////////////////// 4 | 5 | var target = Argument("target", "Default"); 6 | var configuration = Argument("configuration", "Release"); 7 | 8 | ////////////////////////////////////////////////////////////////////// 9 | // EXTERNAL NUGET TOOLS 10 | ////////////////////////////////////////////////////////////////////// 11 | 12 | #Tool "xunit.runner.console" 13 | #Tool "GitVersion.CommandLine" 14 | #Tool "Brutal.Dev.StrongNameSigner" 15 | 16 | ////////////////////////////////////////////////////////////////////// 17 | // EXTERNAL NUGET LIBRARIES 18 | ////////////////////////////////////////////////////////////////////// 19 | 20 | #addin "Cake.FileHelpers" 21 | #addin "System.Text.Json" 22 | #addin nuget:?package=Cake.Yaml 23 | #addin nuget:?package=YamlDotNet&version=5.2.1 24 | 25 | using System.Text.Json; 26 | 27 | /////////////////////////////////////////////////////////////////////////////// 28 | // GLOBAL VARIABLES 29 | /////////////////////////////////////////////////////////////////////////////// 30 | 31 | var projectName = "Polly.Contrib.LoggingPolicy"; 32 | var keyName = projectName + ".snk"; 33 | 34 | var solutions = GetFiles("./**/*.sln"); 35 | var solutionPaths = solutions.Select(solution => solution.GetDirectory()); 36 | 37 | var srcDir = Directory("./src"); 38 | var buildDir = Directory("./build"); 39 | var artifactsDir = Directory("./artifacts"); 40 | var testResultsDir = artifactsDir + Directory("test-results"); 41 | 42 | // NuGet 43 | var nuspecFilename = projectName + ".nuspec"; 44 | var nuspecSrcFile = srcDir + File(nuspecFilename); 45 | var nuspecDestFile = buildDir + File(nuspecFilename); 46 | var nupkgDestDir = artifactsDir + Directory("nuget-package"); 47 | var snkFile = srcDir + File(keyName); 48 | 49 | // Gitversion 50 | var gitVersionPath = ToolsExePath("GitVersion.exe"); 51 | Dictionary gitVersionOutput; 52 | var gitVersionConfigFilePath = "./GitVersionConfig.yaml"; 53 | 54 | // Versioning 55 | string nugetVersion; 56 | string appveyorBuildNumber; 57 | string assemblyVersion; 58 | string assemblySemver; 59 | 60 | // StrongNameSigner 61 | var strongNameSignerPath = ToolsExePath("StrongNameSigner.Console.exe"); 62 | 63 | /////////////////////////////////////////////////////////////////////////////// 64 | // INNER CLASSES 65 | /////////////////////////////////////////////////////////////////////////////// 66 | class GitVersionConfigYaml 67 | { 68 | public string NextVersion { get; set; } 69 | } 70 | 71 | /////////////////////////////////////////////////////////////////////////////// 72 | // SETUP / TEARDOWN 73 | /////////////////////////////////////////////////////////////////////////////// 74 | 75 | Setup(_ => 76 | { 77 | Information("=============================="); 78 | Information("Starting the cake build script"); 79 | Information("Building: " + projectName); 80 | Information("=============================="); 81 | }); 82 | 83 | Teardown(_ => 84 | { 85 | Information("Finished running tasks."); 86 | }); 87 | 88 | ////////////////////////////////////////////////////////////////////// 89 | // PRIVATE TASKS 90 | ////////////////////////////////////////////////////////////////////// 91 | 92 | Task("__Clean") 93 | .Does(() => 94 | { 95 | DirectoryPath[] cleanDirectories = new DirectoryPath[] { 96 | buildDir, 97 | testResultsDir, 98 | nupkgDestDir, 99 | artifactsDir 100 | }; 101 | 102 | CleanDirectories(cleanDirectories); 103 | 104 | foreach(var path in cleanDirectories) { EnsureDirectoryExists(path); } 105 | 106 | foreach(var path in solutionPaths) 107 | { 108 | Information("Cleaning {0}", path); 109 | CleanDirectories(path + "/**/bin/" + configuration); 110 | CleanDirectories(path + "/**/obj/" + configuration); 111 | } 112 | }); 113 | 114 | Task("__RestoreNugetPackages") 115 | .Does(() => 116 | { 117 | foreach(var solution in solutions) 118 | { 119 | Information("Restoring NuGet Packages for {0}", solution); 120 | NuGetRestore(solution); 121 | } 122 | }); 123 | 124 | Task("__UpdateAssemblyVersionInformation") 125 | .Does(() => 126 | { 127 | var gitVersionSettings = new ProcessSettings() 128 | .SetRedirectStandardOutput(true); 129 | 130 | try { 131 | IEnumerable outputLines; 132 | StartProcess(gitVersionPath, gitVersionSettings, out outputLines); 133 | 134 | var output = string.Join("\n", outputLines); 135 | gitVersionOutput = new JsonParser().Parse>(output); 136 | } 137 | catch 138 | { 139 | Information("Error reading git version information. Build may be running outside of a git repo. Falling back to version specified in " + gitVersionConfigFilePath); 140 | 141 | string gitVersionYamlString = System.IO.File.ReadAllText(gitVersionConfigFilePath); 142 | GitVersionConfigYaml deserialized = DeserializeYaml(gitVersionYamlString.Replace("next-version", "NextVersion")); 143 | string gitVersionConfig = deserialized.NextVersion; 144 | 145 | gitVersionOutput = new Dictionary{ 146 | { "NuGetVersion", gitVersionConfig + "-NotFromGitRepo" }, 147 | { "FullSemVer", gitVersionConfig }, 148 | { "AssemblySemVer", gitVersionConfig }, 149 | { "Major", gitVersionConfig.Split('.')[0] }, 150 | }; 151 | 152 | } 153 | 154 | Information(""); 155 | Information("Obtained raw version info for package versioning:"); 156 | Information("NuGetVersion -> {0}", gitVersionOutput["NuGetVersion"]); 157 | Information("FullSemVer -> {0}", gitVersionOutput["FullSemVer"]); 158 | Information("AssemblySemVer -> {0}", gitVersionOutput["AssemblySemVer"]); 159 | 160 | appveyorBuildNumber = gitVersionOutput["FullSemVer"].ToString(); 161 | nugetVersion = gitVersionOutput["NuGetVersion"].ToString(); 162 | assemblyVersion = gitVersionOutput["Major"].ToString() + ".0.0.0"; 163 | assemblySemver = gitVersionOutput["AssemblySemVer"].ToString(); 164 | 165 | Information(""); 166 | Information("Mapping versioning information to:"); 167 | Information("Appveyor build number -> {0}", appveyorBuildNumber); 168 | Information("Nuget package version -> {0}", nugetVersion); 169 | Information("AssemblyVersion -> {0}", assemblyVersion); 170 | Information("AssemblyFileVersion -> {0}", assemblySemver); 171 | Information("AssemblyInformationalVersion -> {0}", assemblySemver); 172 | }); 173 | 174 | Task("__UpdateDotNetStandardAssemblyVersionNumber") 175 | .Does(() => 176 | { 177 | Information("Updating Assembly Version Information"); 178 | 179 | var attributeToValueMap = new Dictionary() { 180 | { "AssemblyVersion", assemblyVersion }, 181 | { "FileVersion", assemblySemver }, 182 | { "InformationalVersion", assemblySemver }, 183 | { "Version", nugetVersion }, 184 | { "PackageVersion", nugetVersion }, 185 | }; 186 | 187 | var csproj = File("./src/" + projectName + "/" + projectName + ".csproj"); 188 | 189 | foreach(var attributeMap in attributeToValueMap) { 190 | var attribute = attributeMap.Key; 191 | var value = attributeMap.Value; 192 | 193 | var replacedFiles = ReplaceRegexInFiles(csproj, $@"\<{attribute}\>[^\<]*\", $@"<{attribute}>{value}"); 194 | if (!replacedFiles.Any()) 195 | { 196 | throw new Exception($"{attribute} version could not be updated in {csproj}."); 197 | } 198 | } 199 | 200 | }); 201 | 202 | Task("__UpdateAppVeyorBuildNumber") 203 | .WithCriteria(() => AppVeyor.IsRunningOnAppVeyor) 204 | .Does(() => 205 | { 206 | AppVeyor.UpdateBuildVersion(appveyorBuildNumber); 207 | }); 208 | 209 | Task("__BuildSolutions") 210 | .Does(() => 211 | { 212 | foreach(var solution in solutions) 213 | { 214 | Information("Building {0}", solution); 215 | 216 | MSBuild(solution, settings => 217 | settings 218 | .SetConfiguration(configuration) 219 | .WithProperty("TreatWarningsAsErrors", "true") 220 | .UseToolVersion(MSBuildToolVersion.VS2017) 221 | .SetVerbosity(Verbosity.Minimal) 222 | .SetNodeReuse(false)); 223 | } 224 | }); 225 | 226 | Task("__RunTests") 227 | .Does(() => 228 | { 229 | foreach(var specsProj in GetFiles("./src/**/*.Specs.csproj")) { 230 | DotNetCoreTest(specsProj.FullPath, new DotNetCoreTestSettings { 231 | Configuration = configuration, 232 | NoBuild = true 233 | }); 234 | } 235 | }); 236 | 237 | Task("__CopyOutputToNugetFolder") 238 | .Does(() => 239 | { 240 | var sourceDir = srcDir + Directory(projectName) + Directory("bin") + Directory(configuration); 241 | 242 | var destDir = buildDir + Directory("lib"); 243 | 244 | Information("Copying {0} -> {1}.", sourceDir, destDir); 245 | CopyDirectory(sourceDir, destDir); 246 | 247 | CopyFile(nuspecSrcFile, nuspecDestFile); 248 | }); 249 | 250 | Task("__StronglySignAssemblies") 251 | .Does(() => 252 | { 253 | //see: https://github.com/brutaldev/StrongNameSigner 254 | var strongNameSignerSettings = new ProcessSettings() 255 | .WithArguments(args => args 256 | .Append("-in") 257 | .AppendQuoted(buildDir) 258 | .Append("-k") 259 | .AppendQuoted(snkFile) 260 | .Append("-l") 261 | .AppendQuoted("Changes")); 262 | 263 | StartProcess(strongNameSignerPath, strongNameSignerSettings); 264 | }); 265 | 266 | Task("__CreateSignedNugetPackage") 267 | .Does(() => 268 | { 269 | var packageName = projectName; 270 | 271 | Information("Building {0}.{1}.nupkg", packageName, nugetVersion); 272 | 273 | var nuGetPackSettings = new NuGetPackSettings { 274 | Id = packageName, 275 | Title = packageName, 276 | Version = nugetVersion, 277 | OutputDirectory = nupkgDestDir 278 | }; 279 | 280 | NuGetPack(nuspecDestFile, nuGetPackSettings); 281 | }); 282 | 283 | ////////////////////////////////////////////////////////////////////// 284 | // BUILD TASKS 285 | ////////////////////////////////////////////////////////////////////// 286 | 287 | Task("Build") 288 | .IsDependentOn("__Clean") 289 | .IsDependentOn("__RestoreNugetPackages") 290 | .IsDependentOn("__UpdateAssemblyVersionInformation") 291 | .IsDependentOn("__UpdateDotNetStandardAssemblyVersionNumber") 292 | .IsDependentOn("__UpdateAppVeyorBuildNumber") 293 | .IsDependentOn("__BuildSolutions") 294 | .IsDependentOn("__RunTests") 295 | .IsDependentOn("__CopyOutputToNugetFolder") 296 | .IsDependentOn("__StronglySignAssemblies") 297 | .IsDependentOn("__CreateSignedNugetPackage"); 298 | 299 | /////////////////////////////////////////////////////////////////////////////// 300 | // PRIMARY TARGETS 301 | /////////////////////////////////////////////////////////////////////////////// 302 | 303 | Task("Default") 304 | .IsDependentOn("Build"); 305 | 306 | /////////////////////////////////////////////////////////////////////////////// 307 | // EXECUTION 308 | /////////////////////////////////////////////////////////////////////////////// 309 | 310 | RunTarget(target); 311 | 312 | ////////////////////////////////////////////////////////////////////// 313 | // HELPER FUNCTIONS 314 | ////////////////////////////////////////////////////////////////////// 315 | 316 | string ToolsExePath(string exeFileName) { 317 | var exePath = System.IO.Directory.GetFiles(@".\Tools", exeFileName, SearchOption.AllDirectories).FirstOrDefault(); 318 | return exePath; 319 | } 320 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | This is a Powershell script to bootstrap a Cake build. 5 | 6 | .DESCRIPTION 7 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 8 | and execute your Cake build script with the parameters you provide. 9 | 10 | .PARAMETER Script 11 | The build script to execute. 12 | .PARAMETER Target 13 | The build script target to run. 14 | .PARAMETER Configuration 15 | The build configuration to use. 16 | .PARAMETER Verbosity 17 | Specifies the amount of information to be displayed. 18 | .PARAMETER Experimental 19 | Tells Cake to use the latest Roslyn release. 20 | .PARAMETER WhatIf 21 | Performs a dry run of the build script. 22 | No tasks will be executed. 23 | .PARAMETER Mono 24 | Tells Cake to use the Mono scripting engine. 25 | 26 | .LINK 27 | http://cakebuild.net 28 | #> 29 | 30 | Param( 31 | [string]$Script = "build.cake", 32 | [string]$Target = "Default", 33 | [string]$Configuration = "Release", 34 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 35 | [string]$Verbosity = "Verbose", 36 | [switch]$Experimental, 37 | [Alias("DryRun","Noop")] 38 | [switch]$WhatIf, 39 | [switch]$Mono, 40 | [switch]$SkipToolPackageRestore, 41 | [switch]$Verbose 42 | ) 43 | 44 | Write-Host "Preparing to run build script..." 45 | 46 | # Should we show verbose messages? 47 | if($Verbose.IsPresent) 48 | { 49 | $VerbosePreference = "continue" 50 | } 51 | 52 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 53 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 54 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 55 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 56 | 57 | # Should we use mono? 58 | $UseMono = ""; 59 | if($Mono.IsPresent) { 60 | Write-Verbose -Message "Using the Mono based scripting engine." 61 | $UseMono = "-mono" 62 | } 63 | 64 | # Should we use the new Roslyn? 65 | $UseExperimental = ""; 66 | if($Experimental.IsPresent -and !($Mono.IsPresent)) { 67 | Write-Verbose -Message "Using experimental version of Roslyn." 68 | $UseExperimental = "-experimental" 69 | } 70 | 71 | # Is this a dry run? 72 | $UseDryRun = ""; 73 | if($WhatIf.IsPresent) { 74 | $UseDryRun = "-dryrun" 75 | } 76 | 77 | # Make sure tools folder exists 78 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 79 | New-Item -Path $TOOLS_DIR -Type directory | out-null 80 | } 81 | 82 | # Try download NuGet.exe if not exists 83 | if (!(Test-Path $NUGET_EXE)) { 84 | Write-Verbose -Message "Downloading NuGet.exe..." 85 | Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $NUGET_EXE 86 | } 87 | 88 | # Make sure NuGet exists where we expect it. 89 | if (!(Test-Path $NUGET_EXE)) { 90 | Throw "Could not find NuGet.exe" 91 | } 92 | 93 | # Save nuget.exe path to environment to be available to child processed 94 | $ENV:NUGET_EXE = $NUGET_EXE 95 | 96 | # Restore tools from NuGet? 97 | if(-Not $SkipToolPackageRestore.IsPresent) 98 | { 99 | # Restore tools from NuGet. 100 | Push-Location 101 | Set-Location $TOOLS_DIR 102 | 103 | Write-Verbose -Message "Restoring tools from NuGet..." 104 | 105 | # Restore packages 106 | if (Test-Path $PACKAGES_CONFIG) 107 | { 108 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion" 109 | Write-Verbose ($NuGetOutput | Out-String) 110 | } 111 | # Install just Cake if missing config 112 | else 113 | { 114 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install Cake -Version 0.25.0 -ExcludeVersion" # Pin Cake version to 0.25.0; see https://github.com/App-vNext/Polly/issues/416 115 | Write-Verbose ($NuGetOutput | Out-String) 116 | } 117 | Pop-Location 118 | if ($LASTEXITCODE -ne 0) 119 | { 120 | exit $LASTEXITCODE 121 | } 122 | } 123 | 124 | # Make sure that Cake has been installed. 125 | if (!(Test-Path $CAKE_EXE)) { 126 | Throw "Could not find Cake.exe" 127 | } 128 | 129 | # Start Cake 130 | Write-Host "Running build script..." 131 | Invoke-Expression "$CAKE_EXE `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" 132 | exit $LASTEXITCODE 133 | -------------------------------------------------------------------------------- /src/ConsoleAppExampleForBlogPost/ConsoleAppExampleForBlogPost.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/ConsoleAppExampleForBlogPost/FooClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Polly; 6 | using Polly.Contrib.LoggingPolicy; 7 | 8 | namespace ConsoleAppExampleForBlogPost 9 | { 10 | public class FooClient 11 | { 12 | private readonly ILogger logger; 13 | private readonly HttpClient client; 14 | 15 | public FooClient(ILogger logger, HttpClient client) 16 | { 17 | this.logger = logger; 18 | this.client = client; 19 | } 20 | 21 | public async Task GetStringAsync(string url, CancellationToken token) 22 | { 23 | var context = new Context { ["url"] = url }.WithLogger(logger); 24 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); 25 | request.SetPolicyExecutionContext(context); 26 | 27 | var response = await client.SendAsync(request, token); 28 | 29 | return await response.Content.ReadAsStringAsync(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ConsoleAppExampleForBlogPost/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Logging; 8 | using Polly; 9 | using Polly.Contrib.LoggingPolicy; 10 | 11 | namespace ConsoleAppExampleForBlogPost 12 | { 13 | class Program 14 | { 15 | static async Task Main(string[] args) 16 | { 17 | IServiceCollection services = new ServiceCollection(); 18 | 19 | services.AddLogging(configure => configure.AddConsole()); 20 | 21 | void LogAction(ILogger logger, Context context, DelegateResult outcome) 22 | { 23 | // This logging can be as sophisticated as you like; this is a simple example to illustrate principles. 24 | string url = context.GetValueOrDefault("url").ToString() ?? "[unknown]"; 25 | string happened = outcome.Exception?.Message ?? outcome.Result.StatusCode.ToString(); 26 | logger?.LogInformation($"{url}: {happened}"); 27 | } 28 | 29 | services.AddHttpClient() 30 | 31 | // Whatever standard resilience logic you want. 32 | .AddTransientHttpErrorPolicy(policy => policy.RetryAsync(5)) 33 | 34 | // Logging policy to log transient faults as they occur. 35 | // The logging policy is inside the retry policy, so will log faults from each try, before they are retried. 36 | .AddTransientHttpErrorPolicy(policy => policy.AsyncLog(ctx => ctx.GetLogger(), LogAction)) 37 | 38 | // For this test example we add a stub DelegatingHandler which will manufacture faults for us. 39 | .AddHttpMessageHandler(() => new StubErroringDelegatingHandler()); 40 | 41 | 42 | // Quickly test our FooClient. 43 | var fooClient = services 44 | .BuildServiceProvider() 45 | .GetRequiredService(); 46 | 47 | string result = await fooClient.GetStringAsync("https://www.google.com", default(CancellationToken)); 48 | 49 | Console.WriteLine($"Got result: {result}"); 50 | 51 | Console.ReadKey(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/ConsoleAppExampleForBlogPost/StubErroringDelegatingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace ConsoleAppExampleForBlogPost 8 | { 9 | public class StubErroringDelegatingHandler : DelegatingHandler 10 | { 11 | private readonly Random random; 12 | 13 | public StubErroringDelegatingHandler() 14 | { 15 | random = new Random(); 16 | } 17 | 18 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 19 | { 20 | switch (random.Next(3)) 21 | { 22 | case 1: 23 | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)); 24 | case 2: 25 | throw new HttpRequestException(); 26 | 27 | default: 28 | return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK){Content = new StringContent("Dummy content from the stub helper class.")}); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/AsyncLoggingPolicySpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | using Microsoft.Extensions.Logging; 5 | using Xunit; 6 | 7 | namespace Polly.Contrib.LoggingPolicy.Specs 8 | { 9 | public class AsyncLoggingPolicySpecs 10 | { 11 | [Fact] 12 | public void PolicyCallsTheLoggerIfPolicyHandlesException() 13 | { 14 | ILogger expectedLogger = new StubLogger(); 15 | Func loggerProvider = _ => expectedLogger; 16 | 17 | ILogger invokedLogger = null; 18 | Exception exceptionInvokedFor = null; 19 | Action logAction = (logger, context, exception) => 20 | { 21 | invokedLogger = logger; 22 | exceptionInvokedFor = exception; 23 | }; 24 | 25 | AsyncLoggingPolicy policy = Policy.Handle().AsyncLog(loggerProvider, logAction); 26 | 27 | var thrownException = new TimeoutException(); 28 | policy.Awaiting(p => p.ExecuteAsync(() => throw thrownException)).ShouldThrow(); 29 | 30 | invokedLogger.Should().Be(expectedLogger); 31 | exceptionInvokedFor.Should().Be(thrownException); 32 | } 33 | 34 | [Fact] 35 | public void PolicyDoesNotCallTheLoggerIfPolicyDoesNotHandleException() 36 | { 37 | ILogger expectedLogger = new StubLogger(); 38 | Func loggerProvider = _ => expectedLogger; 39 | 40 | ILogger invokedLogger = null; 41 | Exception exceptionInvokedFor = null; 42 | Action logAction = (logger, context, exception) => 43 | { 44 | invokedLogger = logger; 45 | exceptionInvokedFor = exception; 46 | }; 47 | 48 | AsyncLoggingPolicy policy = Policy.Handle().AsyncLog(loggerProvider, logAction); 49 | 50 | var thrownException = new InvalidOperationException(); 51 | policy.Awaiting(p => p.ExecuteAsync(() => throw thrownException)).ShouldThrow(); 52 | 53 | invokedLogger.Should().BeNull(); 54 | exceptionInvokedFor.Should().BeNull(); 55 | } 56 | 57 | [Fact] 58 | public void PolicyDoesNotCallTheLoggerIfSuccessfulExecution() 59 | { 60 | ILogger expectedLogger = new StubLogger(); 61 | Func loggerProvider = _ => expectedLogger; 62 | 63 | ILogger invokedLogger = null; 64 | Exception exceptionInvokedFor = null; 65 | Action logAction = (logger, context, exception) => 66 | { 67 | invokedLogger = logger; 68 | exceptionInvokedFor = exception; 69 | }; 70 | 71 | AsyncLoggingPolicy policy = Policy.Handle().AsyncLog(loggerProvider, logAction); 72 | 73 | policy.ExecuteAsync(() => Task.CompletedTask); 74 | 75 | invokedLogger.Should().BeNull(); 76 | exceptionInvokedFor.Should().BeNull(); 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/AsyncLoggingPolicyTResultSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging; 6 | using Xunit; 7 | 8 | namespace Polly.Contrib.LoggingPolicy.Specs 9 | { 10 | public class AsyncLoggingPolicyTResultSpecs 11 | { 12 | [Fact] 13 | public void PolicyCallsTheLoggerIfPolicyHandlesException() 14 | { 15 | ILogger expectedLogger = new StubLogger(); 16 | Func loggerProvider = _ => expectedLogger; 17 | 18 | ILogger invokedLogger = null; 19 | Exception exceptionInvokedFor = null; 20 | HttpStatusCode? resultInvokedFor = null; 21 | Action> logAction = (logger, context, outcome) => 22 | { 23 | invokedLogger = logger; 24 | exceptionInvokedFor = outcome.Exception; 25 | resultInvokedFor = outcome.Result; 26 | }; 27 | 28 | AsyncLoggingPolicy policy = Policy 29 | .Handle() 30 | .OrResult(r => r != HttpStatusCode.OK) 31 | .AsyncLog(loggerProvider, logAction); 32 | 33 | var thrownException = new TaskCanceledException(); 34 | policy.Awaiting(p => p.ExecuteAsync(() => throw thrownException)).ShouldThrow(); 35 | 36 | invokedLogger.Should().Be(expectedLogger); 37 | exceptionInvokedFor.Should().Be(thrownException); 38 | resultInvokedFor.Should().Be(default(HttpStatusCode)); 39 | } 40 | 41 | [Fact] 42 | public async Task PolicyCallsTheLoggerIfPolicyHandlesResult() 43 | { 44 | ILogger expectedLogger = new StubLogger(); 45 | Func loggerProvider = _ => expectedLogger; 46 | 47 | ILogger invokedLogger = null; 48 | Exception exceptionInvokedFor = null; 49 | HttpStatusCode? resultInvokedFor = null; 50 | Action> logAction = (logger, context, outcome) => 51 | { 52 | invokedLogger = logger; 53 | exceptionInvokedFor = outcome.Exception; 54 | resultInvokedFor = outcome.Result; 55 | }; 56 | 57 | AsyncLoggingPolicy policy = Policy 58 | .Handle() 59 | .OrResult(r => r != HttpStatusCode.OK) 60 | .AsyncLog(loggerProvider, logAction); 61 | 62 | var returnedResult = HttpStatusCode.InternalServerError; 63 | await policy.ExecuteAsync(() => Task.FromResult(returnedResult)); 64 | 65 | invokedLogger.Should().Be(expectedLogger); 66 | exceptionInvokedFor.Should().BeNull(); 67 | resultInvokedFor.Should().Be(returnedResult); 68 | } 69 | 70 | [Fact] 71 | public void PolicyDoesNotCallTheLoggerIfPolicyDoesNotHandleException() 72 | { 73 | ILogger expectedLogger = new StubLogger(); 74 | Func loggerProvider = _ => expectedLogger; 75 | 76 | ILogger invokedLogger = null; 77 | Exception exceptionInvokedFor = null; 78 | HttpStatusCode? resultInvokedFor = null; 79 | Action> logAction = (logger, context, outcome) => 80 | { 81 | invokedLogger = logger; 82 | exceptionInvokedFor = outcome.Exception; 83 | resultInvokedFor = outcome.Result; 84 | }; 85 | 86 | AsyncLoggingPolicy policy = Policy 87 | .Handle() 88 | .OrResult(r => r != HttpStatusCode.OK) 89 | .AsyncLog(loggerProvider, logAction); 90 | 91 | var thrownException = new InvalidOperationException(); 92 | policy.Awaiting(p => p.ExecuteAsync(() => throw thrownException)).ShouldThrow(); 93 | 94 | invokedLogger.Should().BeNull(); 95 | exceptionInvokedFor.Should().BeNull(); 96 | resultInvokedFor.Should().BeNull(); 97 | } 98 | 99 | [Fact] 100 | public async Task PolicyDoesNotCallTheLoggerIfSuccessfulExecution() 101 | { 102 | ILogger expectedLogger = new StubLogger(); 103 | Func loggerProvider = _ => expectedLogger; 104 | 105 | ILogger invokedLogger = null; 106 | Exception exceptionInvokedFor = null; 107 | HttpStatusCode? resultInvokedFor = null; 108 | Action> logAction = (logger, context, outcome) => 109 | { 110 | invokedLogger = logger; 111 | exceptionInvokedFor = outcome.Exception; 112 | resultInvokedFor = outcome.Result; 113 | }; 114 | 115 | AsyncLoggingPolicy policy = Policy 116 | .Handle() 117 | .OrResult(r => r != HttpStatusCode.OK) 118 | .AsyncLog(loggerProvider, logAction); 119 | 120 | await policy.ExecuteAsync(() => Task.FromResult(HttpStatusCode.OK)); 121 | 122 | invokedLogger.Should().BeNull(); 123 | exceptionInvokedFor.Should().BeNull(); 124 | resultInvokedFor.Should().BeNull(); 125 | } 126 | 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/LoggingPolicySpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Microsoft.Extensions.Logging; 4 | using Xunit; 5 | 6 | namespace Polly.Contrib.LoggingPolicy.Specs 7 | { 8 | public class LoggingPolicySpecs 9 | { 10 | [Fact] 11 | public void PolicyCallsTheLoggerIfPolicyHandlesException() 12 | { 13 | ILogger expectedLogger = new StubLogger(); 14 | Func loggerProvider = _ => expectedLogger; 15 | 16 | ILogger invokedLogger = null; 17 | Exception exceptionInvokedFor = null; 18 | Action logAction = (logger, context, exception) => 19 | { 20 | invokedLogger = logger; 21 | exceptionInvokedFor = exception; 22 | }; 23 | 24 | LoggingPolicy policy = Policy.Handle().Log(loggerProvider, logAction); 25 | 26 | var thrownException = new TimeoutException(); 27 | policy.Invoking(p => p.Execute(() => throw thrownException)).ShouldThrow(); 28 | 29 | invokedLogger.Should().Be(expectedLogger); 30 | exceptionInvokedFor.Should().Be(thrownException); 31 | } 32 | 33 | [Fact] 34 | public void PolicyDoesNotCallTheLoggerIfPolicyDoesNotHandleException() 35 | { 36 | ILogger expectedLogger = new StubLogger(); 37 | Func loggerProvider = _ => expectedLogger; 38 | 39 | ILogger invokedLogger = null; 40 | Exception exceptionInvokedFor = null; 41 | Action logAction = (logger, context, exception) => 42 | { 43 | invokedLogger = logger; 44 | exceptionInvokedFor = exception; 45 | }; 46 | 47 | LoggingPolicy policy = Policy.Handle().Log(loggerProvider, logAction); 48 | 49 | var thrownException = new InvalidOperationException(); 50 | policy.Invoking(p => p.Execute(() => throw thrownException)).ShouldThrow(); 51 | 52 | invokedLogger.Should().BeNull(); 53 | exceptionInvokedFor.Should().BeNull(); 54 | } 55 | 56 | [Fact] 57 | public void PolicyDoesNotCallTheLoggerIfSuccessfulExecution() 58 | { 59 | ILogger expectedLogger = new StubLogger(); 60 | Func loggerProvider = _ => expectedLogger; 61 | 62 | ILogger invokedLogger = null; 63 | Exception exceptionInvokedFor = null; 64 | Action logAction = (logger, context, exception) => 65 | { 66 | invokedLogger = logger; 67 | exceptionInvokedFor = exception; 68 | }; 69 | 70 | LoggingPolicy policy = Policy.Handle().Log(loggerProvider, logAction); 71 | 72 | policy.Execute(() => { }); 73 | 74 | invokedLogger.Should().BeNull(); 75 | exceptionInvokedFor.Should().BeNull(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/LoggingPolicyTResultSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using FluentAssertions; 5 | using Microsoft.Extensions.Logging; 6 | using Xunit; 7 | 8 | namespace Polly.Contrib.LoggingPolicy.Specs 9 | { 10 | public class LoggingPolicyTResultSpecs 11 | { 12 | [Fact] 13 | public void PolicyCallsTheLoggerIfPolicyHandlesException() 14 | { 15 | ILogger expectedLogger = new StubLogger(); 16 | Func loggerProvider = _ => expectedLogger; 17 | 18 | ILogger invokedLogger = null; 19 | Exception exceptionInvokedFor = null; 20 | HttpStatusCode? resultInvokedFor = null; 21 | Action> logAction = (logger, context, outcome) => 22 | { 23 | invokedLogger = logger; 24 | exceptionInvokedFor = outcome.Exception; 25 | resultInvokedFor = outcome.Result; 26 | }; 27 | 28 | LoggingPolicy policy = Policy 29 | .Handle() 30 | .OrResult(r => r != HttpStatusCode.OK) 31 | .Log(loggerProvider, logAction); 32 | 33 | var thrownException = new TaskCanceledException(); 34 | policy.Invoking(p => p.Execute(() => throw thrownException)).ShouldThrow(); 35 | 36 | invokedLogger.Should().Be(expectedLogger); 37 | exceptionInvokedFor.Should().Be(thrownException); 38 | resultInvokedFor.Should().Be(default(HttpStatusCode)); 39 | } 40 | 41 | [Fact] public void PolicyCallsTheLoggerIfPolicyHandlesResult() 42 | { 43 | ILogger expectedLogger = new StubLogger(); 44 | Func loggerProvider = _ => expectedLogger; 45 | 46 | ILogger invokedLogger = null; 47 | Exception exceptionInvokedFor = null; 48 | HttpStatusCode? resultInvokedFor = null; 49 | Action> logAction = (logger, context, outcome) => 50 | { 51 | invokedLogger = logger; 52 | exceptionInvokedFor = outcome.Exception; 53 | resultInvokedFor = outcome.Result; 54 | }; 55 | 56 | LoggingPolicy policy = Policy 57 | .Handle() 58 | .OrResult(r => r != HttpStatusCode.OK) 59 | .Log(loggerProvider, logAction); 60 | 61 | var returnedResult = HttpStatusCode.InternalServerError; 62 | policy.Execute(() => returnedResult); 63 | 64 | invokedLogger.Should().Be(expectedLogger); 65 | exceptionInvokedFor.Should().BeNull(); 66 | resultInvokedFor.Should().Be(returnedResult); 67 | } 68 | 69 | [Fact] 70 | public void PolicyDoesNotCallTheLoggerIfPolicyDoesNotHandleException() 71 | { 72 | ILogger expectedLogger = new StubLogger(); 73 | Func loggerProvider = _ => expectedLogger; 74 | 75 | ILogger invokedLogger = null; 76 | Exception exceptionInvokedFor = null; 77 | HttpStatusCode? resultInvokedFor = null; 78 | Action> logAction = (logger, context, outcome) => 79 | { 80 | invokedLogger = logger; 81 | exceptionInvokedFor = outcome.Exception; 82 | resultInvokedFor = outcome.Result; 83 | }; 84 | 85 | LoggingPolicy policy = Policy 86 | .Handle() 87 | .OrResult(r => r != HttpStatusCode.OK) 88 | .Log(loggerProvider, logAction); 89 | 90 | var thrownException = new InvalidOperationException(); 91 | policy.Invoking(p => p.Execute(() => throw thrownException)).ShouldThrow(); 92 | 93 | invokedLogger.Should().BeNull(); 94 | exceptionInvokedFor.Should().BeNull(); 95 | resultInvokedFor.Should().BeNull(); 96 | } 97 | 98 | [Fact] 99 | public void PolicyDoesNotCallTheLoggerIfSuccessfulExecution() 100 | { 101 | ILogger expectedLogger = new StubLogger(); 102 | Func loggerProvider = _ => expectedLogger; 103 | 104 | ILogger invokedLogger = null; 105 | Exception exceptionInvokedFor = null; 106 | HttpStatusCode? resultInvokedFor = null; 107 | Action> logAction = (logger, context, outcome) => 108 | { 109 | invokedLogger = logger; 110 | exceptionInvokedFor = outcome.Exception; 111 | resultInvokedFor = outcome.Result; 112 | }; 113 | 114 | LoggingPolicy policy = Policy 115 | .Handle() 116 | .OrResult(r => r != HttpStatusCode.OK) 117 | .Log(loggerProvider, logAction); 118 | 119 | policy.Execute(() => HttpStatusCode.OK); 120 | 121 | invokedLogger.Should().BeNull(); 122 | exceptionInvokedFor.Should().BeNull(); 123 | resultInvokedFor.Should().BeNull(); 124 | } 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/Polly.Contrib.LoggingPolicy.Specs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1;netcoreapp2.0;net462;net472 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.Specs/StubLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Polly.Contrib.LoggingPolicy.Specs 5 | { 6 | public class StubLogger : ILogger 7 | { 8 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public bool IsEnabled(LogLevel logLevel) 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public IDisposable BeginScope(TState state) 19 | { 20 | throw new NotImplementedException(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | App vNext 5 | App vNext 6 | 7 | Polly.Contrib.LoggingPolicy is a demonstration reactive custom Polly policy, to log faults and results the policy is configured to handle. 8 | 9 | en-US 10 | BSD-3-Clause 11 | https://raw.github.com/App-vNext/Polly/master/Polly.png 12 | https://github.com/Polly-Contrib/Polly.Contrib.LoggingPolicy 13 | Copyright © 2019, App vNext and contributors 14 | 15 | 0.1.0 16 | --------------------- 17 | - Initial launch 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Contrib.LoggingPolicy", "Polly.Contrib.LoggingPolicy\Polly.Contrib.LoggingPolicy.csproj", "{B7730E1D-0796-4151-95C3-1C6E7DE897B9}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Contrib.LoggingPolicy.Specs", "Polly.Contrib.LoggingPolicy.Specs\Polly.Contrib.LoggingPolicy.Specs.csproj", "{ED587F9F-BBE6-4715-9FAA-BAAEFE288A73}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppExampleForBlogPost", "ConsoleAppExampleForBlogPost\ConsoleAppExampleForBlogPost.csproj", "{49D1E5B1-87AF-4838-8809-DDBE0628441E}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B7730E1D-0796-4151-95C3-1C6E7DE897B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {B7730E1D-0796-4151-95C3-1C6E7DE897B9}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {B7730E1D-0796-4151-95C3-1C6E7DE897B9}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {B7730E1D-0796-4151-95C3-1C6E7DE897B9}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {ED587F9F-BBE6-4715-9FAA-BAAEFE288A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {ED587F9F-BBE6-4715-9FAA-BAAEFE288A73}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {ED587F9F-BBE6-4715-9FAA-BAAEFE288A73}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {ED587F9F-BBE6-4715-9FAA-BAAEFE288A73}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {49D1E5B1-87AF-4838-8809-DDBE0628441E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {49D1E5B1-87AF-4838-8809-DDBE0628441E}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {49D1E5B1-87AF-4838-8809-DDBE0628441E}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {49D1E5B1-87AF-4838-8809-DDBE0628441E}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {509DA380-9907-48F4-BB77-DD6F8C4EF207} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polly-Contrib/Polly.Contrib.LoggingPolicy/f11b9803d1d235085c8dd6e738d34f66fcb868d8/src/Polly.Contrib.LoggingPolicy.snk -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/AsyncLoggingEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using Polly.Utilities; 6 | 7 | namespace Polly.Contrib.LoggingPolicy 8 | { 9 | internal static class AsyncLoggingEngine 10 | { 11 | internal static async Task ImplementationAsync( 12 | Func> action, 13 | Context context, 14 | CancellationToken cancellationToken, 15 | bool continueOnCapturedContext, 16 | ExceptionPredicates shouldHandleExceptionPredicates, 17 | ResultPredicates shouldHandleResultPredicates, 18 | Func loggerProvider, 19 | Action> logAction) 20 | { 21 | try 22 | { 23 | TResult result = await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext); 24 | 25 | if (!shouldHandleResultPredicates.AnyMatch(result)) 26 | { 27 | return result; // Not an outcome the policy handles - just return it. 28 | } 29 | 30 | ILogger logger = loggerProvider(context); 31 | logAction(logger, context, new DelegateResult(result)); 32 | 33 | // The policy intentionally bubbles the result outwards after logging. 34 | return result; 35 | } 36 | catch (Exception exception) 37 | { 38 | Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(exception); 39 | if (handledException == null) 40 | { 41 | throw; // Not an exception the policy handles - propagate the exception. 42 | } 43 | 44 | ILogger logger = loggerProvider(context); 45 | logAction(logger, context, new DelegateResult(exception)); 46 | 47 | // The policy intentionally bubbles the exception outwards after logging. 48 | handledException.RethrowWithOriginalStackTraceIfDiffersFrom(exception); 49 | throw; 50 | } 51 | 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/AsyncLoggingPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Polly.Contrib.LoggingPolicy /* Use a namespace broadly describing the topic, eg Polly.Contrib.Logging, Polly.Contrib.RateLimiting */ 7 | { 8 | /// 9 | /// A Logging policy that can be applied to asynchronous delegates. 10 | /// 11 | public class AsyncLoggingPolicy : AsyncPolicy, ILoggingPolicy 12 | { 13 | private readonly Func _loggerProvider; 14 | private readonly Action _logAction; 15 | 16 | internal AsyncLoggingPolicy( 17 | PolicyBuilder policyBuilder, 18 | Func loggerProvider, 19 | Action logAction) 20 | : base(policyBuilder) 21 | { 22 | _loggerProvider = loggerProvider ?? throw new NullReferenceException(nameof(loggerProvider)); 23 | _logAction = logAction ?? throw new NullReferenceException(nameof(logAction)); 24 | } 25 | 26 | /// 27 | protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, 28 | bool continueOnCapturedContext) 29 | { 30 | return AsyncLoggingEngine.ImplementationAsync( 31 | action, 32 | context, 33 | cancellationToken, 34 | continueOnCapturedContext, 35 | ExceptionPredicates, 36 | ResultPredicates.None, 37 | _loggerProvider, 38 | (logger, ctx, delegateResult) => _logAction(logger, ctx, delegateResult.Exception) 39 | ); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/AsyncLoggingPolicySyntax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Polly.Contrib.LoggingPolicy 5 | { 6 | /// 7 | /// Contains configuration syntax for the 8 | /// 9 | public static class AsyncLoggingPolicySyntax 10 | { 11 | /// 12 | /// Constructs a new instance of , configured to handle the exceptions specified in the . 13 | /// 14 | /// The policy builder. 15 | /// A func returning a logger to use. 16 | /// A logging action. 17 | /// 18 | public static AsyncLoggingPolicy AsyncLog( 19 | this PolicyBuilder policyBuilder, 20 | Func loggerProvider, 21 | Action logAction 22 | ) 23 | { 24 | return new AsyncLoggingPolicy( 25 | policyBuilder, 26 | loggerProvider, 27 | logAction 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/AsyncLoggingPolicyTResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Polly.Contrib.LoggingPolicy /* Use a namespace broadly describing the topic, eg Polly.Contrib.Logging, Polly.Contrib.RateLimiting */ 7 | { 8 | /// 9 | /// A Logging policy that can be applied to asynchronous delegates returning a value of type . 10 | /// 11 | /// The type of return values this policy will handle. 12 | public class AsyncLoggingPolicy : AsyncPolicy, ILoggingPolicy 13 | { 14 | private readonly Func _loggerProvider; 15 | private readonly Action> _logAction; 16 | 17 | internal AsyncLoggingPolicy( 18 | PolicyBuilder policyBuilder, 19 | Func loggerProvider, 20 | Action> logAction) 21 | : base(policyBuilder) 22 | { 23 | _loggerProvider = loggerProvider ?? throw new NullReferenceException(nameof(loggerProvider)); 24 | _logAction = logAction ?? throw new NullReferenceException(nameof(logAction)); 25 | } 26 | 27 | /// 28 | protected override Task ImplementationAsync(Func> action, Context context, CancellationToken cancellationToken, 29 | bool continueOnCapturedContext) 30 | { 31 | return AsyncLoggingEngine.ImplementationAsync( 32 | action, 33 | context, 34 | cancellationToken, 35 | continueOnCapturedContext, 36 | ExceptionPredicates, 37 | ResultPredicates, 38 | _loggerProvider, 39 | _logAction 40 | ); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/AsyncLoggingPolicyTResultSyntax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Polly.Contrib.LoggingPolicy 5 | { 6 | /// 7 | /// Contains configuration syntax for the 8 | /// 9 | public static class AsyncLoggingPolicyTResultSyntax 10 | { 11 | /// 12 | /// Constructs a new instance of , configured to handle the exceptions and results specified in the . 13 | /// 14 | /// The return type of delegates which may be executed through the policy. 15 | /// The policy builder. 16 | /// A func returning a logger to use. 17 | /// A logging action. 18 | /// 19 | public static AsyncLoggingPolicy AsyncLog(this PolicyBuilder policyBuilder, 20 | Func loggerProvider, 21 | Action> logAction 22 | ) 23 | { 24 | return new AsyncLoggingPolicy( 25 | policyBuilder, 26 | loggerProvider, 27 | logAction 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/ContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Polly.Contrib.LoggingPolicy 4 | { 5 | /// 6 | /// Defines extension methods on for registering and retrieving instances of . 7 | /// 8 | public static class ContextExtensions 9 | { 10 | private static readonly string LoggerKey = $"{nameof(LoggingPolicy)}.Logger"; 11 | 12 | /// 13 | /// Registers a on the Polly execution , so that a policy from 14 | /// can retrieve it using the method. 15 | /// 16 | /// The Polly execution context. 17 | /// The logger to register on the execution context. 18 | /// The Polly execution , for fluent chaining. 19 | public static Context WithLogger(this Context context, ILogger logger) 20 | { 21 | context[LoggerKey] = logger; 22 | return context; 23 | } 24 | 25 | /// 26 | /// Retrieves a previously registered on the Polly execution 27 | /// using the method. 28 | /// 29 | /// The Polly execution context. 30 | /// The ; or null, if none was registered. 31 | public static ILogger GetLogger(this Context context) 32 | { 33 | if (context.TryGetValue(LoggerKey, out object logger)) 34 | { 35 | return logger as ILogger; 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/ILoggingPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace Polly.Contrib.LoggingPolicy /* Use a namespace broadly describing the topic, eg Polly.Contrib.Logging, Polly.Contrib.RateLimiting */ 2 | { 3 | /// 4 | /// Defines properties common to synchronous and asynchronous Logging policies. 5 | /// 6 | public interface ILoggingPolicy : IsPolicy 7 | { 8 | /* Define properties (if any) or methods (if any) you may want to expose on LoggingPolicy. 9 | 10 | - Perhaps the custom policy takes configuration properties which you want to expose. 11 | - Perhaps the custom policy exposes methods for manual control. 12 | 13 | ... but it is equally common to have none. 14 | */ 15 | } 16 | 17 | /// 18 | /// Defines properties common to generic, synchronous and asynchronous Logging policies. 19 | /// 20 | public interface ILoggingPolicy : ILoggingPolicy 21 | { 22 | /* Define properties (if any) or methods (if any) you may want to expose on LoggingPolicy. 23 | 24 | Typically, ILoggingPolicyPolicy : ILoggingPolicyPolicy, so you would only expose here any 25 | extra properties/methods typed in for TResult policies. 26 | */ 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/LoggingEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.Extensions.Logging; 4 | using Polly.Utilities; 5 | 6 | namespace Polly.Contrib.LoggingPolicy 7 | { 8 | internal static class LoggingEngine 9 | { 10 | internal static TResult Implementation( 11 | Func action, 12 | Context context, 13 | CancellationToken cancellationToken, 14 | ExceptionPredicates shouldHandleExceptionPredicates, 15 | ResultPredicates shouldHandleResultPredicates, 16 | Func loggerProvider, 17 | Action> logAction) 18 | { 19 | try 20 | { 21 | TResult result = action(context, cancellationToken); 22 | 23 | if (!shouldHandleResultPredicates.AnyMatch(result)) 24 | { 25 | return result; // Not an outcome the policy handles - just return it. 26 | } 27 | 28 | ILogger logger = loggerProvider(context); 29 | logAction(logger, context, new DelegateResult(result)); 30 | 31 | // The policy intentionally bubbles the result outwards after logging. 32 | return result; 33 | } 34 | catch (Exception exception) 35 | { 36 | Exception handledException = shouldHandleExceptionPredicates.FirstMatchOrDefault(exception); 37 | if (handledException == null) 38 | { 39 | throw; // Not an exception the policy handles - propagate the exception. 40 | } 41 | 42 | ILogger logger = loggerProvider(context); 43 | logAction(logger, context, new DelegateResult(exception)); 44 | 45 | // The policy intentionally bubbles the exception outwards after logging. 46 | handledException.RethrowWithOriginalStackTraceIfDiffersFrom(exception); 47 | throw; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/LoggingPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Polly.Contrib.LoggingPolicy /* Use a namespace broadly describing the topic, eg Polly.Contrib.Logging, Polly.Contrib.RateLimiting */ 6 | { 7 | /// 8 | /// A Logging policy that can be applied to delegates. 9 | /// 10 | public class LoggingPolicy : Policy, ILoggingPolicy 11 | { 12 | private readonly Func _loggerProvider; 13 | private readonly Action _logAction; 14 | 15 | internal LoggingPolicy( 16 | PolicyBuilder policyBuilder, 17 | Func loggerProvider, 18 | Action logAction) 19 | : base(policyBuilder) 20 | { 21 | _loggerProvider = loggerProvider ?? throw new NullReferenceException(nameof(loggerProvider)); 22 | _logAction = logAction ?? throw new NullReferenceException(nameof(logAction)); 23 | } 24 | 25 | /// 26 | protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) 27 | { 28 | return LoggingEngine.Implementation( 29 | action, 30 | context, 31 | cancellationToken, 32 | ExceptionPredicates, 33 | ResultPredicates.None, 34 | _loggerProvider, 35 | (logger, ctx, delegateResult) => _logAction(logger, ctx, delegateResult.Exception) 36 | ); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/LoggingPolicySyntax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Polly.Contrib.LoggingPolicy 5 | { 6 | /// 7 | /// Contains configuration syntax for the 8 | /// 9 | public static class LoggingPolicySyntax 10 | { 11 | /// 12 | /// Constructs a new instance of , configured to handle the exceptions specified in the . 13 | /// 14 | /// The policy builder. 15 | /// A func returning a logger to use. 16 | /// A logging action. 17 | /// 18 | public static LoggingPolicy Log( 19 | this PolicyBuilder policyBuilder, 20 | Func loggerProvider, 21 | Action logAction 22 | ) 23 | { 24 | return new LoggingPolicy( 25 | policyBuilder, 26 | loggerProvider, 27 | logAction 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/LoggingPolicyTResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Polly.Contrib.LoggingPolicy /* Use a namespace broadly describing the topic, eg Polly.Contrib.Logging, Polly.Contrib.RateLimiting */ 6 | { 7 | /// 8 | /// A Logging policy that can be applied to delegates returning a value of type 9 | /// 10 | /// The type of return values this policy will handle. 11 | public class LoggingPolicy : Policy, ILoggingPolicy 12 | { 13 | private readonly Func _loggerProvider; 14 | private readonly Action> _logAction; 15 | 16 | internal LoggingPolicy( 17 | PolicyBuilder policyBuilder, 18 | Func loggerProvider, 19 | Action> logAction) 20 | : base(policyBuilder) 21 | { 22 | _loggerProvider = loggerProvider ?? throw new NullReferenceException(nameof(loggerProvider)); 23 | _logAction = logAction ?? throw new NullReferenceException(nameof(logAction)); 24 | } 25 | 26 | /// 27 | protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) 28 | { 29 | return LoggingEngine.Implementation( 30 | action, 31 | context, 32 | cancellationToken, 33 | ExceptionPredicates, 34 | ResultPredicates, 35 | _loggerProvider, 36 | _logAction 37 | ); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/LoggingPolicyTResultSyntax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Polly.Contrib.LoggingPolicy 5 | { 6 | /// 7 | /// Contains configuration syntax for the 8 | /// 9 | public static class LoggingPolicyTResultSyntax 10 | { 11 | /// 12 | /// Constructs a new instance of , configured to handle the exceptions and results specified in the . 13 | /// 14 | /// The return type of delegates which may be executed through the policy. 15 | /// The policy builder. 16 | /// A func returning a logger to use. 17 | /// A logging action. 18 | /// 19 | public static LoggingPolicy Log(this PolicyBuilder policyBuilder, 20 | Func loggerProvider, 21 | Action> logAction 22 | ) 23 | { 24 | return new LoggingPolicy( 25 | policyBuilder, 26 | loggerProvider, 27 | logAction 28 | ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Polly.Contrib.LoggingPolicy/Polly.Contrib.LoggingPolicy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.1;netstandard2.0 5 | Polly.Contrib.LoggingPolicy 6 | Polly.Contrib.LoggingPolicy 7 | 0.1.0-v010-0001 8 | 0.0.0.0 9 | 0.1.0.0 10 | 0.1.0.0 11 | 0.1.0-v010-0001 12 | App vNext 13 | Copyright (c) 2019, App vNext and contributors 14 | Polly.Contrib.LoggingPolicy is a custom Polly policy to log exceptions or handled results. 15 | en-US 16 | true 17 | true 18 | App vNext 19 | 20 | 21 | full 22 | 23 | 24 | pdbonly 25 | true 26 | 27 | 28 | 1.6.1 29 | 30 | 31 | 32 | <_Parameter1>Polly.Contrib.LoggingPolicy.Specs 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | --------------------------------------------------------------------------------