├── .all-contributorsrc ├── .gitattributes ├── .gitignore ├── BuildAndPushPackage.bat ├── Changelog.md ├── Delete-BIN-OBJ-Folders.bat ├── HowToUse.md ├── Icon.png ├── License.txt ├── PolicyExample.json ├── README.md ├── Updating.md └── src ├── .editorconfig ├── Directory.Build.props ├── Serilog.Sinks.AmazonS3.Tests ├── AmazonS3BasicTests.cs ├── GlobalUsings.cs └── Serilog.Sinks.AmazonS3.Tests.csproj ├── Serilog.Sinks.AmazonS3.sln ├── Serilog.Sinks.AmazonS3.sln.DotSettings └── Serilog.Sinks.AmazonS3 ├── GlobalUsings.cs ├── LoggerConfigurationAmazonS3Extensions.cs ├── Serilog.Sinks.AmazonS3.csproj └── Sinks └── AmazonS3 ├── AmazonS3Options.cs ├── AmazonS3Sink.cs ├── ConfigurationValidator.cs ├── ErrorMessageConstants.cs ├── FileInformation.cs ├── PathRoller.cs ├── RollingInterval.cs ├── RollingIntervalExtensions.cs └── RollingLogFile.cs /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Galouw", 10 | "name": "Galouw", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/6368030?v=4", 12 | "profile": "https://github.com/Galouw", 13 | "contributions": [ 14 | "code", 15 | "doc" 16 | ] 17 | }, 18 | { 19 | "login": "profet23", 20 | "name": "Ben Grabkowitz", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/2411974?v=4", 22 | "profile": "https://github.com/profet23", 23 | "contributions": [ 24 | "code", 25 | "doc" 26 | ] 27 | }, 28 | { 29 | "login": "aherrmann13", 30 | "name": "Adam Herrmann", 31 | "avatar_url": "https://avatars.githubusercontent.com/u/1924089?v=4", 32 | "profile": "https://github.com/aherrmann13", 33 | "contributions": [ 34 | "code", 35 | "doc" 36 | ] 37 | }, 38 | { 39 | "login": "qrczak0", 40 | "name": "qrczak0", 41 | "avatar_url": "https://avatars.githubusercontent.com/u/45206900?v=4", 42 | "profile": "https://github.com/qrczak0", 43 | "contributions": [ 44 | "code", 45 | "doc" 46 | ] 47 | }, 48 | { 49 | "login": "longfin", 50 | "name": "Swen Mun", 51 | "avatar_url": "https://avatars.githubusercontent.com/u/128436?v=4", 52 | "profile": "http://longfin.github.com", 53 | "contributions": [ 54 | "code", 55 | "doc" 56 | ] 57 | }, 58 | { 59 | "login": "codingbadger", 60 | "name": "Barry Mooring", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/1089628?v=4", 62 | "profile": "https://www.linkedin.com/in/barrymooring/", 63 | "contributions": [ 64 | "code", 65 | "doc" 66 | ] 67 | }, 68 | { 69 | "login": "samburville", 70 | "name": "Sam Burville", 71 | "avatar_url": "https://avatars.githubusercontent.com/u/7041731?v=4", 72 | "profile": "https://github.com/samburville", 73 | "contributions": [ 74 | "code", 75 | "doc" 76 | ] 77 | }, 78 | { 79 | "login": "stylesm", 80 | "name": "Matt Styles", 81 | "avatar_url": "https://avatars.githubusercontent.com/u/5602910?v=4", 82 | "profile": "https://github.com/stylesm", 83 | "contributions": [ 84 | "code", 85 | "doc" 86 | ] 87 | }, 88 | { 89 | "login": "ioxFR", 90 | "name": "Valentin LECERF", 91 | "avatar_url": "https://avatars.githubusercontent.com/u/7376668?v=4", 92 | "profile": "http://vlecerf.com", 93 | "contributions": [ 94 | "code", 95 | "doc" 96 | ] 97 | }, 98 | { 99 | "login": "SeppPenner", 100 | "name": "HansM", 101 | "avatar_url": "https://avatars.githubusercontent.com/u/9639361?v=4", 102 | "profile": "https://franzhuber23.blogspot.de/", 103 | "contributions": [ 104 | "code", 105 | "doc", 106 | "example", 107 | "maintenance", 108 | "projectManagement", 109 | "test" 110 | ] 111 | }, 112 | { 113 | "login": "serilog-contrib", 114 | "name": "Serilog Contrib", 115 | "avatar_url": "https://avatars.githubusercontent.com/u/78050538?v=4", 116 | "profile": "https://github.com/serilog-contrib", 117 | "contributions": [ 118 | "code", 119 | "doc", 120 | "example", 121 | "maintenance", 122 | "projectManagement", 123 | "test" 124 | ] 125 | }, 126 | { 127 | "login": "seruminar", 128 | "name": "Yuriy Sountsov", 129 | "avatar_url": "https://avatars.githubusercontent.com/u/35008875?v=4", 130 | "profile": "https://github.com/seruminar", 131 | "contributions": [ 132 | "doc" 133 | ] 134 | }, 135 | { 136 | "login": "kosovrzn", 137 | "name": "Sergey Kosov", 138 | "avatar_url": "https://avatars.githubusercontent.com/u/13337834?v=4", 139 | "profile": "https://github.com/kosovrzn", 140 | "contributions": [ 141 | "code", 142 | "test" 143 | ] 144 | } 145 | ], 146 | "contributorsPerLine": 7, 147 | "projectName": "Serilog.Sinks.AmazonS3", 148 | "projectOwner": "serilog-contrib", 149 | "repoType": "github", 150 | "repoHost": "https://github.com", 151 | "skipCi": true 152 | } 153 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.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 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 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 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /BuildAndPushPackage.bat: -------------------------------------------------------------------------------- 1 | cd .\src 2 | 3 | @ECHO off 4 | cls 5 | 6 | FOR /d /r . %%d in (bin,obj) DO ( 7 | IF EXIST "%%d" ( 8 | ECHO %%d | FIND /I "\node_modules\" > Nul && ( 9 | ECHO.Skipping: %%d 10 | ) || ( 11 | ECHO.Deleting: %%d 12 | rd /s/q "%%d" 13 | ) 14 | ) 15 | ) 16 | 17 | @ECHO on 18 | @ECHO.Building solution... 19 | @dotnet restore 20 | @dotnet build -c Release 21 | @cd .\Serilog.Sinks.AmazonS3\bin\Release 22 | @ECHO.Build successful. 23 | dotnet nuget push *.nupkg -s "nuget.org" --skip-duplicate -k "%NUGET_API_KEY%" 24 | dotnet nuget push *.snupkg -s "nuget.org" --skip-duplicate -k "%NUGET_API_KEY%" 25 | @ECHO.Upload success. Press any key to exit. 26 | PAUSE -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | Change history 2 | -------------- 3 | 4 | * **Version 1.6.0.0 (2025-03-24)** : Fixes https://github.com/serilog-contrib/Serilog.Sinks.Postgresql.Alternative/issues/83, updates NuGet packages, deprecates `failureCallback`. 5 | * **Version 1.5.3.0 (2024-12-26)** : Removed support for Net6.0, added support for Net9.0, updated NuGet packages. 6 | * **Version 1.5.2.0 (2024-05-16)** : Removed support for Net7.0. 7 | * **Version 1.5.1.0 (2024-03-03)**: Updated NuGet packages. 8 | * **Version 1.5.0.0 (2023-11-21)** : Updated NuGet packages, removed support for netstandard, added support for Net8.0. 9 | * **Version 1.4.1.0 (2023-06-06)** : Updated NuGet packages, added option to disable Amazon S3 SigV4 payload signing (Thanks to [jawand](https://github.com/jawand)). 10 | * **Version 1.4.0.0 (2023-04-07)** : Updated NuGet packages, removed support for NetCore3.1, hopefully fixes rolling interval bug from https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/issues/52. 11 | * **Version 1.3.0.0 (2022-11-20)** : Updated NuGet packages, removed support for Net5.0, added support for Net7.0. 12 | * **Version 1.2.9.0 (2022-10-30)** : Updated NuGet packages. 13 | * **Version 1.2.8.0 (2022-08-28)** : Updated NuGet packages. 14 | * **Version 1.2.7.0 (2022-08-03)** : Updated NuGet packages, fixed issue with service url (Thanks to @kosovrzn). 15 | * **Version 1.2.6.0 (2022-06-01)** : Updated NuGet packages. 16 | * **Version 1.2.5.0 (2022-04-04)** : Updated NuGet packages. 17 | * **Version 1.2.4.0 (2022-02-16)** : Updated NuGet packages, added nullable checks, added editorconfig, added file scoped namespaces, added global usings, removed native support for Net Framework (Breaking change). 18 | * **Version 1.2.3.0 (2022-01-12)** : NuGet packages updated. 19 | * **Version 1.2.2.0 (2021-11-09)** : NuGet packages updated, added support for Net6.0. 20 | * **Version 1.2.1.0 (2021-11-04)** : NuGet packages updated. 21 | * **Version 1.2.0.0 (2021-09-12)** : Added example of loading logger config from appsettings (Thanks to @stylesm), added validation for the serviceUrl property (Thanks to @stylesm), updated icon, smaller adjustements due to move to the serilog-contrib organization, updated NuGet packages. 22 | * **Version 1.1.11.0 (2021-09-03)** : Updated license to fit the new owning repository, updated readme and so on to fit new package name. 23 | * **Version 1.1.10.0 (2021-08-29)** : Removed logging of S3 responses (Thanks to @KindOfANiceGuy), updated NuGet packages. 24 | * **Version 1.1.9.0 (2021-08-09)** : Removed support for soon deprecated NetCore 2.1. 25 | * **Version 1.1.8.0 (2021-07-25)** : Updated NuGet packages, enabled source linking for debugging. 26 | * **Version 1.1.7.0 (2021-06-04)** : Updated NuGet packages. 27 | * **Version 1.1.6.0 (2021-05-15)** : Updated NuGet packages. 28 | * **Version 1.1.5.0 (2021-04-29)** : Updated NuGet packages. 29 | * **Version 1.1.4.0 (2021-03-31)** : Updated NuGet packages, fixed a bug where the Service url wasn't injected correctly (Thanks to [aherrmann13](https://github.com/aherrmann13)). 30 | * **Version 1.1.3.0 (2021-03-31)** : Updated NuGet packages, fixed a bug where the Amazon S3 client wasn't injected correctly (Thanks to [longfin](https://github.com/longfin)). 31 | * **Version 1.1.2.0 (2021-02-27)** : Updated NuGet packages, added deletion of local files. 32 | * **Version 1.1.1.0 (2021-02-21)** : Fixed a null reference exception with the formatter property. 33 | * **Version 1.1.0.0 (2021-02-03)** : Big rework of the sink to make it work better. Check the options to configure the new sink. 34 | * **Version 1.0.12.0 (2020-07-16)** : Added default AWS S3 serviceUrl. 35 | * **Version 1.0.11.0 (2020-07-15)** : Updated NuGet packages, added new constructor taking serviceUrl instead of endpoint (Thanks to [Galouw](https://github.com/Galouw)). 36 | * **Version 1.0.10.0 (2020-06-05)** : Updated NuGet packages, adjusted build to Visual Studio, moved changelog to extra file. 37 | * **Version 1.0.9.0 (2020-05-10)** : Updated NuGet packages, added option to add standard and custom formatters. 38 | * **Version 1.0.8.0 (2020-03-26)** : Updated NuGet packages. 39 | * **Version 1.0.7.0 (2020-02-09)** : Updated NuGet packages, updated available versions. 40 | * **Version 1.0.6.0 (2019-12-09)** : Fixed NuGet package dependency bug. 41 | * **Version 1.0.5.0 (2019-12-08)** : Updated NuGet packages, added new option "bucketPath" to the sink. 42 | * **Version 1.0.4.0 (2019-11-12)** : Small updates, added new option "autoUploadEvents" to the sink. 43 | * **Version 1.0.3.0 (2019-11-08)** : Updated NuGet packages, added GitVersionTask. 44 | * **Version 1.0.2.0 (2019-07-19)** : Support of role based authorization to S3 added, failureCallback parameter added. 45 | * **Version 1.0.1.0 (2019-06-23)** : Added icon to the NuGet package. 46 | * **Version 1.0.0.0 (2019-05-31)** : 1.0 release. -------------------------------------------------------------------------------- /Delete-BIN-OBJ-Folders.bat: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | cls 3 | 4 | ECHO Deleting all BIN and OBJ folders... 5 | ECHO. 6 | 7 | FOR /d /r . %%d in (bin,obj) DO ( 8 | IF EXIST "%%d" ( 9 | ECHO %%d | FIND /I "\node_modules\" > Nul && ( 10 | ECHO.Skipping: %%d 11 | ) || ( 12 | ECHO.Deleting: %%d 13 | rd /s/q "%%d" 14 | ) 15 | ) 16 | ) 17 | 18 | ECHO. 19 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit. 20 | pause > nul -------------------------------------------------------------------------------- /HowToUse.md: -------------------------------------------------------------------------------- 1 | ## Basic usage 2 | ```csharp 3 | var logger = new LoggerConfiguration().WriteTo 4 | .AmazonS3( 5 | "log.txt", 6 | "mytestbucket-aws", 7 | Amazon.RegionEndpoint.EUWest2, 8 | "ABCDEFGHIJKLMNOP", 9 | "c3fghsrgwegfn://asdfsdfsdgfsdg", 10 | rollingInterval: RollingInterval.Minute) 11 | .CreateLogger(); 12 | 13 | for (var x = 0; x < 200; x++) 14 | { 15 | var ex = new Exception("Test"); 16 | logger.Error(ex.ToString()); 17 | } 18 | ``` 19 | 20 | ## Usage with role based authentication in AWS 21 | Use this method if you gave access to Amazon S3 from your AWS program execution machine using roles. In this case, authorization is managed by AWS and `awsAccessKeyId` and `awsSecretAccessKey` are not required. 22 | 23 | ```csharp 24 | var logger = new LoggerConfiguration().WriteTo 25 | .AmazonS3( 26 | "log.txt", 27 | "mytestbucket-aws", 28 | Amazon.RegionEndpoint.EUWest2, 29 | rollingInterval: RollingInterval.Minute) 30 | .CreateLogger(); 31 | 32 | for (var x = 0; x < 200; x++) 33 | { 34 | var ex = new Exception("Test"); 35 | logger.Error(ex.ToString()); 36 | } 37 | ``` 38 | 39 | ## Using JSON or custom formatters 40 | ```csharp 41 | var levelSwitch = new LoggingLevelSwitch { MinimumLevel = LogEventLevel.Information }; 42 | 43 | var logger = new LoggerConfiguration() 44 | .WriteTo.AmazonS3( 45 | new CompactJsonFormatter(), 46 | "log.json", 47 | "mytestbucket-aws", 48 | Amazon.RegionEndpoint.EUWest2, 49 | rollingInterval: RollingInterval.Minute, 50 | ) 51 | .CreateLogger(); 52 | 53 | for (var x = 0; x < 200; x++) 54 | { 55 | var ex = new Exception("Test"); 56 | logger.Error(ex.ToString()); 57 | } 58 | ``` 59 | 60 | ## Configuring from appsettings.json files 61 | ```json 62 | "Serilog": { 63 | "MinimumLevel": { 64 | "Default": "Information" 65 | }, 66 | "WriteTo": [ 67 | { 68 | "Name": "AmazonS3", 69 | "Args": { 70 | "path": "log.txt", 71 | "bucketName": "mybucket-aws", 72 | "rollingInterval": "Day", 73 | "serviceUrl": "https://s3.eu-west-2.amazonaws.com", 74 | "disablePayloadSigning": "false" 75 | } 76 | } 77 | ] 78 | } 79 | ``` 80 | 81 | For more information regarding this use case, see [Issue number 10](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/issues/10) and [Serilog formatting JSON](https://github.com/serilog/serilog/wiki/Formatting-Output#formatting-json). 82 | 83 | 84 | ## Exception handling 85 | You can pass a callback to the sink parameters on failure to define which action needs to be done if an exception occured on the sink side. If something is going wrong in the sink code, `failureCallback` will be executed. This is deprecated with version 1.6.0+. Use fallback logging instead. Check https://nblumhardt.com/2024/10/fallback-logging/. 86 | 87 | ```csharp 88 | var logger = new LoggerConfiguration().WriteTo 89 | .AmazonS3( 90 | "log.txt", 91 | "mytestbucket-aws", 92 | Amazon.RegionEndpoint.EUWest2, 93 | rollingInterval: RollingInterval.Minute, 94 | failureCallback: e => Console.WriteLine($"An error occured in my sink: {e.Message}") 95 | ) 96 | .CreateLogger(); 97 | 98 | for (var x = 0; x < 200; x++) 99 | { 100 | var ex = new Exception("Test"); 101 | logger.Error(ex.ToString()); 102 | } 103 | ``` 104 | 105 | The project can be found on [nuget](https://www.nuget.org/packages/Serilog.Sinks.AmazonS3/). 106 | 107 | ## Configuration options 108 | 109 | |Parameter|Meaning|Example|Default value| 110 | |-|-|-|-| 111 | |client|The Amazon S3 client. It will be created in the sink from the given options if the specified one is `null`.|`new AmazonS3Client()`|`null`| 112 | |formatter|The formatter that can be implemented as desired. See [Issue number 10](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/issues/10) and [Serilog formatting JSON](https://github.com/serilog/serilog/wiki/Formatting-Output#formatting-json) for more information.|`new CompactJsonFormatter()`|None, is optional.| 113 | |path|The main log file name used.|`"log.txt"`|None, is mandatory.| 114 | |bucketName|The name of the Amazon S3 bucket to use.
Check: https://docs.aws.amazon.com/general/latest/gr/rande.html.|`"mytestbucket-aws"`|None, is mandatory.| 115 | |endpoint|The Amazon S3 endpoint location.|`RegionEndpoint.EUWest2`|None, is mandatory. (Either `endpoint` or `serviceUrl` needs to be set.)| 116 | |serviceUrl|The Amazon S3 service URL.
Check: https://docs.aws.amazon.com/general/latest/gr/s3.html.|`https://s3.amazonaws.com`|Default is `https://s3.amazonaws.com`. (Either `endpoint` or `serviceUrl` needs to be set.)| 117 | |awsAccessKeyId|The Amazon S3 access key id.|`ABCDEFGHIJKLMNOP`|None, is mandatory. (Not required if you are using role based authentication).| 118 | |awsSecretAccessKey|The Amazon S3 secret access key.|`c3fghsrgwegfn://asdfsdfsdgfsdg`|None, is mandatory. (Not required if you are using role based authentication).| 119 | |restrictedToMinimumLevel|The minimum level for events passed through the sink. Ignored when `levelSwitch` is specified.
Check: https://github.com/serilog/serilog/blob/dev/src/Serilog/Events/LogEventLevel.cs.|`LogEventLevel.Information`|`LogEventLevel.Verbose`| 120 | |outputTemplate|A message template describing the format used to write to the sink.|`"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"`|`"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"`. If `formatter` is specified: Not needed.| 121 | |formatProvider|The `IFormatProvider` to use. Supplies culture-specific formatting information.
Check: https://docs.microsoft.com/en-us/dotnet/api/system.iformatprovider?view=netframework-4.8.|`new CultureInfo("de-DE")`|`null`. If `formatter` is specified: Not needed.| 122 | |levelSwitch|A switch allowing the pass-through minimum level to be changed at runtime.
Check: https://nblumhardt.com/2014/10/dynamically-changing-the-serilog-level/.|`var levelSwitch = new LoggingLevelSwitch(); levelSwitch.MinimumLevel = LogEventLevel.Warning;`|`null`| 123 | |rollingInterval|The interval at which logging will roll over to a new file.
Check: https://github.com/serilog/serilog-sinks-file/blob/dev/src/Serilog.Sinks.File/RollingInterval.cs.|`rollingInterval: RollingInterval.Minute`|`RollingInterval.Day`| 124 | |encoding|Character encoding used to write the text file.
Check: https://docs.microsoft.com/de-de/dotnet/api/system.text.encoding?view=netframework-4.8.|`encoding: Encoding.Unicode`|`null` meaning `Encoding.UTF8`| 125 | |~~failureCallback~~|~~Adds an option to add a failure callback action.~~ (Deprecated, use fallback logging instead.Check https://nblumhardt.com/2024/10/fallback-logging/.)|~~`failureCallback: e => Console.WriteLine($"Sink error: {e.Message}")`~~|~~`null`~~| 126 | |bucketPath|Optionally add a sub-path for the bucket. Files are stored on S3 `mytestbucket-aws/awsSubPath/log.txt` in the example below.|`bucketPath = "awsSubPath"`|`null`| 127 | |batchSizeLimit|The maximum number of events to include in a single batch. This means an upload of events as a file to S3 will contain at most this number of events.
Check: https://github.com/serilog/serilog-sinks-periodicbatching|`batchSizeLimit = 20`|`100`| 128 | |batchingPeriod|The time to wait between checking for unemitted events. If there are any unemitted events, they will then be uploaded to S3 in a batch of maximum size `batchSizeLimit`.
Check: https://github.com/serilog/serilog-sinks-periodicbatching|`batchingPeriod = TimeSpan.FromSeconds(5)`|`TimeSpan.FromSeconds(2)`| 129 | |eagerlyEmitFirstEvent|A value indicating whether the first event should be emitted immediately or not.|`eagerlyEmitFirstEvent = false`|`true`| 130 | |queueSizeLimit|The queue size limit meaning the limit until the last not emitted events are discarded (Standard mechanims to stop queue overflows).|`queueSizeLimit = 2000`|`10000`| 131 | |disablePayloadSigning|Setting `disablePayloadSigning` to `true` disables the Amazon S3 SigV4 payload signing data integrity check on each upload request. This option is provided if you are using other cloud storage providers e.g. Cloudflare R2 and they support AWS S3 APIs but currently lack support for the Streaming SigV4 implementation used by AWSSDK.S3.|`disablePayloadSigning = true`| Default is `false`. Even a null value will also result in a `false` setting.| 132 | 133 | Hint: Only `outputTemplate` and `formatProvider` together or the `formatter` can be used. 134 | 135 | ## Bigger example 136 | 137 | ```csharp 138 | var levelSwitch = new LoggingLevelSwitch(); 139 | levelSwitch.MinimumLevel = LogEventLevel.Warning; 140 | 141 | var logger = new LoggerConfiguration().WriteTo 142 | .AmazonS3( 143 | "log.txt", 144 | "mytestbucket-aws", 145 | Amazon.RegionEndpoint.EUWest2, 146 | "ABCDEFGHIJKLMNOP", 147 | "c3fghsrgwegfn://asdfsdfsdgfsdg", 148 | restrictedToMinimumLevel:LogEventLevel.Verbose, 149 | outputTemplate:"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}", 150 | new CultureInfo("de-DE"), 151 | levelSwitch: levelSwitch, 152 | rollingInterval: RollingInterval.Minute, 153 | encoding: Encoding.Unicode, 154 | bucketPath = "awsSubPath" 155 | ) 156 | .CreateLogger(); 157 | 158 | for (var x = 0; x < 200; x++) 159 | { 160 | var ex = new Exception("Test"); 161 | logger.Error(ex.ToString()); 162 | } 163 | ``` 164 | 165 | ## Further links 166 | 167 | * Overview over the Amazon endpoints and locations: https://docs.aws.amazon.com/general/latest/gr/rande.html 168 | * How to prepare your S3 bucket to access it with a software: https://www.c-sharpcorner.com/article/fileupload-to-aws-s3-using-asp-net/ 169 | * Example on how to use the Amazon S3 API for .Net: https://stackoverflow.com/questions/25814972/how-to-upload-a-file-to-amazon-s3-super-easy-using-c-sharp 170 | * AWS authorizations for requests: https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-auth-workflow-object-operation.html 171 | -------------------------------------------------------------------------------- /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/Serilog.Sinks.AmazonS3/2ee9fb21a8fe4727137ca2e4022b719da8296bd4/Icon.png -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) SeppPenner (https://github.com/SeppPenner) and the Serilog contributors (https://github.com/serilog-contrib). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PolicyExample.json: -------------------------------------------------------------------------------- 1 | { 2 | "Id": "Policy1575837688950", 3 | "Version": "2012-10-17", 4 | "Statement": [ 5 | { 6 | "Sid": "Stmt1575837684072", 7 | "Action": [ 8 | "s3:GetObject", 9 | "s3:PutObject" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:s3:::test-bucket-aws-s3-serilog/*", 13 | "Principal": "*" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Serilog.Sinks.AmazonS3 2 | ==================================== 3 | 4 | Serilog.Sinks.AmazonS3 is a library to save logging information from [Serilog](https://github.com/serilog/serilog) to [Amazon S3](https://aws.amazon.com/s3/). 5 | The idea there was to upload log files to [Amazon S3](https://aws.amazon.com/s3/) to later evaluate them with [Amazon EMR](https://aws.amazon.com/emr/) services. 6 | This project makes use of the [Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file)'s code in a major part, 7 | so thanks to all the [contributors](https://github.com/serilog/serilog-sinks-file/graphs/contributors) of this project :thumbsup:. 8 | 9 | [![GitHub issues](https://img.shields.io/github/issues/serilog-contrib/Serilog.Sinks.AmazonS3.svg)](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/issues) 10 | [![GitHub forks](https://img.shields.io/github/forks/serilog-contrib/Serilog.Sinks.AmazonS3.svg)](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/network) 11 | [![GitHub stars](https://img.shields.io/github/stars/serilog-contrib/Serilog.Sinks.AmazonS3.svg)](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/stargazers) 12 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://raw.githubusercontent.com/serilog-contrib/Serilog.Sinks.AmazonS3/master/License.txt) 13 | [![Nuget](https://img.shields.io/badge/Serilog.Sinks.AmazonS3-Nuget-brightgreen.svg)](https://www.nuget.org/packages/Serilog.Sinks.AmazonS3/) 14 | [![NuGet Downloads](https://img.shields.io/nuget/dt/Serilog.Sinks.AmazonS3.svg)](https://www.nuget.org/packages/Serilog.Sinks.AmazonS3/) 15 | [![Known Vulnerabilities](https://snyk.io/test/github/serilog-contrib/Serilog.Sinks.AmazonS3/badge.svg)](https://snyk.io/test/github/serilog-contrib/Serilog.Sinks.AmazonS3) 16 | [![Gitter](https://img.shields.io/matrix/Serilog-Sinks-AmazonS3_community%3Agitter.im?server_fqdn=matrix.org)](https://matrix.to/#/#Serilog-Sinks-AmazonS3_community:gitter.im) 17 | 18 | [![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-) 19 | 20 | 21 | ## Available for 22 | * Net 8.0 23 | * Net 9.0 24 | 25 | ## Net Core and Net Framework latest and LTS versions 26 | * https://dotnet.microsoft.com/download/dotnet 27 | 28 | ## Basic usage 29 | Check out the how to use file [here](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/blob/master/HowToUse.md). 30 | 31 | ## Install 32 | 33 | ```bash 34 | dotnet add package Serilog.Sinks.AmazonS3 35 | ``` 36 | 37 | ## Special notice for read-only file systems (e.g. AWS Lambda) 38 | AWS Lambda exposes the `tmp` folder for writing, so prepending `/tmp/` to the path parameter of the AmazonS3 extension method allowed this sink to work with AWS Lambda. 39 | Otherwise a similar exception is thrown: 40 | ```log 41 | Exception while emitting periodic batch from Serilog.Sinks.PeriodicBatching.PeriodicBatchingSink: System.IO.IOException: Read-only file system 42 | ``` 43 | 44 | Change history 45 | -------------- 46 | 47 | See the [Changelog](https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/blob/master/Changelog.md). 48 | 49 | ## Contributors ✨ 50 | 51 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |

Galouw

💻 📖

Ben Grabkowitz

💻 📖

Adam Herrmann

💻 📖

qrczak0

💻 📖

Swen Mun

💻 📖

Barry Mooring

💻 📖

Sam Burville

💻 📖

Matt Styles

💻 📖

Valentin LECERF

💻 📖

HansM

💻 📖 💡 🚧 📆 ⚠️

Serilog Contrib

💻 📖 💡 🚧 📆 ⚠️

Yuriy Sountsov

📖

Sergey Kosov

💻 ⚠️
75 | 76 | 77 | 78 | 79 | 80 | 81 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 82 | -------------------------------------------------------------------------------- /Updating.md: -------------------------------------------------------------------------------- 1 | ## How to update the package 2 | 3 | 1. Add your changes. 4 | 2. Update the release notes in the *.csproj file. 5 | 3. Update version using a Git tag. 6 | 4. Build the project with Visual Studio. 7 | 5. Upload nuget package. -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | csharp_using_directive_placement=inside_namespace:warning 9 | csharp_prefer_braces=true:warning 10 | dotnet_style_allow_multiple_blank_lines_experimental=false:warning 11 | dotnet_style_allow_statement_immediately_after_block_experimental=false:warning 12 | dotnet_style_qualification_for_field=true:warning 13 | dotnet_style_qualification_for_property=true:warning 14 | dotnet_style_qualification_for_method=true:warning 15 | dotnet_style_qualification_for_event=true:warning 16 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental=false:warning 17 | dotnet_sort_system_directives_first=true:warning 18 | dotnet_diagnostic.IDE0005.severity=warning 19 | 20 | [*.cs] 21 | csharp_style_namespace_declarations = file_scoped:warning -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3.Tests/AmazonS3BasicTests.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // This class is used for some basic test regarding the Amazon S3 sink. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog.Sinks.AmazonS3.Tests; 11 | 12 | /// 13 | /// This class is used for some basic test regarding the Amazon S3 sink. 14 | /// 15 | [TestClass] 16 | public class AmazonS3BasicTests 17 | { 18 | /// 19 | /// The Amazon S3 access key id. 20 | /// 21 | private readonly string awsAccessKeyId = Environment.GetEnvironmentVariable("AwsAccessKeyId") ?? string.Empty; 22 | 23 | /// 24 | /// The Amazon S3 secret access key. 25 | /// 26 | private readonly string awsSecretAccessKey = Environment.GetEnvironmentVariable("AwsSecretAccessKey") ?? string.Empty; 27 | 28 | /// 29 | /// The Amazon S3 bucket name. 30 | /// 31 | private readonly string awsBucketName = Environment.GetEnvironmentVariable("AwsBucketName") ?? string.Empty; 32 | 33 | /// 34 | /// Initializes global settings for the current test class. 35 | /// 36 | [ClassInitialize] 37 | public static void ClassInitialize(TestContext _) 38 | { 39 | SelfLog.Enable(Console.WriteLine); 40 | } 41 | 42 | /// 43 | /// This method is used to test a basic file upload to Amazon S3 with no credentials (IAM). 44 | /// 45 | [TestMethod] 46 | public void BasicFileUploadAuthorizedTest() 47 | { 48 | var logger = new LoggerConfiguration() 49 | .WriteTo.AmazonS3( 50 | "log.txt", 51 | this.awsBucketName, 52 | RegionEndpoint.EUWest2, 53 | LogEventLevel.Verbose, 54 | outputTemplate: null, 55 | formatProvider: null, 56 | levelSwitch: null, 57 | rollingInterval: RollingInterval.Minute, 58 | encoding: null, 59 | bucketPath: null, 60 | batchSizeLimit: null, 61 | batchingPeriod: null, 62 | eagerlyEmitFirstEvent: null, 63 | queueSizeLimit: null, 64 | disablePayloadSigning: null) 65 | .CreateLogger(); 66 | 67 | for (var x = 0; x < 200; x++) 68 | { 69 | var ex = new Exception("Test"); 70 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 71 | logger.Error(ex.ToString()); 72 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 73 | } 74 | 75 | Log.CloseAndFlush(); 76 | } 77 | 78 | /// 79 | /// This method is used to test a basic file upload to Amazon S3. 80 | /// 81 | [TestMethod] 82 | public void BasicFileUploadTest() 83 | { 84 | var logger = new LoggerConfiguration() 85 | .WriteTo.AmazonS3( 86 | "log.txt", 87 | this.awsBucketName, 88 | RegionEndpoint.EUWest2, 89 | this.awsAccessKeyId, 90 | this.awsSecretAccessKey, 91 | LogEventLevel.Verbose, 92 | outputTemplate: null, 93 | formatProvider: null, 94 | levelSwitch: null, 95 | rollingInterval: RollingInterval.Minute, 96 | encoding: null, 97 | bucketPath: null, 98 | batchSizeLimit: null, 99 | batchingPeriod: null, 100 | eagerlyEmitFirstEvent: null, 101 | queueSizeLimit: null, 102 | disablePayloadSigning: null) 103 | .CreateLogger(); 104 | 105 | for (var x = 0; x < 200; x++) 106 | { 107 | var ex = new Exception("Test"); 108 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 109 | logger.Error(ex.ToString()); 110 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 111 | } 112 | 113 | Log.CloseAndFlush(); 114 | } 115 | 116 | /// 117 | /// This method is used to test the JSON upload functionality. 118 | /// 119 | [TestMethod] 120 | public void JsonFileUploadTest() 121 | { 122 | var logger = new LoggerConfiguration() 123 | .WriteTo.AmazonS3( 124 | "log.txt", 125 | this.awsBucketName, 126 | RegionEndpoint.EUWest2, 127 | this.awsAccessKeyId, 128 | this.awsSecretAccessKey, 129 | LogEventLevel.Verbose, 130 | formatter: new CompactJsonFormatter(), 131 | levelSwitch: null, 132 | rollingInterval: RollingInterval.Minute, 133 | encoding: null, 134 | bucketPath: null, 135 | batchSizeLimit: null, 136 | batchingPeriod: null, 137 | eagerlyEmitFirstEvent: null, 138 | queueSizeLimit: null, 139 | disablePayloadSigning: null) 140 | .CreateLogger(); 141 | 142 | for (var x = 0; x < 200; x++) 143 | { 144 | var ex = new Exception("Test"); 145 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 146 | logger.Error(ex.ToString()); 147 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 148 | } 149 | 150 | Log.CloseAndFlush(); 151 | } 152 | 153 | /// 154 | /// This method is used to test the formatting functionality. 155 | /// 156 | [TestMethod] 157 | public void FormattingTest() 158 | { 159 | var logger = new LoggerConfiguration() 160 | .WriteTo.AmazonS3( 161 | "log.txt", 162 | this.awsBucketName, 163 | RegionEndpoint.EUWest2, 164 | this.awsAccessKeyId, 165 | this.awsSecretAccessKey, 166 | LogEventLevel.Verbose, 167 | "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") 168 | .CreateLogger(); 169 | 170 | for (var x = 0; x < 200; x++) 171 | { 172 | var ex = new Exception("Test"); 173 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 174 | logger.Error(ex.ToString()); 175 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 176 | } 177 | 178 | Log.CloseAndFlush(); 179 | } 180 | 181 | /// 182 | /// This method is used to test a basic file upload to Amazon S3 with the settings provided from https://github.com/serilog-contrib/Serilog.Sinks.AmazonS3/issues/52 183 | /// that tests the rolling interval. 184 | /// 185 | [TestMethod] 186 | public void BasicFileUploadRollingIntervalTest() 187 | { 188 | var logger = new LoggerConfiguration() 189 | .WriteTo.AmazonS3( 190 | restrictedToMinimumLevel: LogEventLevel.Debug, 191 | path: "log.txt", 192 | bucketName: this.awsBucketName, 193 | endpoint: RegionEndpoint.EUWest2, 194 | bucketPath: null, 195 | awsAccessKeyId: this.awsAccessKeyId, 196 | awsSecretAccessKey: this.awsSecretAccessKey, 197 | rollingInterval: RollingInterval.Hour, 198 | eagerlyEmitFirstEvent: false, 199 | formatter: new JsonFormatter(), 200 | batchingPeriod: TimeSpan.FromHours(2), 201 | batchSizeLimit: 100) 202 | .CreateLogger(); 203 | 204 | for (var x = 0; x < 200; x++) 205 | { 206 | var ex = new Exception("Test"); 207 | #pragma warning disable Serilog004 // Constant MessageTemplate verifier 208 | logger.Error(ex.ToString()); 209 | #pragma warning restore Serilog004 // Constant MessageTemplate verifier 210 | } 211 | 212 | Log.CloseAndFlush(); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using Amazon; 3 | 4 | global using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | global using Serilog.Debugging; 7 | global using Serilog.Events; 8 | global using Serilog.Formatting.Compact; 9 | global using Serilog.Formatting.Json; 10 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3.Tests/Serilog.Sinks.AmazonS3.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | latest 6 | enable 7 | enable 8 | NU1803 9 | true 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29418.71 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.AmazonS3", "Serilog.Sinks.AmazonS3\Serilog.Sinks.AmazonS3.csproj", "{69627EEF-98B1-4DD5-9B15-269FC99EA24D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.AmazonS3.Tests", "Serilog.Sinks.AmazonS3.Tests\Serilog.Sinks.AmazonS3.Tests.csproj", "{54305F2A-6706-442F-AD66-F7AF6961B8D5}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {69627EEF-98B1-4DD5-9B15-269FC99EA24D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {69627EEF-98B1-4DD5-9B15-269FC99EA24D}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {69627EEF-98B1-4DD5-9B15-269FC99EA24D}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {69627EEF-98B1-4DD5-9B15-269FC99EA24D}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {54305F2A-6706-442F-AD66-F7AF6961B8D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {54305F2A-6706-442F-AD66-F7AF6961B8D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {54305F2A-6706-442F-AD66-F7AF6961B8D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {54305F2A-6706-442F-AD66-F7AF6961B8D5}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {6D3B84F0-0AD6-4B43-89F2-40F164C98081} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert. 2 | global using System.Globalization; 3 | global using System.Text; 4 | global using System.Text.RegularExpressions; 5 | 6 | global using Amazon; 7 | global using Amazon.S3; 8 | global using Amazon.S3.Model; 9 | 10 | global using Serilog.Configuration; 11 | global using Serilog.Core; 12 | global using Serilog.Debugging; 13 | global using Serilog.Events; 14 | global using Serilog.Formatting; 15 | global using Serilog.Formatting.Display; 16 | global using Serilog.Sinks.AmazonS3; 17 | global using Serilog.Sinks.PeriodicBatching; 18 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert. -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/LoggerConfigurationAmazonS3Extensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // This class contains the Amazon S3 logger configuration. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog; 11 | 12 | /// 13 | /// This class contains the Amazon S3 logger configuration. 14 | /// 15 | public static class LoggerConfigurationAmazonS3Extensions 16 | { 17 | /// 18 | /// The default output template. 19 | /// 20 | private const string DefaultOutputTemplate = 21 | "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"; 22 | 23 | /// 24 | /// The default batch size limit. 25 | /// 26 | private const int DefaultBatchSizeLimit = 100; 27 | 28 | /// 29 | /// The default value to eagerly emit the first event. 30 | /// 31 | private const bool DefaultEagerlyEmitFirstEvent = true; 32 | 33 | /// 34 | /// The default queue size limit. 35 | /// 36 | private const int DefaultQueueSizeLimit = 10000; 37 | 38 | /// 39 | /// The default encoding. 40 | /// 41 | private static readonly Encoding DefaultEncoding = Encoding.UTF8; 42 | 43 | /// 44 | /// The default batching period. 45 | /// 46 | private static readonly TimeSpan DefaultBatchingPeriod = TimeSpan.FromSeconds(2); 47 | 48 | /// Write log events to the specified file. 49 | /// 50 | /// Thrown when one or more required arguments are 51 | /// null. 52 | /// 53 | /// The logger sink configuration. 54 | /// The path to the file 55 | /// The Amazon S3 bucket name. 56 | /// The Amazon S3 endpoint. 57 | /// The Amazon S3 access key id. 58 | /// The Amazon S3 access key. 59 | /// 60 | /// (Optional) 61 | /// The minimum level for 62 | /// events passed through the sink. Ignored when 63 | /// is specified. 64 | /// 65 | /// The output template. 66 | /// 67 | /// (Optional) 68 | /// Supplies culture-specific formatting information, or 69 | /// null. 70 | /// 71 | /// 72 | /// (Optional) 73 | /// A switch allowing the pass-through minimum level 74 | /// to be changed at runtime. 75 | /// 76 | /// 77 | /// (Optional) 78 | /// The interval at which logging will roll over to a new 79 | /// file. 80 | /// 81 | /// 82 | /// (Optional) 83 | /// Character encoding used to write the text file. The 84 | /// default is UTF-8 without BOM. 85 | /// 86 | /// The Amazon S3 bucket path. 87 | /// The batch size limit. 88 | /// The batching period. 89 | /// A value indicating whether the first event should be emitted immediately or not. 90 | /// The queue size limit. 91 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 92 | /// The configuration object allowing method chaining. 93 | public static LoggerConfiguration AmazonS3( 94 | this LoggerSinkConfiguration sinkConfiguration, 95 | string path, 96 | string bucketName, 97 | RegionEndpoint endpoint, 98 | string awsAccessKeyId, 99 | string awsSecretAccessKey, 100 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 101 | string? outputTemplate = DefaultOutputTemplate, 102 | IFormatProvider? formatProvider = null, 103 | LoggingLevelSwitch? levelSwitch = null, 104 | RollingInterval rollingInterval = RollingInterval.Day, 105 | Encoding? encoding = null, 106 | string? bucketPath = null, 107 | int? batchSizeLimit = DefaultBatchSizeLimit, 108 | TimeSpan? batchingPeriod = null, 109 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 110 | int? queueSizeLimit = DefaultQueueSizeLimit, 111 | bool? disablePayloadSigning = false) 112 | { 113 | if (sinkConfiguration is null) 114 | { 115 | throw new ArgumentNullException(nameof(sinkConfiguration)); 116 | } 117 | 118 | if (string.IsNullOrWhiteSpace(path)) 119 | { 120 | throw new ArgumentNullException(nameof(path)); 121 | } 122 | 123 | if (string.IsNullOrWhiteSpace(bucketName)) 124 | { 125 | throw new ArgumentNullException(nameof(bucketName)); 126 | } 127 | 128 | if (endpoint is null) 129 | { 130 | throw new ArgumentNullException(nameof(endpoint)); 131 | } 132 | 133 | if (string.IsNullOrWhiteSpace(awsAccessKeyId)) 134 | { 135 | throw new ArgumentNullException(nameof(awsAccessKeyId)); 136 | } 137 | 138 | if (string.IsNullOrWhiteSpace(awsSecretAccessKey)) 139 | { 140 | throw new ArgumentNullException(nameof(awsSecretAccessKey)); 141 | } 142 | 143 | if (string.IsNullOrWhiteSpace(outputTemplate)) 144 | { 145 | outputTemplate = DefaultOutputTemplate; 146 | } 147 | 148 | encoding ??= DefaultEncoding; 149 | batchingPeriod ??= DefaultBatchingPeriod; 150 | 151 | var options = new AmazonS3Options 152 | { 153 | Path = path, 154 | BucketName = bucketName, 155 | Endpoint = endpoint, 156 | AwsAccessKeyId = awsAccessKeyId, 157 | AwsSecretAccessKey = awsSecretAccessKey, 158 | OutputTemplate = outputTemplate, 159 | FormatProvider = formatProvider, 160 | RollingInterval = rollingInterval, 161 | Encoding = encoding, 162 | BucketPath = bucketPath, 163 | DisablePayloadSigning = disablePayloadSigning 164 | }; 165 | 166 | var amazonS3Sink = new AmazonS3Sink(options); 167 | 168 | var batchingOptions = new PeriodicBatchingSinkOptions 169 | { 170 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 171 | Period = (TimeSpan)batchingPeriod, 172 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 173 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 174 | }; 175 | 176 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 177 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 178 | } 179 | 180 | /// Write log events to the specified file. 181 | /// 182 | /// Thrown when one or more required arguments are 183 | /// null. 184 | /// 185 | /// The logger sink configuration. 186 | /// The path to the file. 187 | /// The Amazon S3 bucket name. 188 | /// The Amazon S3 endpoint. 189 | /// The Amazon S3 access key id. 190 | /// The Amazon S3 access key. 191 | /// 192 | /// (Optional) 193 | /// The minimum level for 194 | /// events passed through the sink. Ignored when 195 | /// is specified. 196 | /// 197 | /// 198 | /// (Optional) 199 | /// A switch allowing the pass-through minimum level 200 | /// to be changed at runtime. 201 | /// 202 | /// The formatter. 203 | /// 204 | /// (Optional) 205 | /// The interval at which logging will roll over to a new 206 | /// file. 207 | /// 208 | /// 209 | /// (Optional) 210 | /// Character encoding used to write the text file. The 211 | /// default is UTF-8 without BOM. 212 | /// 213 | /// The Amazon S3 bucket path. 214 | /// The batch size limit. 215 | /// The batching period. 216 | /// A value indicating whether the first event should be emitted immediately or not. 217 | /// The queue size limit. 218 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 219 | /// The configuration object allowing method chaining. 220 | public static LoggerConfiguration AmazonS3( 221 | this LoggerSinkConfiguration sinkConfiguration, 222 | string path, 223 | string bucketName, 224 | RegionEndpoint endpoint, 225 | string awsAccessKeyId, 226 | string awsSecretAccessKey, 227 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 228 | LoggingLevelSwitch? levelSwitch = null, 229 | ITextFormatter? formatter = null, 230 | RollingInterval rollingInterval = RollingInterval.Day, 231 | Encoding? encoding = null, 232 | string? bucketPath = null, 233 | int? batchSizeLimit = DefaultBatchSizeLimit, 234 | TimeSpan? batchingPeriod = null, 235 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 236 | int? queueSizeLimit = DefaultQueueSizeLimit, 237 | bool? disablePayloadSigning = false) 238 | { 239 | if (sinkConfiguration is null) 240 | { 241 | throw new ArgumentNullException(nameof(sinkConfiguration)); 242 | } 243 | 244 | if (string.IsNullOrWhiteSpace(path)) 245 | { 246 | throw new ArgumentNullException(nameof(path)); 247 | } 248 | 249 | if (string.IsNullOrWhiteSpace(bucketName)) 250 | { 251 | throw new ArgumentNullException(nameof(bucketName)); 252 | } 253 | 254 | if (endpoint is null) 255 | { 256 | throw new ArgumentNullException(nameof(endpoint)); 257 | } 258 | 259 | if (string.IsNullOrWhiteSpace(awsAccessKeyId)) 260 | { 261 | throw new ArgumentNullException(nameof(awsAccessKeyId)); 262 | } 263 | 264 | if (string.IsNullOrWhiteSpace(awsSecretAccessKey)) 265 | { 266 | throw new ArgumentNullException(nameof(awsSecretAccessKey)); 267 | } 268 | 269 | if (formatter is null) 270 | { 271 | throw new ArgumentNullException(nameof(formatter)); 272 | } 273 | 274 | encoding ??= DefaultEncoding; 275 | batchingPeriod ??= DefaultBatchingPeriod; 276 | 277 | var options = new AmazonS3Options 278 | { 279 | Path = path, 280 | BucketName = bucketName, 281 | Endpoint = endpoint, 282 | AwsAccessKeyId = awsAccessKeyId, 283 | AwsSecretAccessKey = awsSecretAccessKey, 284 | Formatter = formatter, 285 | RollingInterval = rollingInterval, 286 | Encoding = encoding, 287 | BucketPath = bucketPath, 288 | DisablePayloadSigning = disablePayloadSigning 289 | }; 290 | 291 | var amazonS3Sink = new AmazonS3Sink(options); 292 | 293 | var batchingOptions = new PeriodicBatchingSinkOptions 294 | { 295 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 296 | Period = (TimeSpan)batchingPeriod, 297 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 298 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 299 | }; 300 | 301 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 302 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 303 | } 304 | 305 | /// Write log events to the specified file. 306 | /// 307 | /// Thrown when one or more required arguments are 308 | /// null. 309 | /// 310 | /// The logger sink configuration. 311 | /// The path to the file. 312 | /// The Amazon S3 bucket name. 313 | /// The Amazon S3 endpoint. 314 | /// 315 | /// (Optional) 316 | /// The minimum level for 317 | /// events passed through the sink. Ignored when 318 | /// is specified. 319 | /// 320 | /// 321 | /// (Optional) 322 | /// A switch allowing the pass-through minimum level 323 | /// to be changed at runtime. 324 | /// 325 | /// 326 | /// (Optional) 327 | /// A message template describing the format used to 328 | /// write to the sink. 329 | /// The default is "{Timestamp:yyyy-MM-dd 330 | /// HH:mm:ss.fff zzz} [{Level:u3}] 331 | /// {Message:lj}{NewLine}{Exception}". 332 | /// 333 | /// 334 | /// (Optional) 335 | /// Supplies culture-specific formatting information, or 336 | /// null. 337 | /// 338 | /// 339 | /// (Optional) 340 | /// The interval at which logging will roll over to a new 341 | /// file. 342 | /// 343 | /// 344 | /// (Optional) 345 | /// Character encoding used to write the text file. The 346 | /// default is UTF-8 without BOM. 347 | /// 348 | /// The Amazon S3 bucket path. 349 | /// The batch size limit. 350 | /// The batching period. 351 | /// A value indicating whether the first event should be emitted immediately or not. 352 | /// The queue size limit. 353 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 354 | /// The configuration object allowing method chaining. 355 | public static LoggerConfiguration AmazonS3( 356 | this LoggerSinkConfiguration sinkConfiguration, 357 | string path, 358 | string bucketName, 359 | RegionEndpoint endpoint, 360 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 361 | LoggingLevelSwitch? levelSwitch = null, 362 | string? outputTemplate = DefaultOutputTemplate, 363 | IFormatProvider? formatProvider = null, 364 | RollingInterval rollingInterval = RollingInterval.Day, 365 | Encoding? encoding = null, 366 | string? bucketPath = null, 367 | int? batchSizeLimit = DefaultBatchSizeLimit, 368 | TimeSpan? batchingPeriod = null, 369 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 370 | int? queueSizeLimit = DefaultQueueSizeLimit, 371 | bool? disablePayloadSigning = false) 372 | { 373 | if (sinkConfiguration is null) 374 | { 375 | throw new ArgumentNullException(nameof(sinkConfiguration)); 376 | } 377 | 378 | if (string.IsNullOrWhiteSpace(path)) 379 | { 380 | throw new ArgumentNullException(nameof(path)); 381 | } 382 | 383 | if (string.IsNullOrWhiteSpace(bucketName)) 384 | { 385 | throw new ArgumentNullException(nameof(bucketName)); 386 | } 387 | 388 | if (endpoint is null) 389 | { 390 | throw new ArgumentNullException(nameof(endpoint)); 391 | } 392 | 393 | if (string.IsNullOrWhiteSpace(outputTemplate)) 394 | { 395 | outputTemplate = DefaultOutputTemplate; 396 | } 397 | 398 | encoding ??= DefaultEncoding; 399 | batchingPeriod ??= DefaultBatchingPeriod; 400 | 401 | var options = new AmazonS3Options 402 | { 403 | Path = path, 404 | BucketName = bucketName, 405 | Endpoint = endpoint, 406 | OutputTemplate = outputTemplate, 407 | FormatProvider = formatProvider, 408 | RollingInterval = rollingInterval, 409 | Encoding = encoding, 410 | BucketPath = bucketPath, 411 | DisablePayloadSigning = disablePayloadSigning 412 | }; 413 | 414 | var amazonS3Sink = new AmazonS3Sink(options); 415 | 416 | var batchingOptions = new PeriodicBatchingSinkOptions 417 | { 418 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 419 | Period = (TimeSpan)batchingPeriod, 420 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 421 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 422 | }; 423 | 424 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 425 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 426 | } 427 | 428 | /// Write log events to the specified file. 429 | /// 430 | /// Thrown when one or more required arguments are 431 | /// null. 432 | /// 433 | /// The logger sink configuration. 434 | /// The path to the file. 435 | /// The Amazon S3 bucket name. 436 | /// The Amazon S3 endpoint. 437 | /// 438 | /// (Optional) 439 | /// The minimum level for 440 | /// events passed through the sink. Ignored when 441 | /// is specified. 442 | /// 443 | /// 444 | /// (Optional) 445 | /// A switch allowing the pass-through minimum level 446 | /// to be changed at runtime. 447 | /// 448 | /// The formatter. 449 | /// 450 | /// (Optional) 451 | /// The interval at which logging will roll over to a new 452 | /// file. 453 | /// 454 | /// 455 | /// (Optional) 456 | /// Character encoding used to write the text file. The 457 | /// default is UTF-8 without BOM. 458 | /// 459 | /// The Amazon S3 bucket path. 460 | /// The batch size limit. 461 | /// The batching period. 462 | /// A value indicating whether the first event should be emitted immediately or not. 463 | /// The queue size limit. 464 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 465 | /// The configuration object allowing method chaining. 466 | public static LoggerConfiguration AmazonS3( 467 | this LoggerSinkConfiguration sinkConfiguration, 468 | string path, 469 | string bucketName, 470 | RegionEndpoint endpoint, 471 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 472 | LoggingLevelSwitch? levelSwitch = null, 473 | ITextFormatter? formatter = null, 474 | RollingInterval rollingInterval = RollingInterval.Day, 475 | Encoding? encoding = null, 476 | string? bucketPath = null, 477 | int? batchSizeLimit = DefaultBatchSizeLimit, 478 | TimeSpan? batchingPeriod = null, 479 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 480 | int? queueSizeLimit = DefaultQueueSizeLimit, 481 | bool? disablePayloadSigning = false) 482 | { 483 | if (sinkConfiguration is null) 484 | { 485 | throw new ArgumentNullException(nameof(sinkConfiguration)); 486 | } 487 | 488 | if (string.IsNullOrWhiteSpace(path)) 489 | { 490 | throw new ArgumentNullException(nameof(path)); 491 | } 492 | 493 | if (string.IsNullOrWhiteSpace(bucketName)) 494 | { 495 | throw new ArgumentNullException(nameof(bucketName)); 496 | } 497 | 498 | if (endpoint is null) 499 | { 500 | throw new ArgumentNullException(nameof(endpoint)); 501 | } 502 | 503 | if (formatter is null) 504 | { 505 | throw new ArgumentNullException(nameof(formatter)); 506 | } 507 | 508 | encoding ??= DefaultEncoding; 509 | batchingPeriod ??= DefaultBatchingPeriod; 510 | 511 | var options = new AmazonS3Options 512 | { 513 | Path = path, 514 | BucketName = bucketName, 515 | Endpoint = endpoint, 516 | Formatter = formatter, 517 | RollingInterval = rollingInterval, 518 | Encoding = encoding, 519 | BucketPath = bucketPath, 520 | DisablePayloadSigning = disablePayloadSigning 521 | }; 522 | 523 | var amazonS3Sink = new AmazonS3Sink(options); 524 | 525 | var batchingOptions = new PeriodicBatchingSinkOptions 526 | { 527 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 528 | Period = (TimeSpan)batchingPeriod, 529 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 530 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 531 | }; 532 | 533 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 534 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 535 | } 536 | 537 | /// Write log events to the specified file. 538 | /// 539 | /// Thrown when one or more required arguments are 540 | /// null. 541 | /// 542 | /// The logger sink configuration. 543 | /// The path to the file 544 | /// The Amazon S3 bucket name. 545 | /// The Amazon S3 service url. 546 | /// The Amazon S3 access key id. 547 | /// The Amazon S3 access key. 548 | /// 549 | /// (Optional) 550 | /// The minimum level for 551 | /// events passed through the sink. Ignored when 552 | /// is specified. 553 | /// 554 | /// The output template. 555 | /// 556 | /// (Optional) 557 | /// Supplies culture-specific formatting information, or 558 | /// null. 559 | /// 560 | /// 561 | /// (Optional) 562 | /// A switch allowing the pass-through minimum level 563 | /// to be changed at runtime. 564 | /// 565 | /// 566 | /// (Optional) 567 | /// The interval at which logging will roll over to a new 568 | /// file. 569 | /// 570 | /// 571 | /// (Optional) 572 | /// Character encoding used to write the text file. The 573 | /// default is UTF-8 without BOM. 574 | /// 575 | /// The Amazon S3 bucket path. 576 | /// The batch size limit. 577 | /// The batching period. 578 | /// A value indicating whether the first event should be emitted immediately or not. 579 | /// The queue size limit. 580 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 581 | /// The configuration object allowing method chaining. 582 | public static LoggerConfiguration AmazonS3( 583 | this LoggerSinkConfiguration sinkConfiguration, 584 | string path, 585 | string bucketName, 586 | string serviceUrl, 587 | string awsAccessKeyId, 588 | string awsSecretAccessKey, 589 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 590 | string? outputTemplate = DefaultOutputTemplate, 591 | IFormatProvider? formatProvider = null, 592 | LoggingLevelSwitch? levelSwitch = null, 593 | RollingInterval rollingInterval = RollingInterval.Day, 594 | Encoding? encoding = null, 595 | string? bucketPath = null, 596 | int? batchSizeLimit = DefaultBatchSizeLimit, 597 | TimeSpan? batchingPeriod = null, 598 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 599 | int? queueSizeLimit = DefaultQueueSizeLimit, 600 | bool? disablePayloadSigning = false) 601 | { 602 | if (sinkConfiguration is null) 603 | { 604 | throw new ArgumentNullException(nameof(sinkConfiguration)); 605 | } 606 | 607 | if (string.IsNullOrWhiteSpace(path)) 608 | { 609 | throw new ArgumentNullException(nameof(path)); 610 | } 611 | 612 | if (string.IsNullOrWhiteSpace(bucketName)) 613 | { 614 | throw new ArgumentNullException(nameof(bucketName)); 615 | } 616 | 617 | if (string.IsNullOrWhiteSpace(serviceUrl)) 618 | { 619 | throw new ArgumentNullException(nameof(serviceUrl)); 620 | } 621 | 622 | if (!ConfigurationValidator.ValidateServiceUrl(serviceUrl)) 623 | { 624 | throw new ArgumentException(ErrorMessageConstants.ServiceUrlInvalidFormat, nameof(serviceUrl)); 625 | } 626 | 627 | if (string.IsNullOrWhiteSpace(awsAccessKeyId)) 628 | { 629 | throw new ArgumentNullException(nameof(awsAccessKeyId)); 630 | } 631 | 632 | if (string.IsNullOrWhiteSpace(awsSecretAccessKey)) 633 | { 634 | throw new ArgumentNullException(nameof(awsSecretAccessKey)); 635 | } 636 | 637 | if (string.IsNullOrWhiteSpace(outputTemplate)) 638 | { 639 | outputTemplate = DefaultOutputTemplate; 640 | } 641 | 642 | encoding ??= DefaultEncoding; 643 | batchingPeriod ??= DefaultBatchingPeriod; 644 | 645 | var options = new AmazonS3Options 646 | { 647 | Path = path, 648 | BucketName = bucketName, 649 | ServiceUrl = serviceUrl, 650 | AwsAccessKeyId = awsAccessKeyId, 651 | AwsSecretAccessKey = awsSecretAccessKey, 652 | OutputTemplate = outputTemplate, 653 | FormatProvider = formatProvider, 654 | RollingInterval = rollingInterval, 655 | Encoding = encoding, 656 | BucketPath = bucketPath, 657 | Endpoint = null, 658 | DisablePayloadSigning = disablePayloadSigning 659 | }; 660 | 661 | var amazonS3Sink = new AmazonS3Sink(options); 662 | 663 | var batchingOptions = new PeriodicBatchingSinkOptions 664 | { 665 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 666 | Period = (TimeSpan)batchingPeriod, 667 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 668 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 669 | }; 670 | 671 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 672 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 673 | } 674 | 675 | /// Write log events to the specified file. 676 | /// 677 | /// Thrown when one or more required arguments are 678 | /// null. 679 | /// 680 | /// The logger sink configuration. 681 | /// The path to the file. 682 | /// The Amazon S3 bucket name. 683 | /// The Amazon S3 service url. 684 | /// The Amazon S3 access key id. 685 | /// The Amazon S3 access key. 686 | /// 687 | /// (Optional) 688 | /// The minimum level for 689 | /// events passed through the sink. Ignored when 690 | /// is specified. 691 | /// 692 | /// 693 | /// (Optional) 694 | /// A switch allowing the pass-through minimum level 695 | /// to be changed at runtime. 696 | /// 697 | /// The formatter. 698 | /// 699 | /// (Optional) 700 | /// The interval at which logging will roll over to a new 701 | /// file. 702 | /// 703 | /// 704 | /// (Optional) 705 | /// Character encoding used to write the text file. The 706 | /// default is UTF-8 without BOM. 707 | /// 708 | /// The Amazon S3 bucket path. 709 | /// The batch size limit. 710 | /// The batching period. 711 | /// A value indicating whether the first event should be emitted immediately or not. 712 | /// The queue size limit. 713 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 714 | /// The configuration object allowing method chaining. 715 | public static LoggerConfiguration AmazonS3( 716 | this LoggerSinkConfiguration sinkConfiguration, 717 | string path, 718 | string bucketName, 719 | string serviceUrl, 720 | string awsAccessKeyId, 721 | string awsSecretAccessKey, 722 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 723 | LoggingLevelSwitch? levelSwitch = null, 724 | ITextFormatter? formatter = null, 725 | RollingInterval rollingInterval = RollingInterval.Day, 726 | Encoding? encoding = null, 727 | string? bucketPath = null, 728 | int? batchSizeLimit = DefaultBatchSizeLimit, 729 | TimeSpan? batchingPeriod = null, 730 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 731 | int? queueSizeLimit = DefaultQueueSizeLimit, 732 | bool? disablePayloadSigning = false) 733 | { 734 | if (sinkConfiguration is null) 735 | { 736 | throw new ArgumentNullException(nameof(sinkConfiguration)); 737 | } 738 | 739 | if (string.IsNullOrWhiteSpace(path)) 740 | { 741 | throw new ArgumentNullException(nameof(path)); 742 | } 743 | 744 | if (string.IsNullOrWhiteSpace(bucketName)) 745 | { 746 | throw new ArgumentNullException(nameof(bucketName)); 747 | } 748 | 749 | if (string.IsNullOrWhiteSpace(serviceUrl)) 750 | { 751 | throw new ArgumentNullException(nameof(serviceUrl)); 752 | } 753 | 754 | if (!ConfigurationValidator.ValidateServiceUrl(serviceUrl)) 755 | { 756 | throw new ArgumentException(ErrorMessageConstants.ServiceUrlInvalidFormat, nameof(serviceUrl)); 757 | } 758 | 759 | if (string.IsNullOrWhiteSpace(awsAccessKeyId)) 760 | { 761 | throw new ArgumentNullException(nameof(awsAccessKeyId)); 762 | } 763 | 764 | if (string.IsNullOrWhiteSpace(awsSecretAccessKey)) 765 | { 766 | throw new ArgumentNullException(nameof(awsSecretAccessKey)); 767 | } 768 | 769 | if (formatter is null) 770 | { 771 | throw new ArgumentNullException(nameof(formatter)); 772 | } 773 | 774 | encoding ??= DefaultEncoding; 775 | batchingPeriod ??= DefaultBatchingPeriod; 776 | 777 | var options = new AmazonS3Options 778 | { 779 | Path = path, 780 | BucketName = bucketName, 781 | ServiceUrl = serviceUrl, 782 | AwsAccessKeyId = awsAccessKeyId, 783 | AwsSecretAccessKey = awsSecretAccessKey, 784 | Formatter = formatter, 785 | RollingInterval = rollingInterval, 786 | Encoding = encoding, 787 | BucketPath = bucketPath, 788 | Endpoint = null, 789 | DisablePayloadSigning = disablePayloadSigning 790 | }; 791 | 792 | var amazonS3Sink = new AmazonS3Sink(options); 793 | 794 | var batchingOptions = new PeriodicBatchingSinkOptions 795 | { 796 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 797 | Period = (TimeSpan)batchingPeriod, 798 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 799 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 800 | }; 801 | 802 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 803 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 804 | } 805 | 806 | /// Write log events to the specified file. 807 | /// 808 | /// Thrown when one or more required arguments are 809 | /// null. 810 | /// 811 | /// The logger sink configuration. 812 | /// The path to the file. 813 | /// The Amazon S3 bucket name. 814 | /// The Amazon S3 service url. 815 | /// 816 | /// (Optional) 817 | /// The minimum level for 818 | /// events passed through the sink. Ignored when 819 | /// is specified. 820 | /// 821 | /// 822 | /// (Optional) 823 | /// A switch allowing the pass-through minimum level 824 | /// to be changed at runtime. 825 | /// 826 | /// 827 | /// (Optional) 828 | /// A message template describing the format used to 829 | /// write to the sink. 830 | /// The default is "{Timestamp:yyyy-MM-dd 831 | /// HH:mm:ss.fff zzz} [{Level:u3}] 832 | /// {Message:lj}{NewLine}{Exception}". 833 | /// 834 | /// 835 | /// (Optional) 836 | /// Supplies culture-specific formatting information, or 837 | /// null. 838 | /// 839 | /// 840 | /// (Optional) 841 | /// The interval at which logging will roll over to a new 842 | /// file. 843 | /// 844 | /// 845 | /// (Optional) 846 | /// Character encoding used to write the text file. The 847 | /// default is UTF-8 without BOM. 848 | /// 849 | /// The Amazon S3 bucket path. 850 | /// The batch size limit. 851 | /// The batching period. 852 | /// A value indicating whether the first event should be emitted immediately or not. 853 | /// The queue size limit. 854 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 855 | /// The configuration object allowing method chaining. 856 | public static LoggerConfiguration AmazonS3( 857 | this LoggerSinkConfiguration sinkConfiguration, 858 | string path, 859 | string bucketName, 860 | string serviceUrl, 861 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 862 | LoggingLevelSwitch? levelSwitch = null, 863 | string? outputTemplate = DefaultOutputTemplate, 864 | IFormatProvider? formatProvider = null, 865 | RollingInterval rollingInterval = RollingInterval.Day, 866 | Encoding? encoding = null, 867 | string? bucketPath = null, 868 | int? batchSizeLimit = DefaultBatchSizeLimit, 869 | TimeSpan? batchingPeriod = null, 870 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 871 | int? queueSizeLimit = DefaultQueueSizeLimit, 872 | bool? disablePayloadSigning = false) 873 | { 874 | if (sinkConfiguration is null) 875 | { 876 | throw new ArgumentNullException(nameof(sinkConfiguration)); 877 | } 878 | 879 | if (string.IsNullOrWhiteSpace(path)) 880 | { 881 | throw new ArgumentNullException(nameof(path)); 882 | } 883 | 884 | if (string.IsNullOrWhiteSpace(bucketName)) 885 | { 886 | throw new ArgumentNullException(nameof(bucketName)); 887 | } 888 | 889 | if (string.IsNullOrWhiteSpace(serviceUrl)) 890 | { 891 | throw new ArgumentNullException(nameof(serviceUrl)); 892 | } 893 | 894 | if (!ConfigurationValidator.ValidateServiceUrl(serviceUrl)) 895 | { 896 | throw new ArgumentException(ErrorMessageConstants.ServiceUrlInvalidFormat, nameof(serviceUrl)); 897 | } 898 | 899 | if (string.IsNullOrWhiteSpace(outputTemplate)) 900 | { 901 | outputTemplate = DefaultOutputTemplate; 902 | } 903 | 904 | encoding ??= DefaultEncoding; 905 | batchingPeriod ??= DefaultBatchingPeriod; 906 | 907 | var options = new AmazonS3Options 908 | { 909 | Path = path, 910 | BucketName = bucketName, 911 | ServiceUrl = serviceUrl, 912 | OutputTemplate = outputTemplate, 913 | FormatProvider = formatProvider, 914 | RollingInterval = rollingInterval, 915 | Encoding = encoding, 916 | BucketPath = bucketPath, 917 | Endpoint = null, 918 | DisablePayloadSigning = disablePayloadSigning 919 | }; 920 | 921 | var amazonS3Sink = new AmazonS3Sink(options); 922 | 923 | var batchingOptions = new PeriodicBatchingSinkOptions 924 | { 925 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 926 | Period = (TimeSpan)batchingPeriod, 927 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 928 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 929 | }; 930 | 931 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 932 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 933 | } 934 | 935 | /// Write log events to the specified file. 936 | /// 937 | /// Thrown when one or more required arguments are 938 | /// null. 939 | /// 940 | /// The logger sink configuration. 941 | /// The path to the file. 942 | /// The Amazon S3 bucket name. 943 | /// The Amazon S3 service url. 944 | /// 945 | /// (Optional) 946 | /// The minimum level for 947 | /// events passed through the sink. Ignored when 948 | /// is specified. 949 | /// 950 | /// 951 | /// (Optional) 952 | /// A switch allowing the pass-through minimum level 953 | /// to be changed at runtime. 954 | /// 955 | /// The formatter. 956 | /// 957 | /// (Optional) 958 | /// The interval at which logging will roll over to a new 959 | /// file. 960 | /// 961 | /// 962 | /// (Optional) 963 | /// Character encoding used to write the text file. The 964 | /// default is UTF-8 without BOM. 965 | /// 966 | /// The Amazon S3 bucket path. 967 | /// The batch size limit. 968 | /// The batching period. 969 | /// A value indicating whether the first event should be emitted immediately or not. 970 | /// The queue size limit. 971 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 972 | /// The configuration object allowing method chaining. 973 | public static LoggerConfiguration AmazonS3( 974 | this LoggerSinkConfiguration sinkConfiguration, 975 | string path, 976 | string bucketName, 977 | string serviceUrl, 978 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 979 | LoggingLevelSwitch? levelSwitch = null, 980 | ITextFormatter? formatter = null, 981 | RollingInterval rollingInterval = RollingInterval.Day, 982 | Encoding? encoding = null, 983 | string? bucketPath = null, 984 | int? batchSizeLimit = DefaultBatchSizeLimit, 985 | TimeSpan? batchingPeriod = null, 986 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 987 | int? queueSizeLimit = DefaultQueueSizeLimit, 988 | bool? disablePayloadSigning = false) 989 | { 990 | if (sinkConfiguration is null) 991 | { 992 | throw new ArgumentNullException(nameof(sinkConfiguration)); 993 | } 994 | 995 | if (string.IsNullOrWhiteSpace(path)) 996 | { 997 | throw new ArgumentNullException(nameof(path)); 998 | } 999 | 1000 | if (string.IsNullOrWhiteSpace(bucketName)) 1001 | { 1002 | throw new ArgumentNullException(nameof(bucketName)); 1003 | } 1004 | 1005 | if (string.IsNullOrWhiteSpace(serviceUrl)) 1006 | { 1007 | throw new ArgumentNullException(nameof(serviceUrl)); 1008 | } 1009 | 1010 | if (!ConfigurationValidator.ValidateServiceUrl(serviceUrl)) 1011 | { 1012 | throw new ArgumentException(ErrorMessageConstants.ServiceUrlInvalidFormat, nameof(serviceUrl)); 1013 | } 1014 | 1015 | if (formatter is null) 1016 | { 1017 | throw new ArgumentNullException(nameof(formatter)); 1018 | } 1019 | 1020 | encoding ??= DefaultEncoding; 1021 | batchingPeriod ??= DefaultBatchingPeriod; 1022 | 1023 | var options = new AmazonS3Options 1024 | { 1025 | Path = path, 1026 | BucketName = bucketName, 1027 | ServiceUrl = serviceUrl, 1028 | Formatter = formatter, 1029 | RollingInterval = rollingInterval, 1030 | Encoding = encoding, 1031 | BucketPath = bucketPath, 1032 | Endpoint = null, 1033 | DisablePayloadSigning = disablePayloadSigning 1034 | }; 1035 | 1036 | var amazonS3Sink = new AmazonS3Sink(options); 1037 | 1038 | var batchingOptions = new PeriodicBatchingSinkOptions 1039 | { 1040 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 1041 | Period = (TimeSpan)batchingPeriod, 1042 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 1043 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 1044 | }; 1045 | 1046 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 1047 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 1048 | } 1049 | 1050 | /// Write log events to the specified file. 1051 | /// 1052 | /// Thrown when one or more required arguments are 1053 | /// null. 1054 | /// 1055 | /// The logger sink configuration. 1056 | /// The Amazon S3 client. 1057 | /// The path to the file. 1058 | /// The Amazon S3 bucket name. 1059 | /// 1060 | /// (Optional) 1061 | /// The minimum level for 1062 | /// events passed through the sink. Ignored when 1063 | /// is specified. 1064 | /// 1065 | /// 1066 | /// (Optional) 1067 | /// A message template describing the format used to 1068 | /// write to the sink. 1069 | /// The default is "{Timestamp:yyyy-MM-dd 1070 | /// HH:mm:ss.fff zzz} [{Level:u3}] 1071 | /// {Message:lj}{NewLine}{Exception}". 1072 | /// 1073 | /// 1074 | /// (Optional) 1075 | /// Supplies culture-specific formatting information, or 1076 | /// null. 1077 | /// 1078 | /// 1079 | /// (Optional) 1080 | /// A switch allowing the pass-through minimum level 1081 | /// to be changed at runtime. 1082 | /// 1083 | /// 1084 | /// (Optional) 1085 | /// The interval at which logging will roll over to a new 1086 | /// file. 1087 | /// 1088 | /// 1089 | /// (Optional) 1090 | /// Character encoding used to write the text file. The 1091 | /// default is UTF-8 without BOM. 1092 | /// 1093 | /// The Amazon S3 bucket path. 1094 | /// The batch size limit. 1095 | /// The batching period. 1096 | /// A value indicating whether the first event should be emitted immediately or not. 1097 | /// The queue size limit. 1098 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 1099 | /// The configuration object allowing method chaining. 1100 | public static LoggerConfiguration AmazonS3( 1101 | this LoggerSinkConfiguration sinkConfiguration, 1102 | AmazonS3Client client, 1103 | string path, 1104 | string bucketName, 1105 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 1106 | string? outputTemplate = DefaultOutputTemplate, 1107 | IFormatProvider? formatProvider = null, 1108 | LoggingLevelSwitch? levelSwitch = null, 1109 | RollingInterval rollingInterval = RollingInterval.Day, 1110 | Encoding? encoding = null, 1111 | string? bucketPath = null, 1112 | int? batchSizeLimit = DefaultBatchSizeLimit, 1113 | TimeSpan? batchingPeriod = null, 1114 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 1115 | int? queueSizeLimit = DefaultQueueSizeLimit, 1116 | bool? disablePayloadSigning = false) 1117 | { 1118 | if (sinkConfiguration is null) 1119 | { 1120 | throw new ArgumentNullException(nameof(sinkConfiguration)); 1121 | } 1122 | 1123 | if (client is null) 1124 | { 1125 | throw new ArgumentNullException(nameof(client)); 1126 | } 1127 | 1128 | if (string.IsNullOrWhiteSpace(path)) 1129 | { 1130 | throw new ArgumentNullException(nameof(path)); 1131 | } 1132 | 1133 | if (string.IsNullOrWhiteSpace(bucketName)) 1134 | { 1135 | throw new ArgumentNullException(nameof(bucketName)); 1136 | } 1137 | 1138 | if (string.IsNullOrWhiteSpace(outputTemplate)) 1139 | { 1140 | outputTemplate = DefaultOutputTemplate; 1141 | } 1142 | 1143 | encoding ??= DefaultEncoding; 1144 | batchingPeriod ??= DefaultBatchingPeriod; 1145 | 1146 | var options = new AmazonS3Options 1147 | { 1148 | AmazonS3Client = client, 1149 | Path = path, 1150 | BucketName = bucketName, 1151 | OutputTemplate = outputTemplate, 1152 | FormatProvider = formatProvider, 1153 | RollingInterval = rollingInterval, 1154 | Encoding = encoding, 1155 | BucketPath = bucketPath, 1156 | DisablePayloadSigning = disablePayloadSigning 1157 | }; 1158 | 1159 | var amazonS3Sink = new AmazonS3Sink(options); 1160 | 1161 | var batchingOptions = new PeriodicBatchingSinkOptions 1162 | { 1163 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 1164 | Period = (TimeSpan)batchingPeriod, 1165 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 1166 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 1167 | }; 1168 | 1169 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 1170 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 1171 | } 1172 | 1173 | /// Write log events to the specified file. 1174 | /// 1175 | /// Thrown when one or more required arguments are 1176 | /// null. 1177 | /// 1178 | /// The logger sink configuration. 1179 | /// The Amazon S3 client. 1180 | /// The path to the file. 1181 | /// The Amazon S3 bucket name. 1182 | /// 1183 | /// (Optional) 1184 | /// The minimum level for 1185 | /// events passed through the sink. Ignored when 1186 | /// is specified. 1187 | /// 1188 | /// 1189 | /// (Optional) 1190 | /// A switch allowing the pass-through minimum level 1191 | /// to be changed at runtime. 1192 | /// 1193 | /// The formatter. 1194 | /// 1195 | /// (Optional) 1196 | /// The interval at which logging will roll over to a new 1197 | /// file. 1198 | /// 1199 | /// 1200 | /// (Optional) 1201 | /// Character encoding used to write the text file. The 1202 | /// default is UTF-8 without BOM. 1203 | /// 1204 | /// The Amazon S3 bucket path. 1205 | /// The batch size limit. 1206 | /// The batching period. 1207 | /// A value indicating whether the first event should be emitted immediately or not. 1208 | /// The queue size limit. 1209 | /// A value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 1210 | /// The configuration object allowing method chaining. 1211 | public static LoggerConfiguration AmazonS3( 1212 | this LoggerSinkConfiguration sinkConfiguration, 1213 | AmazonS3Client client, 1214 | string path, 1215 | string bucketName, 1216 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 1217 | LoggingLevelSwitch? levelSwitch = null, 1218 | ITextFormatter? formatter = null, 1219 | RollingInterval rollingInterval = RollingInterval.Day, 1220 | Encoding? encoding = null, 1221 | string? bucketPath = null, 1222 | int? batchSizeLimit = DefaultBatchSizeLimit, 1223 | TimeSpan? batchingPeriod = null, 1224 | bool? eagerlyEmitFirstEvent = DefaultEagerlyEmitFirstEvent, 1225 | int? queueSizeLimit = DefaultQueueSizeLimit, 1226 | bool? disablePayloadSigning = false) 1227 | { 1228 | if (sinkConfiguration is null) 1229 | { 1230 | throw new ArgumentNullException(nameof(sinkConfiguration)); 1231 | } 1232 | 1233 | if (client is null) 1234 | { 1235 | throw new ArgumentNullException(nameof(client)); 1236 | } 1237 | 1238 | if (string.IsNullOrWhiteSpace(path)) 1239 | { 1240 | throw new ArgumentNullException(nameof(path)); 1241 | } 1242 | 1243 | if (string.IsNullOrWhiteSpace(bucketName)) 1244 | { 1245 | throw new ArgumentNullException(nameof(bucketName)); 1246 | } 1247 | 1248 | if (formatter is null) 1249 | { 1250 | throw new ArgumentNullException(nameof(formatter)); 1251 | } 1252 | 1253 | encoding ??= DefaultEncoding; 1254 | batchingPeriod ??= DefaultBatchingPeriod; 1255 | 1256 | var options = new AmazonS3Options 1257 | { 1258 | AmazonS3Client = client, 1259 | Path = path, 1260 | BucketName = bucketName, 1261 | Formatter = formatter, 1262 | RollingInterval = rollingInterval, 1263 | Encoding = encoding, 1264 | BucketPath = bucketPath, 1265 | DisablePayloadSigning = disablePayloadSigning 1266 | }; 1267 | 1268 | var amazonS3Sink = new AmazonS3Sink(options); 1269 | 1270 | var batchingOptions = new PeriodicBatchingSinkOptions 1271 | { 1272 | BatchSizeLimit = batchSizeLimit ?? DefaultBatchSizeLimit, 1273 | Period = (TimeSpan)batchingPeriod, 1274 | EagerlyEmitFirstEvent = eagerlyEmitFirstEvent ?? DefaultEagerlyEmitFirstEvent, 1275 | QueueLimit = queueSizeLimit ?? DefaultQueueSizeLimit 1276 | }; 1277 | 1278 | var batchingSink = new PeriodicBatchingSink(amazonS3Sink, batchingOptions); 1279 | return sinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch); 1280 | } 1281 | } 1282 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Serilog.Sinks.AmazonS3.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0 5 | Serilog.Sinks.AmazonS3 6 | Serilog 7 | true 8 | true 9 | Serilog.Sinks.AmazonS3 10 | SeppPenner and the Serilog contributors 11 | SeppPenner and the Serilog contributors 12 | Copyright © SeppPenner and the Serilog contributors 13 | Serilog.Sinks.AmazonS3 is a library to save logging information from https://github.com/serilog/serilog to https://aws.amazon.com/s3/. The idea there was to upload log files to https://aws.amazon.com/s3/ to later evaluate them with https://aws.amazon.com/emr/ services. This project makes use of the https://github.com/serilog/serilog-sinks-file's code in a major part, so thanks to all the https://github.com/serilog/serilog-sinks-file/graphs/contributors of this project :thumbsup:. 14 | c# csharp serilog amazon s3 amazons3 sink logging log 15 | https://www.nuget.org/packages/Serilog.Sinks.AmazonS3/ 16 | https://github.com/SeppPenner/Serilog.Sinks.AmazonS3 17 | Icon.png 18 | Github 19 | Version 1.6.0.0 (2025-03-24): Fixes https://github.com/serilog-contrib/Serilog.Sinks.Postgresql.Alternative/issues/83, updates NuGet packages, deprecates `failureCallback`. 20 | MIT 21 | latest 22 | enable 23 | true 24 | true 25 | true 26 | snupkg 27 | enable 28 | NU1803 29 | true 30 | README.md 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | True 48 | 49 | 50 | 51 | True 52 | 53 | 54 | 55 | True 56 | 57 | 58 | 59 | True 60 | 61 | 62 | 63 | True 64 | 65 | 66 | 67 | True 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/AmazonS3Options.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // This class is the main options class. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog.Sinks.AmazonS3; 11 | 12 | /// 13 | /// This class is the main options class. 14 | /// 15 | public class AmazonS3Options 16 | { 17 | /// 18 | /// Gets or sets the Amazon S3 client. 19 | /// 20 | public AmazonS3Client? AmazonS3Client { get; set; } 21 | 22 | /// 23 | /// Gets or sets the AWS access key identifier. 24 | /// 25 | public string AwsAccessKeyId { get; set; } = string.Empty; 26 | 27 | /// 28 | /// Gets or sets the AWS secret access key. 29 | /// 30 | public string AwsSecretAccessKey { get; set; } = string.Empty; 31 | 32 | /// 33 | /// Gets or sets the Amazon S3 bucket name. 34 | /// 35 | public string BucketName { get; set; } = string.Empty; 36 | 37 | /// 38 | /// Gets or sets the path where local files are stored. 39 | /// 40 | public string? BucketPath { get; set; } 41 | 42 | /// 43 | /// Gets or sets the encoding. 44 | /// 45 | public Encoding Encoding { get; set; } = Encoding.UTF8; 46 | 47 | /// 48 | /// Gets or sets the Amazon S3 key endpoint. 49 | /// 50 | public RegionEndpoint? Endpoint { get; set; } 51 | 52 | /// 53 | /// Gets or sets the local path where the files are stored. 54 | /// 55 | public string Path { get; set; } = string.Empty; 56 | 57 | /// 58 | /// Gets or sets the text formatter. 59 | /// 60 | public ITextFormatter? Formatter { get; set; } 61 | 62 | /// 63 | /// Gets or sets the Amazon S3 service url. 64 | /// 65 | public string ServiceUrl { get; set; } = "https://s3.amazonaws.com"; 66 | 67 | /// 68 | /// Gets or sets the rolling interval. 69 | /// 70 | public RollingInterval RollingInterval { get; set; } 71 | 72 | /// 73 | /// Gets or sets the output template. 74 | /// 75 | public string? OutputTemplate { get; set; } 76 | 77 | /// 78 | /// Gets or sets the format provider. 79 | /// 80 | public IFormatProvider? FormatProvider { get; set; } 81 | 82 | /// 83 | /// Gets or sets the path roller. Internally used only, not to be set by the options. 84 | /// 85 | public PathRoller? PathRoller { get; set; } 86 | 87 | /// 88 | /// Gets or sets the next checkpoint. Internally used only, not to be set by the options. 89 | /// 90 | public DateTime? NextCheckpoint { get; set; } 91 | 92 | /// 93 | /// Gets or sets the current file sequence number. Internally used only, not to be set by the options. 94 | /// 95 | public int? CurrentFileSequence { get; set; } 96 | 97 | /// 98 | /// Gets or sets a value indicating whether the Amazon S3 SigV4 payload signing should be disabled or not (Needed for e.g. for the Cloudflare R2 API). 99 | /// 100 | public bool? DisablePayloadSigning { get; set; } = false; 101 | } 102 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/AmazonS3Sink.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // This class is the main class and contains all logic for the AmazonS3 sink. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog.Sinks.AmazonS3; 11 | 12 | using IBatchedLogEventSink = PeriodicBatching.IBatchedLogEventSink; 13 | 14 | /// 15 | /// This class is the main class and contains all logic for the AmazonS3 sink. 16 | /// 17 | public class AmazonS3Sink : IBatchedLogEventSink 18 | { 19 | /// 20 | /// The Amazon S3 options. 21 | /// 22 | private readonly AmazonS3Options amazonS3Options = new(); 23 | 24 | /// Initializes a new instance of the class. 25 | /// A given value is null. 26 | /// The Amazon S3 options. 27 | public AmazonS3Sink(AmazonS3Options amazonS3Options) 28 | { 29 | this.amazonS3Options.AmazonS3Client = amazonS3Options.AmazonS3Client; 30 | this.amazonS3Options.AwsAccessKeyId = amazonS3Options.AwsAccessKeyId; 31 | this.amazonS3Options.AwsSecretAccessKey = amazonS3Options.AwsSecretAccessKey; 32 | this.amazonS3Options.BucketName = amazonS3Options.BucketName; 33 | this.amazonS3Options.BucketPath = amazonS3Options.BucketPath; 34 | this.amazonS3Options.Encoding = amazonS3Options.Encoding; 35 | this.amazonS3Options.Endpoint = amazonS3Options.Endpoint; 36 | this.amazonS3Options.Path = amazonS3Options.Path; 37 | 38 | if (amazonS3Options.Formatter is null) 39 | { 40 | var textFormatter = new MessageTemplateTextFormatter(amazonS3Options.OutputTemplate!, amazonS3Options.FormatProvider); 41 | this.amazonS3Options.Formatter = textFormatter; 42 | } 43 | else 44 | { 45 | this.amazonS3Options.Formatter = amazonS3Options.Formatter; 46 | } 47 | 48 | this.amazonS3Options.ServiceUrl = amazonS3Options.ServiceUrl; 49 | this.amazonS3Options.PathRoller = new PathRoller(amazonS3Options.Path, amazonS3Options.RollingInterval); 50 | this.amazonS3Options.RollingInterval = amazonS3Options.RollingInterval; 51 | this.amazonS3Options.OutputTemplate = amazonS3Options.OutputTemplate; 52 | this.amazonS3Options.FormatProvider = amazonS3Options.FormatProvider; 53 | this.amazonS3Options.DisablePayloadSigning = amazonS3Options.DisablePayloadSigning; 54 | } 55 | 56 | /// Emit a batch of log events, running asynchronously. 57 | /// The batch of events to emit. 58 | /// A returning any asynchronous operation. 59 | public async Task EmitBatchAsync(IEnumerable batch) 60 | { 61 | var fileInformation = this.OpenFile(); 62 | 63 | if (fileInformation.OutputWriter is null) 64 | { 65 | throw new InvalidOperationException($"The output writer was not properly set for {fileInformation.FileName}"); 66 | } 67 | 68 | foreach (var logEvent in batch) 69 | { 70 | this.amazonS3Options.Formatter?.Format(logEvent, fileInformation.OutputWriter); 71 | } 72 | 73 | await fileInformation.OutputWriter.FlushAsync(); 74 | fileInformation.OutputWriter.Close(); 75 | 76 | _ = await this.UploadFileToS3(fileInformation.FileName); 77 | File.Delete(fileInformation.FileName); 78 | } 79 | 80 | /// 81 | /// Allows sinks to perform periodic work without requiring additional threads 82 | /// or timers (thus avoiding additional flush/shut-down complexity). 83 | /// 84 | /// A returning any asynchronous operation. 85 | public async Task OnEmptyBatchAsync() 86 | { 87 | await Task.Delay(0); 88 | } 89 | 90 | /// 91 | /// Open a file and returns the file name and file stream. 92 | /// 93 | /// The . 94 | private FileInformation OpenFile() 95 | { 96 | var fileName = this.AlignCurrentFileTo(DateTime.Now, true); 97 | 98 | var directory = Path.GetDirectoryName(this.amazonS3Options.Path); 99 | 100 | if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) 101 | { 102 | Directory.CreateDirectory(directory); 103 | } 104 | 105 | var outputStream = File.Open(fileName, FileMode.Append, FileAccess.Write, FileShare.Read); 106 | 107 | return new FileInformation 108 | { 109 | FileName = fileName, 110 | OutputWriter = new StreamWriter(outputStream, this.amazonS3Options.Encoding) 111 | }; 112 | } 113 | 114 | /// 115 | /// Aligns the current file name to the sequence and returns the file name. 116 | /// 117 | /// The current date. 118 | /// The next sequence number. 119 | /// The file name as . 120 | private string AlignCurrentFileTo(DateTime now, bool nextSequence = false) 121 | { 122 | if (!this.amazonS3Options.NextCheckpoint.HasValue) 123 | { 124 | return this.GetFileName(now); 125 | } 126 | 127 | if (nextSequence || now >= this.amazonS3Options.NextCheckpoint.Value) 128 | { 129 | int? minSequence = null; 130 | 131 | if (nextSequence) 132 | { 133 | if (this.amazonS3Options.CurrentFileSequence is null) 134 | { 135 | minSequence = 1; 136 | } 137 | else 138 | { 139 | minSequence = this.amazonS3Options.CurrentFileSequence.Value + 1; 140 | } 141 | } 142 | 143 | return this.GetFileName(now, minSequence); 144 | } 145 | 146 | return string.Empty; 147 | } 148 | 149 | /// 150 | /// Gets the file name according to the rolling file sequence. 151 | /// 152 | /// The current date. 153 | /// The minimum sequence number. 154 | /// The file name as . 155 | private string GetFileName(DateTime now, int? minSequence = null) 156 | { 157 | if (this.amazonS3Options.PathRoller is null) 158 | { 159 | throw new InvalidOperationException("The path roller wasn't properly set."); 160 | } 161 | 162 | var currentCheckpoint = this.amazonS3Options.PathRoller.GetCurrentCheckpoint(now); 163 | this.amazonS3Options.NextCheckpoint = this.amazonS3Options.PathRoller.GetNextCheckpoint(now); 164 | 165 | var existingFiles = new List(); 166 | 167 | try 168 | { 169 | if (Directory.Exists(this.amazonS3Options.PathRoller.LogFileDirectory)) 170 | { 171 | var directoryFiles = Directory.GetFiles(this.amazonS3Options.PathRoller.LogFileDirectory, this.amazonS3Options.PathRoller.DirectorySearchPattern); 172 | var files = directoryFiles.Select(f => Path.GetFileName(f) ?? string.Empty)?.ToList() ?? new List(); 173 | existingFiles = files ?? new List(); 174 | } 175 | } 176 | catch (DirectoryNotFoundException) 177 | { 178 | // Ignored 179 | } 180 | 181 | var latestForThisCheckpoint = this.amazonS3Options.PathRoller 182 | .SelectMatches(existingFiles) 183 | .Where(m => m.DateTime == currentCheckpoint) 184 | .OrderByDescending(m => m.SequenceNumber) 185 | .FirstOrDefault(); 186 | 187 | var sequence = latestForThisCheckpoint?.SequenceNumber; 188 | 189 | if (minSequence is not null) 190 | { 191 | if (sequence is null || sequence.Value < minSequence.Value) 192 | { 193 | sequence = minSequence; 194 | } 195 | } 196 | 197 | this.amazonS3Options.PathRoller.GetLogFilePath(now, sequence, out var localPath); 198 | this.amazonS3Options.CurrentFileSequence = sequence; 199 | return localPath; 200 | } 201 | 202 | /// 203 | /// Uploads the file to a specified Amazon S3 bucket. 204 | /// 205 | /// he file name. 206 | /// 207 | /// Thrown when an Unauthorized Access error 208 | /// condition occurs. 209 | /// 210 | /// 211 | /// Thrown when an Amazon S3 error condition 212 | /// occurs. 213 | /// 214 | /// 215 | /// Check the provided AWS credentials. 216 | /// 217 | /// 218 | /// An asynchronous result that yields a PutObjectResponse. 219 | /// 220 | private async Task UploadFileToS3(string fileName) 221 | { 222 | var client = this.amazonS3Options.AmazonS3Client; 223 | 224 | if (client is null) 225 | { 226 | if (this.amazonS3Options.Endpoint != null) 227 | { 228 | client = new AmazonS3Client(this.amazonS3Options.Endpoint); 229 | } 230 | else 231 | { 232 | client = new AmazonS3Client( 233 | new AmazonS3Config 234 | { 235 | ServiceURL = this.amazonS3Options.ServiceUrl 236 | }); 237 | } 238 | 239 | // In the case that awsAccessKeyId and awsSecretAccessKey is passed, we use it. Otherwise authorization is given by roles in AWS directly. 240 | if (!string.IsNullOrEmpty(this.amazonS3Options.AwsAccessKeyId) && !string.IsNullOrEmpty(this.amazonS3Options.AwsSecretAccessKey)) 241 | { 242 | if (this.amazonS3Options.Endpoint != null) 243 | { 244 | client = new AmazonS3Client(this.amazonS3Options.AwsAccessKeyId, this.amazonS3Options.AwsSecretAccessKey, this.amazonS3Options.Endpoint); 245 | } 246 | else 247 | { 248 | client = new AmazonS3Client( 249 | this.amazonS3Options.AwsAccessKeyId, 250 | this.amazonS3Options.AwsSecretAccessKey, 251 | new AmazonS3Config 252 | { 253 | ServiceURL = this.amazonS3Options.ServiceUrl 254 | }); 255 | } 256 | } 257 | } 258 | 259 | try 260 | { 261 | // S3 does not support updates, files are automatically rewritten. So we will have to upload the entire file. 262 | // Open the file for shared reading and writing. 263 | using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read); 264 | 265 | var key = string.IsNullOrWhiteSpace(this.amazonS3Options.BucketPath) 266 | ? Path.GetFileName(fileName).Replace("\\", "/") 267 | : Path.Combine(this.amazonS3Options.BucketPath, Path.GetFileName(fileName)) 268 | .Replace("\\", "/"); 269 | 270 | if (fs.Length is 0) 271 | { 272 | throw new InvalidOperationException("The file size is 0."); 273 | } 274 | 275 | var putRequest = new PutObjectRequest 276 | { 277 | BucketName = this.amazonS3Options.BucketName, 278 | Key = key, 279 | InputStream = fs, 280 | DisablePayloadSigning = this.amazonS3Options.DisablePayloadSigning 281 | }; 282 | 283 | return await client.PutObjectAsync(putRequest); 284 | } 285 | catch (AmazonS3Exception amazonS3Exception) 286 | { 287 | if (amazonS3Exception.ErrorCode != null && (amazonS3Exception.ErrorCode.Equals("InvalidAccessKeyId") || amazonS3Exception.ErrorCode.Equals("InvalidSecurity"))) 288 | { 289 | throw new UnauthorizedAccessException("Check the provided AWS credentials."); 290 | } 291 | 292 | throw; 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/ConfigurationValidator.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // Validators for configuration parameters. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog.Sinks.AmazonS3; 11 | 12 | /// 13 | /// Validators for configuration parameters. 14 | /// 15 | internal static class ConfigurationValidator 16 | { 17 | /// 18 | /// Validates the value provided for property. 19 | /// 20 | /// The provided service url. 21 | /// A value indicating whether the service url is valid or not. 22 | internal static bool ValidateServiceUrl(string serviceUrl) => Uri.TryCreate(serviceUrl, UriKind.Absolute, out _); 23 | } 24 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/ErrorMessageConstants.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // Constants to use in user-facing error messages. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | namespace Serilog.Sinks.AmazonS3; 10 | 11 | /// 12 | /// Constants to use in user-facing error messages. 13 | /// 14 | internal static class ErrorMessageConstants 15 | { 16 | /// 17 | /// The provided is in invalid format. 18 | /// 19 | internal const string ServiceUrlInvalidFormat = "URL must be a valid absolute URL including the protocol."; 20 | } 21 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/FileInformation.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is licensed under the MIT license. 4 | // 5 | // 6 | // The file information. 7 | // 8 | // -------------------------------------------------------------------------------------------------------------------- 9 | 10 | namespace Serilog.Sinks.AmazonS3; 11 | 12 | /// 13 | /// The file information. 14 | /// 15 | public class FileInformation 16 | { 17 | /// 18 | /// Gets or sets the output stream writer. 19 | /// 20 | public StreamWriter? OutputWriter { get; set; } 21 | 22 | /// 23 | /// Gets or sets the file name. 24 | /// 25 | public string FileName { get; set; } = string.Empty; 26 | } 27 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/PathRoller.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is double licensed under the MIT license and the Apache License, Version 2.0. 4 | // This code is a partly modified source code of the original Serilog code. 5 | // The original license is: 6 | // 7 | // -------------------------------------------------------------------------------------------------------------------- 8 | // Copyright 2013-2016 Serilog Contributors 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // -------------------------------------------------------------------------------------------------------------------- 22 | // 23 | // 24 | // 25 | // The path roller to get the correct file sequences. 26 | // 27 | // -------------------------------------------------------------------------------------------------------------------- 28 | 29 | namespace Serilog.Sinks.AmazonS3; 30 | 31 | /// 32 | /// The path roller to get the correct file sequences. 33 | /// 34 | public class PathRoller 35 | { 36 | /// 37 | /// The period match group. 38 | /// 39 | private const string PeriodMatchGroup = "period"; 40 | 41 | /// 42 | /// The sequence number match group. 43 | /// 44 | private const string SequenceNumberMatchGroup = "sequence"; 45 | 46 | /// 47 | /// The file name prefix. 48 | /// 49 | private readonly string fileNamePrefix; 50 | 51 | /// 52 | /// The file name suffix. 53 | /// 54 | private readonly string fileNameSuffix; 55 | 56 | /// 57 | /// The file name matcher. 58 | /// 59 | private readonly Regex fileNameMatcher; 60 | 61 | /// 62 | /// The interval. 63 | /// 64 | private readonly RollingInterval interval; 65 | 66 | /// 67 | /// The period format. 68 | /// 69 | private readonly string periodFormat; 70 | 71 | /// 72 | /// Initializes a new instance of the class. 73 | /// 74 | /// The path. 75 | /// The interval. 76 | public PathRoller(string path, RollingInterval interval) 77 | { 78 | if (path is null) 79 | { 80 | throw new ArgumentNullException(nameof(path)); 81 | } 82 | 83 | this.interval = interval; 84 | this.periodFormat = interval.GetFormat(); 85 | 86 | var pathDirectory = Path.GetDirectoryName(path); 87 | 88 | if (string.IsNullOrEmpty(pathDirectory)) 89 | { 90 | pathDirectory = Directory.GetCurrentDirectory(); 91 | } 92 | 93 | this.LogFileDirectory = Path.GetFullPath(pathDirectory); 94 | this.fileNamePrefix = Path.GetFileNameWithoutExtension(path); 95 | this.fileNameSuffix = Path.GetExtension(path); 96 | this.fileNameMatcher = new Regex( 97 | "^" + 98 | Regex.Escape(this.fileNamePrefix) + 99 | "(?<" + PeriodMatchGroup + ">\\d{" + this.periodFormat.Length + "})" + 100 | "(?<" + SequenceNumberMatchGroup + ">_[0-9]{3,}){0,1}" + 101 | Regex.Escape(this.fileNameSuffix) + 102 | "$", 103 | RegexOptions.Compiled); 104 | 105 | this.DirectorySearchPattern = $"{this.fileNamePrefix}*{this.fileNameSuffix}"; 106 | } 107 | 108 | /// 109 | /// Gets the log file directory. 110 | /// 111 | public string LogFileDirectory { get; } 112 | 113 | /// 114 | /// Gets the directory search pattern. 115 | /// 116 | public string DirectorySearchPattern { get; } 117 | 118 | /// 119 | /// Gets the log file path. 120 | /// 121 | /// The date. 122 | /// The sequence number. 123 | /// The path. 124 | public void GetLogFilePath(DateTime date, int? sequenceNumber, out string path) 125 | { 126 | var currentCheckpoint = this.GetCurrentCheckpoint(date); 127 | 128 | var tok = currentCheckpoint?.ToString(this.periodFormat, CultureInfo.InvariantCulture) ?? string.Empty; 129 | 130 | if (sequenceNumber != null) 131 | { 132 | tok += "_" + sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture); 133 | } 134 | 135 | path = Path.Combine(this.LogFileDirectory, this.fileNamePrefix + tok + this.fileNameSuffix); 136 | } 137 | 138 | /// 139 | /// Selects the matches. 140 | /// 141 | /// The file names. 142 | /// A of s. 143 | public IEnumerable SelectMatches(IEnumerable fileNames) 144 | { 145 | foreach (var fileName in fileNames) 146 | { 147 | var match = this.fileNameMatcher.Match(fileName); 148 | 149 | if (!match.Success) 150 | { 151 | continue; 152 | } 153 | 154 | int? inc = null; 155 | var incGroup = match.Groups[SequenceNumberMatchGroup]; 156 | 157 | if (incGroup.Captures.Count != 0) 158 | { 159 | var incPart = incGroup.Captures[0].Value.Substring(1); 160 | inc = int.Parse(incPart, CultureInfo.InvariantCulture); 161 | } 162 | 163 | DateTime? period = null; 164 | var periodGroup = match.Groups[PeriodMatchGroup]; 165 | if (periodGroup.Captures.Count != 0) 166 | { 167 | var dateTimePart = periodGroup.Captures[0].Value; 168 | if (DateTime.TryParseExact( 169 | dateTimePart, 170 | this.periodFormat, 171 | CultureInfo.InvariantCulture, 172 | DateTimeStyles.None, 173 | out var dateTime)) 174 | { 175 | period = dateTime; 176 | } 177 | } 178 | 179 | yield return new RollingLogFile(fileName, period, inc); 180 | } 181 | } 182 | 183 | /// 184 | /// Gets the current check point as . 185 | /// 186 | /// The date time. 187 | /// The current check point as . 188 | public DateTime? GetCurrentCheckpoint(DateTime dateTime) => this.interval.GetCurrentCheckpoint(dateTime); 189 | 190 | /// 191 | /// Gets the next check point as . 192 | /// 193 | /// The date time. 194 | /// The next check point as . 195 | public DateTime? GetNextCheckpoint(DateTime dateTime) => this.interval.GetNextCheckpoint(dateTime); 196 | } 197 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/RollingInterval.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is double licensed under the MIT license and the Apache License, Version 2.0. 4 | // This code is a partly modified source code of the original Serilog code. 5 | // The original license is: 6 | // 7 | // -------------------------------------------------------------------------------------------------------------------- 8 | // Copyright 2013-2016 Serilog Contributors 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // -------------------------------------------------------------------------------------------------------------------- 22 | // 23 | // 24 | // 25 | // Specifies the frequency at which the log file should roll. 26 | // 27 | // -------------------------------------------------------------------------------------------------------------------- 28 | 29 | namespace Serilog.Sinks.AmazonS3; 30 | 31 | /// 32 | /// Specifies the frequency at which the log file should roll. 33 | /// 34 | public enum RollingInterval 35 | { 36 | /// 37 | /// The log file will never roll; no time period information will be appended to the log file name. 38 | /// 39 | Infinite, 40 | 41 | /// 42 | /// Roll every year. File names will have a four-digit year appended in the pattern yyyy. 43 | /// 44 | Year, 45 | 46 | /// 47 | /// Roll every calendar month. File names will have yyyyMM appended. 48 | /// 49 | Month, 50 | 51 | /// 52 | /// Roll every day. File names will have yyyyMMdd appended. 53 | /// 54 | Day, 55 | 56 | /// 57 | /// Roll every hour. File names will have yyyyMMddHH appended. 58 | /// 59 | Hour, 60 | 61 | /// 62 | /// Roll every minute. File names will have yyyyMMddHHmm appended. 63 | /// 64 | Minute 65 | } 66 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/RollingIntervalExtensions.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is double licensed under the MIT license and the Apache License, Version 2.0. 4 | // This code is a partly modified source code of the original Serilog code. 5 | // The original license is: 6 | // 7 | // -------------------------------------------------------------------------------------------------------------------- 8 | // Copyright 2013-2016 Serilog Contributors 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // -------------------------------------------------------------------------------------------------------------------- 22 | // 23 | // 24 | // 25 | // This class contains extension methods for the enumeration. 26 | // 27 | // -------------------------------------------------------------------------------------------------------------------- 28 | 29 | namespace Serilog.Sinks.AmazonS3; 30 | 31 | /// 32 | /// This class contains extension methods for the enumeration. 33 | /// 34 | public static class RollingIntervalExtensions 35 | { 36 | /// 37 | /// Gets the format . 38 | /// 39 | /// The interval. 40 | /// The format . 41 | public static string GetFormat(this RollingInterval interval) 42 | { 43 | return interval switch 44 | { 45 | RollingInterval.Infinite => "", 46 | RollingInterval.Year => "yyyy", 47 | RollingInterval.Month => "yyyyMM", 48 | RollingInterval.Day => "yyyyMMdd", 49 | RollingInterval.Hour => "yyyyMMddHH", 50 | RollingInterval.Minute => "yyyyMMddHHmm", 51 | _ => throw new ArgumentException("Invalid rolling interval"), 52 | }; 53 | } 54 | 55 | /// 56 | /// Gets the current check point as . 57 | /// 58 | /// The interval. 59 | /// The date time. 60 | /// The current check point as . 61 | public static DateTime? GetCurrentCheckpoint(this RollingInterval interval, DateTime dateTime) 62 | { 63 | return interval switch 64 | { 65 | RollingInterval.Infinite => null, 66 | RollingInterval.Year => new DateTime(dateTime.Year, 1, 1, 0, 0, 0, dateTime.Kind), 67 | RollingInterval.Month => new DateTime(dateTime.Year, dateTime.Month, 1, 0, 0, 0, dateTime.Kind), 68 | RollingInterval.Day => new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0, dateTime.Kind), 69 | RollingInterval.Hour => new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, 0, 0, dateTime.Kind), 70 | RollingInterval.Minute => new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, 0, dateTime.Kind), 71 | _ => throw new ArgumentException("Invalid rolling interval"), 72 | }; 73 | } 74 | 75 | /// 76 | /// Gets the next check point as . 77 | /// 78 | /// The interval. 79 | /// The date time. 80 | /// The next check point as . 81 | public static DateTime? GetNextCheckpoint(this RollingInterval interval, DateTime dateTime) 82 | { 83 | var current = GetCurrentCheckpoint(interval, dateTime); 84 | 85 | if (current is null) 86 | { 87 | return null; 88 | } 89 | 90 | return interval switch 91 | { 92 | RollingInterval.Year => current.Value.AddYears(1), 93 | RollingInterval.Month => current.Value.AddMonths(1), 94 | RollingInterval.Day => current.Value.AddDays(1), 95 | RollingInterval.Hour => current.Value.AddHours(1), 96 | RollingInterval.Minute => current.Value.AddMinutes(1), 97 | _ => throw new ArgumentException("Invalid rolling interval"), 98 | }; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.AmazonS3/Sinks/AmazonS3/RollingLogFile.cs: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------------------------------- 2 | // 3 | // The project is double licensed under the MIT license and the Apache License, Version 2.0. 4 | // This code is a partly modified source code of the original Serilog code. 5 | // The original license is: 6 | // 7 | // -------------------------------------------------------------------------------------------------------------------- 8 | // Copyright 2013-2016 Serilog Contributors 9 | // 10 | // Licensed under the Apache License, Version 2.0 (the "License"); 11 | // you may not use this file except in compliance with the License. 12 | // You may obtain a copy of the License at 13 | // 14 | // http://www.apache.org/licenses/LICENSE-2.0 15 | // 16 | // Unless required by applicable law or agreed to in writing, software 17 | // distributed under the License is distributed on an "AS IS" BASIS, 18 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | // See the License for the specific language governing permissions and 20 | // limitations under the License. 21 | // -------------------------------------------------------------------------------------------------------------------- 22 | // 23 | // 24 | // 25 | // The rolling log file class to handle rolling log files. 26 | // 27 | // -------------------------------------------------------------------------------------------------------------------- 28 | 29 | namespace Serilog.Sinks.AmazonS3; 30 | 31 | /// 32 | /// The rolling log file class to handle rolling log files. 33 | /// 34 | public class RollingLogFile 35 | { 36 | /// 37 | /// Initializes a new instance of the class. 38 | /// 39 | /// The file name. 40 | /// The date time. 41 | /// The sequence number. 42 | public RollingLogFile(string fileName, DateTime? dateTime, int? sequenceNumber) 43 | { 44 | this.FileName = fileName; 45 | this.DateTime = dateTime; 46 | this.SequenceNumber = sequenceNumber; 47 | } 48 | 49 | /// 50 | /// Gets the file name. 51 | /// 52 | public string FileName { get; } 53 | 54 | /// 55 | /// Gets the date time. 56 | /// 57 | public DateTime? DateTime { get; } 58 | 59 | /// 60 | /// Gets the sequence number. 61 | /// 62 | public int? SequenceNumber { get; } 63 | } 64 | --------------------------------------------------------------------------------