├── .editorconfig
├── .gitattributes
├── .gitignore
├── Build.ps1
├── CHANGES.md
├── LICENSE
├── README.md
├── appveyor.yml
├── assets
├── CommonAssemblyInfo.cs
└── Serilog.snk
├── sample
└── AmazonKinesisSample
│ ├── AmazonKinesisSample.csproj
│ ├── App.config
│ ├── Program.cs
│ ├── Properties
│ └── CommonAssemblyInfo.cs
│ └── packages.config
├── serilog-sinks-amazonkinesis.sln
├── src
├── Serilog.Sinks.Amazon.Kinesis
│ ├── Common
│ │ ├── CustomJsonFormatter.cs
│ │ ├── HttpLogShipperBase.cs
│ │ ├── ILogReader.cs
│ │ ├── ILogReaderFactory.cs
│ │ ├── ILogShipperFileManager.cs
│ │ ├── ILogShipperOptions.cs
│ │ ├── IPersistedBookmark.cs
│ │ ├── IPersistedBookmarkFactory.cs
│ │ ├── KinesisSinkOptionsBase.cs
│ │ ├── KinesisSinkStateBase.cs
│ │ ├── LogReader.cs
│ │ ├── LogReaderFactory.cs
│ │ ├── LogSendErrorEventArgs.cs
│ │ ├── LogShipperFileManager.cs
│ │ ├── PersistedBookmark.cs
│ │ ├── PersistedBookmarkFactory.cs
│ │ └── Throttle.cs
│ ├── Firehose
│ │ ├── KinesisFirehoseLoggerConfigurationExtensions.cs
│ │ └── Sinks
│ │ │ ├── DurableKinesisFirehoseSink.cs
│ │ │ ├── HttpLogShipper.cs
│ │ │ ├── KinesisFirehoseSink.cs
│ │ │ ├── KinesisFirehoseSinkOptions.cs
│ │ │ └── KinesisSinkState.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Serilog.Sinks.Amazon.Kinesis.csproj
│ ├── Stream
│ │ ├── KinesisLoggerConfigurationExtensions.cs
│ │ └── Sinks
│ │ │ ├── DurableKinesisSink.cs
│ │ │ ├── HttpLogShipper.cs
│ │ │ ├── KinesisApi.cs
│ │ │ ├── KinesisSink.cs
│ │ │ ├── KinesisSinkState.cs
│ │ │ └── KinesisStreamSinkOptions.cs
│ └── assets
│ │ └── Serilog.snk
└── Serilog.Sinks.AmazonKinesis.nuspec
└── tests
└── Serilog.Sinks.Amazon.Kinesis.Tests
├── HttpLogShipperTests
├── HttpLogShipperBaseTestBase.cs
├── LogSendErrorEventTests.cs
├── LogShipperSUT.cs
├── WhenBookmarkCannotBeCreated.cs
├── WhenLogFilesFound.cs
└── WhenNoLogFilesFound.cs
├── Integration
├── DurableKinesisFirehoseSinkTests
│ ├── DurableKinesisFirehoseSinkTestBase.cs
│ └── WhenLogAndWaitEnough.cs
└── DurableKinesisSinkTests
│ ├── DurableKinesisSinkTestBase.cs
│ └── WhenLogAndWaitEnough.cs
├── LogReaderTests
├── LogReaderTestBase.cs
├── WhenLogFileExists.cs
└── WhenNoLogFileExists.cs
├── LogShipperFileManagerTests
├── FileTestBase.cs
├── WhenGetFileLengthExclusiveAccess.cs
└── WhenLockAndDeleteFile.cs
├── PersistedBookmarkTests
├── PersistedBookmarkTestBase.cs
├── WhenBookmarkExistsAndContainsGarbage.cs
├── WhenFileNameAndPositionAreUpdated.cs
├── WhenNoBookmarkExists.cs
└── WhenPositionIsUpdated.cs
├── Properties
└── AssemblyInfo.cs
├── Serilog.Sinks.Amazon.Kinesis.Tests.csproj
├── Serilog.snk
├── TestFixtureCategory.cs
├── app.config
└── packages.config
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig
2 | # 4 space indentation
3 | [*.cs]
4 | indent_style = space
5 | indent_size = 4
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 |
3 | * text=auto
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Dd]ebugPublic/
12 | [Rr]elease/
13 | [Rr]eleases/
14 | x64/
15 | x86/
16 | build/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 |
21 | # Roslyn cache directories
22 | *.ide/
23 |
24 | # MSTest test Results
25 | [Tt]est[Rr]esult*/
26 | [Bb]uild[Ll]og.*
27 |
28 | #NUNIT
29 | *.VisualState.xml
30 | TestResult.xml
31 |
32 | # Build Results of an ATL Project
33 | [Dd]ebugPS/
34 | [Rr]eleasePS/
35 | dlldata.c
36 |
37 | *_i.c
38 | *_p.c
39 | *_i.h
40 | *.ilk
41 | *.meta
42 | *.obj
43 | *.pch
44 | *.pdb
45 | *.pgc
46 | *.pgd
47 | *.rsp
48 | *.sbr
49 | *.tlb
50 | *.tli
51 | *.tlh
52 | *.tmp
53 | *.tmp_proj
54 | *.log
55 | *.vspscc
56 | *.vssscc
57 | .builds
58 | *.pidb
59 | *.svclog
60 | *.scc
61 |
62 | # Chutzpah Test files
63 | _Chutzpah*
64 |
65 | # Visual C++ cache files
66 | ipch/
67 | *.aps
68 | *.ncb
69 | *.opensdf
70 | *.sdf
71 | *.cachefile
72 |
73 | # Visual Studio profiler
74 | *.psess
75 | *.vsp
76 | *.vspx
77 |
78 | # TFS 2012 Local Workspace
79 | $tf/
80 |
81 | # Guidance Automation Toolkit
82 | *.gpState
83 |
84 | # ReSharper is a .NET coding add-in
85 | _ReSharper*/
86 | *.[Rr]e[Ss]harper
87 | *.DotSettings.user
88 |
89 | # JustCode is a .NET coding addin-in
90 | .JustCode
91 |
92 | # TeamCity is a build add-in
93 | _TeamCity*
94 |
95 | # DotCover is a Code Coverage Tool
96 | *.dotCover
97 |
98 | # NCrunch
99 | _NCrunch_*
100 | .*crunch*.local.xml
101 |
102 | # MightyMoose
103 | *.mm.*
104 | AutoTest.Net/
105 |
106 | # Web workbench (sass)
107 | .sass-cache/
108 |
109 | # Installshield output folder
110 | [Ee]xpress/
111 |
112 | # DocProject is a documentation generator add-in
113 | DocProject/buildhelp/
114 | DocProject/Help/*.HxT
115 | DocProject/Help/*.HxC
116 | DocProject/Help/*.hhc
117 | DocProject/Help/*.hhk
118 | DocProject/Help/*.hhp
119 | DocProject/Help/Html2
120 | DocProject/Help/html
121 |
122 | # Click-Once directory
123 | publish/
124 |
125 | # Publish Web Output
126 | *.[Pp]ublish.xml
127 | *.azurePubxml
128 | # TODO: Comment the next line if you want to checkin your web deploy settings
129 | # but database connection strings (with potential passwords) will be unencrypted
130 | *.pubxml
131 | *.publishproj
132 |
133 | # NuGet Packages
134 | *.nupkg
135 | # The packages folder can be ignored because of Package Restore
136 | **/packages/*
137 | # except build/, which is used as an MSBuild target.
138 | !**/packages/build/
139 | # If using the old MSBuild-Integrated Package Restore, uncomment this:
140 | #!**/packages/repositories.config
141 |
142 | # Windows Azure Build Output
143 | csx/
144 | *.build.csdef
145 |
146 | # Windows Store app package directory
147 | AppPackages/
148 |
149 | # Others
150 | sql/
151 | *.Cache
152 | ClientBin/
153 | [Ss]tyle[Cc]op.*
154 | ~$*
155 | *~
156 | *.dbmdl
157 | *.dbproj.schemaview
158 | *.pfx
159 | *.publishsettings
160 | node_modules/
161 | serilog-sinks-mongodb.sln.GhostDoc.xml
162 |
163 | # RIA/Silverlight projects
164 | Generated_Code/
165 |
166 | # Backup & report files from converting an old project file
167 | # to a newer Visual Studio version. Backup files are not needed,
168 | # because we have git ;-)
169 | _UpgradeReport_Files/
170 | Backup*/
171 | UpgradeLog*.XML
172 | UpgradeLog*.htm
173 |
174 | # SQL Server files
175 | *.mdf
176 | *.ldf
177 |
178 | # Business Intelligence projects
179 | *.rdl.data
180 | *.bim.layout
181 | *.bim_*.settings
182 |
183 | # Microsoft Fakes
184 | FakesAssemblies/
185 |
186 | # NCrunch
187 | _NCrunch_*
188 | .*crunch*.local.xml
189 | *.ncrunch*
190 |
191 | .vs/
192 |
--------------------------------------------------------------------------------
/Build.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | [String] $majorMinor = "2.1", # 2.1
3 | [String] $patch = "0", # $env:APPVEYOR_BUILD_VERSION
4 | [String] $customLogger = "", # C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll
5 | [Switch] $notouch
6 | )
7 |
8 | function Set-AssemblyVersions($informational, $assembly)
9 | {
10 | (Get-Content assets/CommonAssemblyInfo.cs) |
11 | ForEach-Object { $_ -replace """0.0.0.0""", """$assembly""" } |
12 | ForEach-Object { $_ -replace """0.0.0""", """$informational""" } |
13 | ForEach-Object { $_ -replace """0.1.1.1""", """$($informational).0""" } |
14 | Set-Content assets/CommonAssemblyInfo.cs
15 | }
16 |
17 | function Install-NuGetPackages($solution)
18 | {
19 | nuget restore "$solution"
20 | }
21 |
22 | function Invoke-MSBuild($solution, $customLogger)
23 | {
24 | if ($customLogger)
25 | {
26 | msbuild "$solution" /verbosity:minimal /p:Configuration=Release /logger:"$customLogger"
27 | }
28 | else
29 | {
30 | msbuild "$solution" /verbosity:minimal /p:Configuration=Release
31 | }
32 | }
33 |
34 | function Invoke-NuGetPackSpec($nuspec, $version)
35 | {
36 | nuget pack $nuspec -Version $version
37 | }
38 |
39 | function Invoke-Build($majorMinor, $patch, $customLogger, $notouch)
40 | {
41 | $project = "serilog-sinks-amazonkinesis"
42 |
43 | $solution = "$project.sln"
44 | $package="$majorMinor.$patch"
45 |
46 | Write-Output "Building $project $package"
47 |
48 | if (-not $notouch)
49 | {
50 | $assembly = "$majorMinor.0.0"
51 |
52 | Write-Output "Assembly version will be set to $assembly"
53 | Set-AssemblyVersions $package $assembly
54 | }
55 |
56 | Install-NuGetPackages $solution
57 |
58 | Invoke-MSBuild $solution $customLogger
59 |
60 | Invoke-NuGetPackSpec "src/Serilog.Sinks.AmazonKinesis.nuspec" $package
61 | }
62 |
63 | $ErrorActionPreference = "Stop"
64 | Invoke-Build $majorMinor $patch $customLogger $notouch
65 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Serilog.Sinks.AmazonKinesis [](https://ci.appveyor.com/project/serilog/serilog-sinks-amazonkinesis/branch/master)
2 |
3 | A Serilog sink that writes events as documents to [Amazon Kinesis](http://aws.amazon.com/kinesis/).
4 |
5 | ## Getting started
6 |
7 | To get started install the _Serilog.Sinks.AmazonKinesis_ package from Visual Studio's _NuGet_ console:
8 |
9 | ```powershell
10 | PM> Install-Package Serilog.Sinks.AmazonKinesis
11 | ```
12 |
13 | Point the logger to Kinesis:
14 |
15 | ```csharp
16 | const string streamName = "firehose";
17 | const int shardCount = 2;
18 |
19 | SelfLog.Out = Console.Out;
20 |
21 | var client = AWSClientFactory.CreateAmazonKinesisClient();
22 |
23 | var streamOk = KinesisApi.CreateAndWaitForStreamToBecomeAvailable(
24 | kinesisClient: client,
25 | streamName: streamName,
26 | shardCount: shardCount
27 | );
28 |
29 | var loggerConfig = new LoggerConfiguration()
30 | .WriteTo.ColoredConsole()
31 | .MinimumLevel.Debug();
32 |
33 | if (streamOk)
34 | {
35 | loggerConfig.WriteTo.AmazonKinesis(
36 | kinesisClient: client,
37 | streamName: streamName,
38 | period: TimeSpan.FromSeconds(2),
39 | bufferLogShippingInterval: TimeSpan.FromSeconds(5),
40 | bufferBaseFilename: "./logs/kinesis-buffer"
41 | );
42 | }
43 |
44 | Log.Logger = loggerConfig.CreateLogger();
45 |
46 | ```
47 |
48 | And use the Serilog logging methods to associate named properties with log events:
49 |
50 | ```csharp
51 | Log.Error("Failed to log on user {ContactId}", contactId);
52 | ```
53 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image: Visual Studio 2017
3 | environment:
4 | VisualStudioVersion: 15.0
5 | build_script:
6 | - ps: >-
7 | $version = "2.2"
8 |
9 | $env:VisualStudioVersion = 15.0
10 |
11 | ./Build.ps1 -majorMinor "$version" -patch "$env:APPVEYOR_BUILD_VERSION" -customLogger "C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
12 |
13 | artifacts:
14 | - path: Serilog.*.nupkg
15 | deploy:
16 | - provider: NuGet
17 | api_key:
18 | secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8
19 | on:
20 | branch: master
21 |
--------------------------------------------------------------------------------
/assets/CommonAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | [assembly: AssemblyVersion("0.0.0.0")]
4 | [assembly: AssemblyFileVersion("0.0.0.0")]
5 | [assembly: AssemblyInformationalVersion("0.0.0")]
6 |
--------------------------------------------------------------------------------
/assets/Serilog.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serilog-archive/serilog-sinks-amazonkinesis/555981e1135a9ca804b843b8faf5a74cc7755412/assets/Serilog.snk
--------------------------------------------------------------------------------
/sample/AmazonKinesisSample/AmazonKinesisSample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6672FF6B-5243-4588-986A-EEA9C0BD6C0B}
8 | Exe
9 | Properties
10 | AmazonKinesisSample
11 | AmazonKinesisSample
12 | v4.5
13 | 512
14 | true
15 |
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 | true
28 |
29 |
30 |
31 |
32 | AnyCPU
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE
37 | prompt
38 | 4
39 |
40 |
41 |
42 | ..\..\packages\AWSSDK.Core.3.1.5.3\lib\net45\AWSSDK.Core.dll
43 | True
44 |
45 |
46 | ..\..\packages\AWSSDK.Kinesis.3.1.3.0\lib\net45\AWSSDK.Kinesis.dll
47 | True
48 |
49 |
50 | ..\..\packages\Serilog.2.0.0\lib\net45\Serilog.dll
51 | True
52 |
53 |
54 | ..\..\packages\Serilog.Sinks.ColoredConsole.2.0.0\lib\net45\Serilog.Sinks.ColoredConsole.dll
55 | True
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | {9cddc147-93bb-47dc-899c-b41384d7ae23}
72 | Serilog.Sinks.Amazon.Kinesis
73 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
--------------------------------------------------------------------------------
/sample/AmazonKinesisSample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/sample/AmazonKinesisSample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using Amazon.Kinesis;
5 | using Serilog;
6 | using Serilog.Debugging;
7 | using Serilog.Sinks.Amazon.Kinesis;
8 | using Serilog.Sinks.Amazon.Kinesis.Stream;
9 | using Serilog.Sinks.Amazon.Kinesis.Stream.Sinks;
10 |
11 | namespace AmazonKinesisSample
12 | {
13 | public class Position
14 | {
15 | public double Lat { get; set; }
16 | public double Long { get; set; }
17 | }
18 |
19 | public class Program
20 | {
21 | const string streamName = "firehose";
22 | const int shardCount = 1;
23 |
24 | public static void Main()
25 | {
26 | SelfLog.Enable(Console.Out);
27 |
28 | var client = new AmazonKinesisClient();
29 |
30 | var streamOk = KinesisApi.CreateAndWaitForStreamToBecomeAvailable(
31 | kinesisClient: client,
32 | streamName: streamName,
33 | shardCount: shardCount
34 | );
35 |
36 | var loggerConfig = new LoggerConfiguration()
37 | .WriteTo.ColoredConsole()
38 | .MinimumLevel.Debug();
39 |
40 | if (streamOk)
41 | {
42 | loggerConfig.WriteTo.AmazonKinesis(
43 | kinesisClient: client,
44 | streamName: streamName,
45 | period: TimeSpan.FromSeconds(2),
46 | bufferBaseFilename: "./logs/kinesis-buffer",
47 | onLogSendError: OnLogSendError
48 | );
49 | }
50 |
51 | Log.Logger = loggerConfig.CreateLogger();
52 |
53 | #if false
54 |
55 | for (var i = 0; i < 50; i++)
56 | {
57 | for (int j = 0; j < 500; j++)
58 | {
59 | Thread.Sleep(1);
60 | Log.Debug("Count: {i} {j}", i, j);
61 | }
62 |
63 | Console.Write(".");
64 | }
65 |
66 | #endif
67 |
68 | LogStuff();
69 |
70 | Log.Fatal("That's all folks - and all done using {WorkingSet} bytes of RAM", Environment.WorkingSet);
71 | Console.ReadKey();
72 | }
73 |
74 | static void OnLogSendError(object sender, LogSendErrorEventArgs logSendErrorEventArgs) {
75 | Console.WriteLine("Error: {0}", logSendErrorEventArgs.Message);
76 | }
77 |
78 | private static void LogStuff()
79 | {
80 | Log.Verbose("This app, {ExeName}, demonstrates the basics of using Serilog", "Demo.exe");
81 |
82 | ProcessInput(new Position { Lat = 24.7, Long = 132.2 });
83 | ProcessInput(new Position { Lat = 24.71, Long = 132.15 });
84 | ProcessInput(new Position { Lat = 24.72, Long = 132.2 });
85 |
86 |
87 | Log.Information("Just biting {Fruit} number {Count}", "Apple", 12);
88 | Log.ForContext().Information("Just biting {Fruit} number {Count:0000}", "Apple", 12);
89 |
90 | // ReSharper disable CoVariantArrayConversion
91 | Log.Information("I've eaten {Dinner}", new[] { "potatoes", "peas" });
92 | // ReSharper restore CoVariantArrayConversion
93 |
94 | Log.Information("I sat at {@Chair}", new { Back = "straight", Legs = new[] { 1, 2, 3, 4 } });
95 | Log.Information("I sat at {Chair}", new { Back = "straight", Legs = new[] { 1, 2, 3, 4 } });
96 |
97 | const int failureCount = 3;
98 | Log.Warning("Exception coming up because of {FailureCount} failures...", failureCount);
99 |
100 | try
101 | {
102 | DoBad();
103 | }
104 | catch (Exception ex)
105 | {
106 | Log.Error(ex, "There's those {FailureCount} failures", failureCount);
107 | }
108 |
109 | Log.Verbose("This app, {ExeName}, demonstrates the basics of using Serilog", "Demo.exe");
110 |
111 | try
112 | {
113 | DoBad();
114 | }
115 | catch (Exception ex)
116 | {
117 | Log.Error(ex, "We did some bad work here.");
118 | }
119 |
120 | var result = 0;
121 | var divideBy = 0;
122 | try
123 | {
124 | result = 10 / divideBy;
125 | }
126 | catch (Exception e)
127 | {
128 | Log.Error(e, "Unable to divide by {divideBy}", divideBy);
129 | }
130 | }
131 |
132 | static void DoBad()
133 | {
134 | throw new InvalidOperationException("Everything's broken!");
135 | }
136 |
137 | static readonly Random Rng = new Random();
138 |
139 | static void ProcessInput(Position sensorInput)
140 | {
141 | var sw = new Stopwatch();
142 | sw.Start();
143 | Log.Debug("Processing some input on {MachineName}...", Environment.MachineName);
144 | Thread.Sleep(Rng.Next(0, 100));
145 | sw.Stop();
146 |
147 | Log.Information("Processed {@SensorInput} in {Time:000} ms", sensorInput, sw.ElapsedMilliseconds);
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/sample/AmazonKinesisSample/Properties/CommonAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | [assembly: AssemblyVersion("1.0.0.0")]
4 | [assembly: AssemblyFileVersion("1.1.1.1")]
5 | [assembly: AssemblyInformationalVersion("1.0.0")]
6 |
--------------------------------------------------------------------------------
/sample/AmazonKinesisSample/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/serilog-sinks-amazonkinesis.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26730.16
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5E1-DEB9-4A04-8BAB-24EC7240ADAF}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | Build.ps1 = Build.ps1
12 | assets\CommonAssemblyInfo.cs = assets\CommonAssemblyInfo.cs
13 | README.md = README.md
14 | assets\Serilog.snk = assets\Serilog.snk
15 | EndProjectSection
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{543E1315-71DE-40CE-A179-4B27B7A64125}"
18 | EndProject
19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Amazon.Kinesis", "src\Serilog.Sinks.Amazon.Kinesis\Serilog.Sinks.Amazon.Kinesis.csproj", "{9CDDC147-93BB-47DC-899C-B41384D7AE23}"
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1310E4CF-DE62-4661-9E62-D1CE08A913B2}"
22 | EndProject
23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Amazon.Kinesis.Tests", "tests\Serilog.Sinks.Amazon.Kinesis.Tests\Serilog.Sinks.Amazon.Kinesis.Tests.csproj", "{B26D2F37-234B-438D-84BE-83BF9E0A255C}"
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Release|Any CPU = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {9CDDC147-93BB-47DC-899C-B41384D7AE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {9CDDC147-93BB-47DC-899C-B41384D7AE23}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {9CDDC147-93BB-47DC-899C-B41384D7AE23}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {9CDDC147-93BB-47DC-899C-B41384D7AE23}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {B26D2F37-234B-438D-84BE-83BF9E0A255C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {B26D2F37-234B-438D-84BE-83BF9E0A255C}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {B26D2F37-234B-438D-84BE-83BF9E0A255C}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {B26D2F37-234B-438D-84BE-83BF9E0A255C}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(SolutionProperties) = preSolution
41 | HideSolutionNode = FALSE
42 | EndGlobalSection
43 | GlobalSection(NestedProjects) = preSolution
44 | {9CDDC147-93BB-47DC-899C-B41384D7AE23} = {037440DE-440B-4129-9F7A-09B42D00397E}
45 | {B26D2F37-234B-438D-84BE-83BF9E0A255C} = {1310E4CF-DE62-4661-9E62-D1CE08A913B2}
46 | EndGlobalSection
47 | GlobalSection(ExtensibilityGlobals) = postSolution
48 | SolutionGuid = {51B588B3-2BD9-4002-98B8-6F4ED8C22122}
49 | EndGlobalSection
50 | EndGlobal
51 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/CustomJsonFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Serilog.Formatting.Json;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Common
6 | {
7 | ///
8 | /// wut
9 | ///
10 | public class CustomJsonFormatter : JsonFormatter
11 | {
12 | ///
13 | /// wut
14 | ///
15 | public CustomJsonFormatter(bool omitEnclosingObject = false, string closingDelimiter = null, bool renderMessage = false, IFormatProvider formatProvider = null)
16 | : base(omitEnclosingObject, closingDelimiter, renderMessage, formatProvider)
17 | {
18 | }
19 |
20 | ///
21 | /// wut
22 | ///
23 | protected override void WriteTimestamp(DateTimeOffset timestamp, ref string precedingDelimiter, TextWriter output)
24 | {
25 | output.Write(precedingDelimiter);
26 | output.Write("\"");
27 | output.Write("Timestamp");
28 | output.Write("\":");
29 | WriteOffset(timestamp, output);
30 | precedingDelimiter = ",";
31 | }
32 |
33 | private static void WriteOffset(DateTimeOffset value, TextWriter output)
34 | {
35 | output.Write("\"");
36 | output.Write(value.UtcDateTime.ToString("o"));
37 | output.Write("\"");
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/HttpLogShipperBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using Serilog.Debugging;
6 |
7 | namespace Serilog.Sinks.Amazon.Kinesis.Common
8 | {
9 | abstract class HttpLogShipperBase
10 | {
11 | private readonly ILogReaderFactory _logReaderFactory;
12 | private readonly IPersistedBookmarkFactory _persistedBookmarkFactory;
13 | private readonly ILogShipperFileManager _fileManager;
14 |
15 | protected readonly int _batchPostingLimit;
16 | protected readonly string _bookmarkFilename;
17 | protected readonly string _candidateSearchPath;
18 | protected readonly string _logFolder;
19 | protected readonly string _streamName;
20 |
21 | protected HttpLogShipperBase(
22 | ILogShipperOptions options,
23 | ILogReaderFactory logReaderFactory,
24 | IPersistedBookmarkFactory persistedBookmarkFactory,
25 | ILogShipperFileManager fileManager
26 | )
27 | {
28 | if (options == null) throw new ArgumentNullException(nameof(options));
29 | if (logReaderFactory == null) throw new ArgumentNullException(nameof(logReaderFactory));
30 | if (persistedBookmarkFactory == null) throw new ArgumentNullException(nameof(persistedBookmarkFactory));
31 | if (fileManager == null) throw new ArgumentNullException(nameof(fileManager));
32 |
33 | _logReaderFactory = logReaderFactory;
34 | _persistedBookmarkFactory = persistedBookmarkFactory;
35 | _fileManager = fileManager;
36 |
37 | _batchPostingLimit = options.BatchPostingLimit;
38 | _streamName = options.StreamName;
39 | _bookmarkFilename = Path.GetFullPath(options.BufferBaseFilename + ".bookmark");
40 | _logFolder = Path.GetDirectoryName(_bookmarkFilename);
41 | _candidateSearchPath = Path.GetFileName(options.BufferBaseFilename) + "*.json";
42 |
43 | }
44 |
45 |
46 | protected abstract TRecord PrepareRecord(MemoryStream stream);
47 | protected abstract TResponse SendRecords(List records, out bool successful);
48 | protected abstract void HandleError(TResponse response, int originalRecordCount);
49 | public event EventHandler LogSendError;
50 |
51 | protected void OnLogSendError(LogSendErrorEventArgs e)
52 | {
53 | var handler = LogSendError;
54 | if (handler != null)
55 | {
56 | handler(this, e);
57 | }
58 | }
59 |
60 | private IPersistedBookmark TryCreateBookmark()
61 | {
62 | try
63 | {
64 | return _persistedBookmarkFactory.Create(_bookmarkFilename);
65 | }
66 | catch (IOException ex)
67 | {
68 | SelfLog.WriteLine("Bookmark cannot be opened.", ex);
69 | return null;
70 | }
71 | }
72 |
73 | protected void ShipLogs()
74 | {
75 | try
76 | {
77 | // Locking the bookmark ensures that though there may be multiple instances of this
78 | // class running, only one will ship logs at a time.
79 |
80 | using (var bookmark = TryCreateBookmark())
81 | {
82 | if (bookmark == null)
83 | return;
84 |
85 | ShipLogs(bookmark);
86 | }
87 | }
88 | catch (IOException ex)
89 | {
90 | SelfLog.WriteLine("Error shipping logs", ex);
91 | }
92 | catch (Exception ex)
93 | {
94 | SelfLog.WriteLine("Error shipping logs", ex);
95 | OnLogSendError(new LogSendErrorEventArgs(string.Format("Error in shipping logs to '{0}' stream", _streamName), ex));
96 | }
97 | }
98 |
99 | private void ShipLogs(IPersistedBookmark bookmark)
100 | {
101 | do
102 | {
103 | string currentFilePath = bookmark.FileName;
104 |
105 | SelfLog.WriteLine("Bookmark is currently at offset {0} in '{1}'", bookmark.Position, currentFilePath);
106 |
107 | var fileSet = GetFileSet();
108 |
109 | if (currentFilePath == null)
110 | {
111 | currentFilePath = fileSet.FirstOrDefault();
112 | SelfLog.WriteLine("New log file is {0}", currentFilePath);
113 | bookmark.UpdateFileNameAndPosition(currentFilePath, 0L);
114 | }
115 | else if (fileSet.All(f => CompareFileNames(f, currentFilePath) != 0))
116 | {
117 | currentFilePath = fileSet.FirstOrDefault(f => CompareFileNames(f, currentFilePath) >= 0);
118 | SelfLog.WriteLine("New log file is {0}", currentFilePath);
119 | bookmark.UpdateFileNameAndPosition(currentFilePath, 0L);
120 | }
121 |
122 | // delete all previous files - we will not read them anyway
123 | if (currentFilePath == null)
124 | {
125 | foreach (var fileToDelete in fileSet)
126 | {
127 | TryDeleteFile(fileToDelete);
128 | }
129 | }
130 | else
131 | {
132 | foreach (var fileToDelete in fileSet.TakeWhile(f => CompareFileNames(f, currentFilePath) < 0))
133 | {
134 | TryDeleteFile(fileToDelete);
135 | }
136 | }
137 |
138 | if (currentFilePath == null)
139 | {
140 | SelfLog.WriteLine("No log file is found. Nothing to do.");
141 | break;
142 | }
143 |
144 | // now we are interested in current file and all after it.
145 | fileSet =
146 | fileSet.SkipWhile(f => CompareFileNames(f, currentFilePath) < 0)
147 | .ToArray();
148 |
149 | var initialPosition = bookmark.Position;
150 | List records;
151 | do
152 | {
153 | var batch = ReadRecordBatch(currentFilePath, bookmark.Position, _batchPostingLimit);
154 | records = batch.Item2;
155 | if (records.Count > 0)
156 | {
157 | bool successful;
158 | var response = SendRecords(records, out successful);
159 |
160 | if (!successful)
161 | {
162 | SelfLog.WriteLine("SendRecords failed for {0} records.", records.Count);
163 | HandleError(response, records.Count);
164 | return;
165 | }
166 | }
167 |
168 | var newPosition = batch.Item1;
169 | if (initialPosition < newPosition)
170 | {
171 | SelfLog.WriteLine("Advancing bookmark from {0} to {1} on {2}", initialPosition, newPosition, currentFilePath);
172 | bookmark.UpdatePosition(newPosition);
173 | }
174 | else if (initialPosition > newPosition)
175 | {
176 | newPosition = 0;
177 | SelfLog.WriteLine("File {2} has been truncated or re-created, bookmark reset from {0} to {1}", initialPosition, newPosition, currentFilePath);
178 | bookmark.UpdatePosition(newPosition);
179 | }
180 |
181 | } while (records.Count >= _batchPostingLimit);
182 |
183 | if (initialPosition == bookmark.Position)
184 | {
185 | SelfLog.WriteLine("Found no records to process");
186 |
187 | // Only advance the bookmark if there is next file in the queue
188 | // and no other process has the current file locked, and its length is as we found it.
189 |
190 | if (fileSet.Length > 1)
191 | {
192 | SelfLog.WriteLine("BufferedFilesCount: {0}; checking if can advance to the next file", fileSet.Length);
193 | var weAreAtEndOfTheFileAndItIsNotLockedByAnotherThread = WeAreAtEndOfTheFileAndItIsNotLockedByAnotherThread(currentFilePath, bookmark.Position);
194 | if (weAreAtEndOfTheFileAndItIsNotLockedByAnotherThread)
195 | {
196 | SelfLog.WriteLine("Advancing bookmark from '{0}' to '{1}'", currentFilePath, fileSet[1]);
197 | bookmark.UpdateFileNameAndPosition(fileSet[1], 0);
198 | }
199 | else
200 | {
201 | break;
202 | }
203 | }
204 | else
205 | {
206 | SelfLog.WriteLine("This is a single log file, and we are in the end of it. Nothing to do.");
207 | break;
208 | }
209 | }
210 | } while (true);
211 | }
212 |
213 | private Tuple> ReadRecordBatch(string currentFilePath, long position, int maxRecords)
214 | {
215 | var records = new List(maxRecords);
216 | long positionSent;
217 | using (var reader = _logReaderFactory.Create(currentFilePath, position))
218 | {
219 | do
220 | {
221 | var stream = reader.ReadLine();
222 | if (stream.Length == 0)
223 | {
224 | break;
225 | }
226 | records.Add(PrepareRecord(stream));
227 | } while (records.Count < maxRecords);
228 |
229 | positionSent = reader.Position;
230 | }
231 |
232 | return Tuple.Create(positionSent, records);
233 | }
234 |
235 | private bool TryDeleteFile(string fileToDelete)
236 | {
237 | try
238 | {
239 | _fileManager.LockAndDeleteFile(fileToDelete);
240 | SelfLog.WriteLine("Log file deleted: {0}", fileToDelete);
241 | return true;
242 | }
243 | catch (Exception ex)
244 | {
245 | SelfLog.WriteLine("Exception deleting file: {0}", ex, fileToDelete);
246 | return false;
247 | }
248 | }
249 |
250 | private bool WeAreAtEndOfTheFileAndItIsNotLockedByAnotherThread(string file, long nextLineBeginsAtOffset)
251 | {
252 | try
253 | {
254 | return _fileManager.GetFileLengthExclusiveAccess(file) <= nextLineBeginsAtOffset;
255 | }
256 | catch (IOException ex)
257 | {
258 | SelfLog.WriteLine("Swallowed I/O exception while testing locked status of {0}", ex, file);
259 | }
260 | catch (Exception ex)
261 | {
262 | SelfLog.WriteLine("Unexpected exception while testing locked status of {0}", ex, file);
263 | }
264 |
265 | return false;
266 | }
267 |
268 | private string[] GetFileSet()
269 | {
270 | var fileSet = _fileManager.GetFiles(_logFolder, _candidateSearchPath)
271 | .OrderBy(n => n, StringComparer.OrdinalIgnoreCase)
272 | .ToArray();
273 |
274 | SelfLog.WriteLine("FileSet contains: {0}", string.Join(";", fileSet));
275 | return fileSet;
276 | }
277 |
278 | private static int CompareFileNames(string fileName1, string fileName2)
279 | {
280 | return string.Compare(fileName1, fileName2, StringComparison.OrdinalIgnoreCase);
281 | }
282 | }
283 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/ILogReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Serilog.Sinks.Amazon.Kinesis.Common
4 | {
5 | interface ILogReader : IDisposable
6 | {
7 | ///
8 | /// Read next line until CR or LF character.
9 | /// Ignoring leading CR/LF (no empty lines returned),
10 | ///
11 | /// Stream with line content, or empty stream in case of EOF
12 | System.IO.MemoryStream ReadLine();
13 | long Position { get; }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/ILogReaderFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | interface ILogReaderFactory
4 | {
5 | ILogReader Create(string fileName, long position);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/ILogShipperFileManager.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | interface ILogShipperFileManager
4 | {
5 | long GetFileLengthExclusiveAccess(string filePath);
6 | string[] GetFiles(string path, string searchPattern);
7 | void LockAndDeleteFile(string filePath);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/ILogShipperOptions.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | ///
4 | /// Options usd by LogShipperBase
5 | ///
6 | public interface ILogShipperOptions
7 | {
8 | ///
9 | /// The maximum number of events to post in a single batch.
10 | ///
11 | int BatchPostingLimit { get; }
12 |
13 | ///
14 | /// Path and base file name of log files.
15 | ///
16 | string BufferBaseFilename { get; }
17 |
18 | ///
19 | /// Name of output stream/sink
20 | ///
21 | string StreamName { get; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/IPersistedBookmark.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Serilog.Sinks.Amazon.Kinesis.Common
4 | {
5 | interface IPersistedBookmark : IDisposable
6 | {
7 | long Position { get; }
8 | string FileName { get; }
9 | void UpdatePosition(long position);
10 | void UpdateFileNameAndPosition(string fileName, long position);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/IPersistedBookmarkFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | interface IPersistedBookmarkFactory
4 | {
5 | IPersistedBookmark Create(string bookmarkFileName);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/KinesisSinkOptionsBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Serilog.Events;
3 | using Serilog.Formatting;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Common
6 | {
7 | ///
8 | ///
9 | ///
10 | public abstract class KinesisSinkOptionsBase : ILogShipperOptions
11 | {
12 | protected KinesisSinkOptionsBase(string streamName)
13 | {
14 | if (streamName == null) throw new ArgumentNullException("streamName");
15 |
16 | StreamName = streamName;
17 | Period = DefaultPeriod;
18 | BatchPostingLimit = DefaultBatchPostingLimit;
19 | }
20 |
21 | ///
22 | /// Optional path to directory that can be used as a log shipping buffer for increasing the reliabilty of the log forwarding.
23 | ///
24 | public string BufferBaseFilename { get; set; }
25 |
26 | ///
27 | /// The default time to wait between checking for event batches. Defaults to 2 seconds.
28 | ///
29 | public static readonly TimeSpan DefaultPeriod = TimeSpan.FromSeconds(2);
30 |
31 | ///
32 | /// The default maximum number of events to post in a single batch. Defaults to 500.
33 | ///
34 | public static readonly int DefaultBatchPostingLimit = 500;
35 |
36 | ///
37 | /// The default stream name to use for the log events.
38 | ///
39 | public string StreamName { get; set; }
40 |
41 | ///
42 | /// The maximum number of events to post in a single batch. Defaults to 500.
43 | ///
44 | public int BatchPostingLimit { get; set; }
45 |
46 | ///
47 | /// The time to wait between sending event batches.
48 | ///
49 | public TimeSpan Period { get; set; }
50 |
51 | ///
52 | /// Supplies culture-specific formatting information, or null.
53 | ///
54 | public IFormatProvider FormatProvider { get; set; }
55 |
56 | ///
57 | /// The minimum log event level required in order to write an event to the sink.
58 | ///
59 | public LogEventLevel? MinimumLogEventLevel { get; set; }
60 |
61 | ///
62 | /// The maximum size, in bytes, to which the buffer log file for a specific date will be allowed to grow.
63 | /// By default no limit will be applied.
64 | ///
65 | public long? BufferFileSizeLimitBytes { get; set; }
66 |
67 | ///
68 | /// Customizes the formatter used when converting the log events into the durable sink.
69 | /// Please note that the formatter output must be valid JSON.
70 | ///
71 | public ITextFormatter CustomDurableFormatter { get; set; }
72 |
73 | ///
74 | /// An eventhandler which will be invoked whenever there is an error in log sending.
75 | ///
76 | public EventHandler OnLogSendError { get; set; }
77 |
78 | ///
79 | /// Allow the log files to be shared by multiple processes. The default is false.
80 | ///
81 | public bool Shared { get; set; }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/KinesisSinkStateBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Serilog.Formatting;
3 |
4 | namespace Serilog.Sinks.Amazon.Kinesis.Common
5 | {
6 | abstract class KinesisSinkStateBase
7 | {
8 | private readonly KinesisSinkOptionsBase _options;
9 | private readonly ITextFormatter _formatter;
10 | private readonly ITextFormatter _durableFormatter;
11 | protected KinesisSinkStateBase(KinesisSinkOptionsBase options)
12 | {
13 | if (options == null) throw new ArgumentNullException("options");
14 | _options = options;
15 | if (string.IsNullOrWhiteSpace(options.StreamName)) throw new ArgumentException("options.StreamName");
16 | _formatter = options.CustomDurableFormatter ?? new CustomJsonFormatter(
17 | omitEnclosingObject: false,
18 | closingDelimiter: string.Empty,
19 | renderMessage: true,
20 | formatProvider: options.FormatProvider
21 | );
22 |
23 | _durableFormatter = options.CustomDurableFormatter ?? new CustomJsonFormatter(
24 | omitEnclosingObject: false,
25 | closingDelimiter: Environment.NewLine,
26 | renderMessage: true,
27 | formatProvider: options.FormatProvider
28 | );
29 |
30 | }
31 |
32 | public KinesisSinkOptionsBase Options { get { return _options; } }
33 | public ITextFormatter Formatter { get { return _formatter; } }
34 | public ITextFormatter DurableFormatter { get { return _durableFormatter; } }
35 |
36 |
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/LogReader.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Text;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Common
6 | {
7 | sealed class LogReader : ILogReader
8 | {
9 | private readonly System.IO.Stream _logStream;
10 |
11 | private LogReader(System.IO.Stream logStream)
12 | {
13 | _logStream = logStream;
14 | }
15 |
16 | public static LogReader Create(string fileName, long position)
17 | {
18 | var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 128, FileOptions.SequentialScan);
19 | var length = stream.Length;
20 | if (position > length)
21 | {
22 | position = length;
23 | }
24 | stream.Seek(position, SeekOrigin.Begin);
25 | return new LogReader(stream);
26 | }
27 |
28 | public System.IO.MemoryStream ReadLine()
29 | {
30 | // check and skip BOM in the beginning of file
31 | if (_logStream.Position == 0)
32 | {
33 | var bom = Encoding.UTF8.GetPreamble();
34 | var bomBuffer = new byte[bom.Length];
35 | if (bomBuffer.Length != _logStream.Read(bomBuffer, 0, bomBuffer.Length)
36 | || !bomBuffer.SequenceEqual(bom))
37 | {
38 | _logStream.Position = 0;
39 | }
40 | }
41 |
42 | var result = new MemoryStream(256);
43 | int thisByte;
44 | while (0 <= (thisByte = _logStream.ReadByte()))
45 | {
46 | if (result.Length == 0
47 | && thisByte < 128 // main ASCII set control or whitespace
48 | && (char.IsControl((char)thisByte) || char.IsWhiteSpace((char)thisByte))
49 | )
50 | {
51 | continue; // Ignore CR/LF and spaces in the beginning of the line
52 | }
53 | else if (thisByte == 10 || thisByte == 13)
54 | {
55 | break; // EOL found
56 | }
57 |
58 | result.WriteByte((byte)thisByte);
59 | }
60 |
61 | return result;
62 | }
63 |
64 | public long Position { get { return _logStream.Position; } }
65 |
66 | public void Dispose()
67 | {
68 | _logStream.Dispose();
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/LogReaderFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | class LogReaderFactory : ILogReaderFactory
4 | {
5 | public ILogReader Create(string fileName, long position)
6 | {
7 | return LogReader.Create(fileName, position);
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/LogSendErrorEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Serilog.Sinks.Amazon.Kinesis.Common
4 | {
5 | ///
6 | /// Args for event raised when log sending errors.
7 | ///
8 | public class LogSendErrorEventArgs : EventArgs
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | ///
14 | ///
15 | public LogSendErrorEventArgs(string message, Exception exception)
16 | {
17 | Message = message;
18 | Exception = exception;
19 | }
20 |
21 | ///
22 | /// A message with details of the error.
23 | ///
24 | public string Message { get; set; }
25 |
26 | ///
27 | /// The underlying exception.
28 | ///
29 | public Exception Exception { get; set; }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/LogShipperFileManager.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace Serilog.Sinks.Amazon.Kinesis.Common
4 | {
5 | class LogShipperFileManager : ILogShipperFileManager
6 | {
7 | public long GetFileLengthExclusiveAccess(string filePath)
8 | {
9 | using (var fileStream = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
10 | {
11 | return fileStream.Length;
12 | }
13 | }
14 |
15 | public string[] GetFiles(string path, string searchPattern)
16 | {
17 | return Directory.GetFiles(path, searchPattern);
18 | }
19 |
20 | public void LockAndDeleteFile(string filePath)
21 | {
22 | using (new FileStream(filePath, FileMode.Open, FileAccess.Read,
23 | FileShare.None, 128, FileOptions.DeleteOnClose))
24 | {
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/PersistedBookmark.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace Serilog.Sinks.Amazon.Kinesis.Common
7 | {
8 | class PersistedBookmark : IPersistedBookmark
9 | {
10 | private static readonly Encoding _bookmarkEncoding = new UTF8Encoding(false, false);
11 | private readonly System.IO.Stream _bookmarkStream;
12 |
13 | private string _fileName;
14 | private long _position;
15 |
16 | private PersistedBookmark(System.IO.Stream bookmarkStream)
17 | {
18 | _bookmarkStream = bookmarkStream;
19 | }
20 |
21 | public static PersistedBookmark Create(string bookmarkFileName)
22 | {
23 | var stream = System.IO.File.Open(bookmarkFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
24 | try
25 | {
26 | var bookmark = new PersistedBookmark(stream);
27 | bookmark.Read();
28 |
29 | stream = null;
30 | return bookmark;
31 | }
32 | finally
33 | {
34 | if (stream != null) stream.Dispose();
35 | }
36 | }
37 |
38 | public long Position
39 | {
40 | get { return _position; }
41 | }
42 |
43 | public string FileName
44 | {
45 | get { return _fileName; }
46 | }
47 |
48 | public void UpdatePosition(long position)
49 | {
50 | if (string.IsNullOrEmpty(_fileName))
51 | throw new InvalidOperationException("Cannot update Position when FileName is not set.");
52 |
53 | _position = position;
54 | Save();
55 | }
56 |
57 | public void UpdateFileNameAndPosition(string fileName, long position)
58 | {
59 | _position = position;
60 | _fileName = fileName;
61 | Save();
62 | }
63 |
64 | private void Save()
65 | {
66 | _bookmarkStream.SetLength(0);
67 | string content = string.Format(CultureInfo.InvariantCulture, "{0}:::{1}", _position, _fileName);
68 | byte[] buffer = _bookmarkEncoding.GetBytes(content);
69 | _bookmarkStream.Position = 0;
70 | _bookmarkStream.Write(buffer, 0, buffer.Length);
71 | _bookmarkStream.Flush();
72 | }
73 |
74 | private void Read()
75 | {
76 | string content = ReadContentString();
77 | if (content == null) return;
78 | var parts = content.Split(new[] { ":::" }, StringSplitOptions.RemoveEmptyEntries);
79 | if (parts.Length == 2)
80 | {
81 | long position;
82 | string fileName = parts[1];
83 | if (long.TryParse(parts[0], NumberStyles.None, CultureInfo.InvariantCulture, out position)
84 | && !string.IsNullOrWhiteSpace(fileName)
85 | && fileName.IndexOfAny(Path.GetInvalidPathChars()) < 0)
86 | {
87 | _position = position;
88 | _fileName = fileName;
89 | }
90 | }
91 | }
92 |
93 | private string ReadContentString()
94 | {
95 | _bookmarkStream.Position = 0;
96 | byte[] buffer = new byte[_bookmarkStream.Length];
97 |
98 | int bufferLength = 0, advance;
99 | while (
100 | bufferLength != buffer.Length
101 | && 0 != (advance = _bookmarkStream.Read(buffer, bufferLength, buffer.Length - bufferLength))
102 | )
103 | {
104 | bufferLength += advance;
105 | }
106 |
107 | if (bufferLength > 0)
108 | {
109 | try
110 | {
111 | return _bookmarkEncoding.GetString(buffer, 0, bufferLength);
112 | }
113 | catch (DecoderFallbackException) { }
114 | }
115 | return null;
116 | }
117 |
118 | public void Dispose()
119 | {
120 | _bookmarkStream.Dispose();
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/PersistedBookmarkFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Common
2 | {
3 | class PersistedBookmarkFactory : IPersistedBookmarkFactory
4 | {
5 | public IPersistedBookmark Create(string bookmarkFileName)
6 | {
7 | return PersistedBookmark.Create(bookmarkFileName);
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Common/Throttle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 |
4 | namespace Serilog.Sinks.Amazon.Kinesis.Common
5 | {
6 | class Throttle
7 | {
8 | private readonly Timer _timer;
9 | private readonly Action _callback;
10 | private readonly object _lockObj = new object();
11 | private readonly TimeSpan _throttlingTime;
12 | private bool _running;
13 | private int _throttling;
14 |
15 | private const int THROTTLING_FREE = 0;
16 | private const int THROTTLING_BUSY = 1;
17 |
18 | public Throttle (Action callback, TimeSpan throttlingTime)
19 | {
20 | _callback = callback;
21 | _throttlingTime = throttlingTime;
22 | _timer = new Timer(
23 | callback: s => ((Throttle)s).FireTimer(),
24 | state: this,
25 | dueTime: Timeout.Infinite,
26 | period: Timeout.Infinite);
27 | _throttling = THROTTLING_FREE;
28 | }
29 |
30 | private void FireTimer()
31 | {
32 | lock (_lockObj)
33 | {
34 | if (_running)
35 | {
36 | _callback();
37 | }
38 | }
39 | }
40 |
41 | public void Flush()
42 | {
43 | FireTimer();
44 | }
45 |
46 | public bool ThrottleAction()
47 | {
48 | if (Interlocked.CompareExchange(ref _throttling, THROTTLING_BUSY, THROTTLING_FREE) == THROTTLING_FREE)
49 | {
50 | _running = true;
51 | return _timer.Change(_throttlingTime, new TimeSpan(0, 0, 0, 0, Timeout.Infinite));
52 | }
53 | return false;
54 | }
55 |
56 | ///
57 | /// Stops the timer and synchronously waits for any running callbacks to finish running
58 | ///
59 | public void Stop()
60 | {
61 | lock (_lockObj)
62 | {
63 | // FireTimer is *not* running _callback (since we got the lock)
64 | _timer.Change(
65 | dueTime: Timeout.Infinite,
66 | period: Timeout.Infinite);
67 |
68 | _running = false;
69 | }
70 | }
71 |
72 | public void Dispose()
73 | {
74 | FireTimer();
75 | _timer.Dispose();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/KinesisFirehoseLoggerConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.KinesisFirehose;
17 | using Serilog.Configuration;
18 | using Serilog.Core;
19 | using Serilog.Events;
20 | using Serilog.Formatting;
21 | using Serilog.Sinks.Amazon.Kinesis.Common;
22 | using Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks;
23 |
24 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose
25 | {
26 | ///
27 | /// Adds the WriteTo.AmazonKinesisFirehose() extension method to .
28 | ///
29 | public static class KinesisFirehoseLoggerConfigurationExtensions
30 | {
31 | ///
32 | /// Adds a sink that writes log events as documents to Amazon Kinesis Firehose.
33 | ///
34 | /// The logger configuration.
35 | ///
36 | ///
37 | /// Logger configuration, allowing configuration to continue.
38 | /// A required parameter is null.
39 | public static LoggerConfiguration AmazonKinesisFirehose(
40 | this LoggerSinkConfiguration loggerConfiguration,
41 | KinesisFirehoseSinkOptions options,
42 | IAmazonKinesisFirehose kinesisFirehoseClient)
43 | {
44 | if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
45 | if (options == null) throw new ArgumentNullException("options");
46 |
47 | ILogEventSink sink;
48 | if (options.BufferBaseFilename == null)
49 | {
50 | sink = new KinesisFirehoseSink(options, kinesisFirehoseClient);
51 | }
52 | else
53 | {
54 | sink = new DurableKinesisFirehoseSink(options, kinesisFirehoseClient);
55 | }
56 |
57 | return loggerConfiguration.Sink(sink, options.MinimumLogEventLevel ?? LevelAlias.Minimum);
58 | }
59 |
60 |
61 | ///
62 | /// Adds a sink that writes log events as documents to Amazon Kinesis.
63 | ///
64 | /// The logger configuration.
65 | ///
66 | ///
67 | ///
68 | ///
69 | ///
70 | ///
71 | ///
72 | ///
73 | ///
74 | /// Logger configuration, allowing configuration to continue.
75 | ///
76 | public static LoggerConfiguration AmazonKinesisFirehose(
77 | this LoggerSinkConfiguration loggerConfiguration,
78 | IAmazonKinesisFirehose kinesisFirehoseClient,
79 | string streamName,
80 | string bufferBaseFilename = null,
81 | int? bufferFileSizeLimitBytes = null,
82 | int? batchPostingLimit = null,
83 | TimeSpan? period = null,
84 | ITextFormatter customFormatter = null,
85 | LogEventLevel? minimumLogEventLevel = null,
86 | EventHandler onLogSendError = null,
87 | bool shared = false)
88 | {
89 | if (kinesisFirehoseClient == null) throw new ArgumentNullException("kinesisFirehoseClient");
90 | if (streamName == null) throw new ArgumentNullException("streamName");
91 |
92 | var options = new KinesisFirehoseSinkOptions(streamName)
93 | {
94 | BufferFileSizeLimitBytes = bufferFileSizeLimitBytes,
95 | BufferBaseFilename = bufferBaseFilename == null ? null : bufferBaseFilename + ".firehose",
96 | Period = period ?? KinesisSinkOptionsBase.DefaultPeriod,
97 | BatchPostingLimit = batchPostingLimit ?? KinesisSinkOptionsBase.DefaultBatchPostingLimit,
98 | MinimumLogEventLevel = minimumLogEventLevel ?? LevelAlias.Minimum,
99 | OnLogSendError = onLogSendError,
100 | CustomDurableFormatter = customFormatter,
101 | Shared = shared
102 | };
103 |
104 | return AmazonKinesisFirehose(loggerConfiguration, options, kinesisFirehoseClient);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/Sinks/DurableKinesisFirehoseSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.KinesisFirehose;
17 | using Serilog.Core;
18 | using Serilog.Events;
19 | using Serilog.Sinks.Amazon.Kinesis.Common;
20 | using Serilog.Sinks.RollingFile;
21 |
22 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks
23 | {
24 | sealed class DurableKinesisFirehoseSink : ILogEventSink, IDisposable
25 | {
26 | readonly HttpLogShipper _shipper;
27 | readonly RollingFileSink _sink;
28 | EventHandler _logSendErrorHandler;
29 |
30 | public DurableKinesisFirehoseSink(KinesisFirehoseSinkOptions options, IAmazonKinesisFirehose kinesisFirehoseClient)
31 | {
32 | var state = new KinesisSinkState(options, kinesisFirehoseClient);
33 |
34 | if (string.IsNullOrWhiteSpace(options.BufferBaseFilename))
35 | {
36 | throw new ArgumentException("Cannot create the durable Amazon Kinesis Firehose sink without a buffer base file name.");
37 | }
38 |
39 | _sink = new RollingFileSink(
40 | options.BufferBaseFilename + "-{Date}.json",
41 | state.DurableFormatter,
42 | options.BufferFileSizeLimitBytes,
43 | null,
44 | shared: options.Shared);
45 |
46 | _shipper = new HttpLogShipper(state);
47 |
48 | _logSendErrorHandler = options.OnLogSendError;
49 | if (_logSendErrorHandler != null)
50 | {
51 | _shipper.LogSendError += _logSendErrorHandler;
52 | }
53 | }
54 |
55 | public void Emit(LogEvent logEvent)
56 | {
57 | _sink.Emit(logEvent);
58 | _shipper.Emit();
59 | }
60 |
61 | public void Dispose()
62 | {
63 | _sink.Dispose();
64 | _shipper.Dispose();
65 |
66 | if (_logSendErrorHandler != null)
67 | {
68 | _shipper.LogSendError -= _logSendErrorHandler;
69 | _logSendErrorHandler = null;
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/Sinks/HttpLogShipper.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System.Collections.Generic;
16 | using System.IO;
17 | using System.Threading.Tasks;
18 | using Amazon.KinesisFirehose;
19 | using Amazon.KinesisFirehose.Model;
20 | using Serilog.Debugging;
21 | using Serilog.Sinks.Amazon.Kinesis.Common;
22 |
23 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks
24 | {
25 | internal class HttpLogShipper : HttpLogShipperBase
26 | {
27 | readonly IAmazonKinesisFirehose _kinesisFirehoseClient;
28 | readonly Throttle _throttle;
29 |
30 | public HttpLogShipper(KinesisSinkState state) : base(state.Options,
31 | new LogReaderFactory(),
32 | new PersistedBookmarkFactory(),
33 | new LogShipperFileManager()
34 | )
35 | {
36 | _throttle = new Throttle(ShipLogs, state.Options.Period);
37 | _kinesisFirehoseClient = state.KinesisFirehoseClient;
38 | }
39 |
40 | public void Emit()
41 | {
42 | _throttle.ThrottleAction();
43 | }
44 |
45 | public void Dispose()
46 | {
47 | _throttle.Flush();
48 | _throttle.Stop();
49 | _throttle.Dispose();
50 | }
51 |
52 | protected override Record PrepareRecord(MemoryStream stream)
53 | {
54 | return new Record
55 | {
56 | Data = stream
57 | };
58 | }
59 |
60 | protected override PutRecordBatchResponse SendRecords(List records, out bool successful)
61 | {
62 | var request = new PutRecordBatchRequest
63 | {
64 | DeliveryStreamName = _streamName,
65 | Records = records
66 | };
67 |
68 | SelfLog.WriteLine("Writing {0} records to firehose", records.Count);
69 | var putRecordBatchTask = _kinesisFirehoseClient.PutRecordBatchAsync(request);
70 |
71 | successful = putRecordBatchTask.GetAwaiter().GetResult().FailedPutCount == 0;
72 | return putRecordBatchTask.Result;
73 | }
74 |
75 | protected override void HandleError(PutRecordBatchResponse response, int originalRecordCount)
76 | {
77 | foreach (var record in response.RequestResponses)
78 | {
79 | SelfLog.WriteLine("Firehose failed to index record in stream '{0}'. {1} {2} ", _streamName, record.ErrorCode, record.ErrorMessage);
80 | }
81 | // fire event
82 | OnLogSendError(new LogSendErrorEventArgs(string.Format("Error writing records to {0} ({1} of {2} records failed)", _streamName, response.FailedPutCount, originalRecordCount), null));
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/Sinks/KinesisFirehoseSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System.Collections.Generic;
16 | using System.IO;
17 | using System.Text;
18 | using System.Threading.Tasks;
19 | using Amazon.KinesisFirehose;
20 | using Amazon.KinesisFirehose.Model;
21 | using Serilog.Events;
22 | using Serilog.Sinks.PeriodicBatching;
23 |
24 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks
25 | {
26 | ///
27 | /// Writes log events as documents to a Amazon KinesisFirehose.
28 | ///
29 | public class KinesisFirehoseSink : PeriodicBatchingSink
30 | {
31 | readonly KinesisSinkState _state;
32 | readonly LogEventLevel? _minimumAcceptedLevel;
33 |
34 | ///
35 | /// Construct a sink posting to the specified database.
36 | ///
37 | /// The Amazon Kinesis Firehose client.
38 | /// Options for configuring how the sink behaves, may NOT be null.
39 | public KinesisFirehoseSink(KinesisFirehoseSinkOptions options, IAmazonKinesisFirehose kinesisFirehoseClient) :
40 | base(options.BatchPostingLimit, options.Period)
41 | {
42 | _state = new KinesisSinkState(options, kinesisFirehoseClient);
43 |
44 | _minimumAcceptedLevel = _state.Options.MinimumLogEventLevel;
45 | }
46 |
47 | ///
48 | /// Emit a batch of log events, running to completion asynchronously.
49 | ///
50 | /// The events to be logged to Kinesis Firehose
51 | protected override Task EmitBatchAsync(IEnumerable events)
52 | {
53 | var request = new PutRecordBatchRequest
54 | {
55 | DeliveryStreamName = _state.Options.StreamName
56 | };
57 |
58 | foreach (var logEvent in events)
59 | {
60 | var json = new StringWriter();
61 | _state.Formatter.Format(logEvent, json);
62 |
63 | var bytes = Encoding.UTF8.GetBytes(json.ToString());
64 |
65 | var entry = new Record
66 | {
67 | Data = new MemoryStream(bytes),
68 | };
69 |
70 | request.Records.Add(entry);
71 | }
72 |
73 | return _state.KinesisFirehoseClient.PutRecordBatchAsync(request);
74 | }
75 |
76 |
77 | ///
78 | ///
79 | ///
80 | ///
81 | ///
82 | protected override bool CanInclude(LogEvent evt)
83 | {
84 | return _minimumAcceptedLevel == null ||
85 | (int)_minimumAcceptedLevel <= (int)evt.Level;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/Sinks/KinesisFirehoseSinkOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using Serilog.Sinks.Amazon.Kinesis.Common;
16 |
17 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks
18 | {
19 | ///
20 | /// Provides KinesisFirehoseSink with configurable options
21 | ///
22 | public class KinesisFirehoseSinkOptions : KinesisSinkOptionsBase
23 | {
24 | ///
25 | /// Configures the Amazon Kinesis sink.
26 | ///
27 | /// The name of the Kinesis stream.
28 | public KinesisFirehoseSinkOptions(string streamName) : base(streamName) {}
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Firehose/Sinks/KinesisSinkState.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.KinesisFirehose;
17 | using Serilog.Sinks.Amazon.Kinesis.Common;
18 |
19 | namespace Serilog.Sinks.Amazon.Kinesis.Firehose.Sinks
20 | {
21 | internal class KinesisSinkState : KinesisSinkStateBase
22 | {
23 | public KinesisSinkState(KinesisFirehoseSinkOptions options, IAmazonKinesisFirehose kinesisFirehoseClient) : base(options)
24 | {
25 | if (kinesisFirehoseClient == null) throw new ArgumentNullException("kinesisFirehoseClient");
26 | KinesisFirehoseClient = kinesisFirehoseClient;
27 | }
28 |
29 | public IAmazonKinesisFirehose KinesisFirehoseClient { get; }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | [assembly: InternalsVisibleTo("Serilog.Sinks.Amazon.Kinesis.Tests, PublicKey=00240000048000009400000006020000" +
3 | "00240000525341310004000001000100" +
4 | "FB8D13FD344A1C6FE0FE83EF33C1080B" +
5 | "F30690765BC6EB0DF26EBFDF8F21670C" +
6 | "64265B30DB09F73A0DEA5B3DB4C9D18D" +
7 | "BF6D5A25AF5CE9016F281014D79DC3B4" +
8 | "201AC646C451830FC7E61A2DFD633D34" +
9 | "C39F87B81894191652DF5AC63CC40C77" +
10 | "F3542F702BDA692E6E8A9158353DF189" +
11 | "007A49DA0F3CFD55EB250066B19485EC")]
12 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=00240000048000009400000006020000002" +
13 | "40000525341310004000001000100c547ca" +
14 | "c37abd99c8db225ef2f6c8a3602f3b3606c" +
15 | "c9891605d02baa56104f4cfc0734aa39b93" +
16 | "bf7852f7d9266654753cc297e7d2edfe0ba" +
17 | "c1cdcf9f717241550e0a7b191195b7667bb" +
18 | "4f64bcb8e2121380fd1d9d46ad2d92d2d15" +
19 | "605093924cceaf74c4861eff62abf69b929" +
20 | "1ed0a340e113be11e6a7d3113e92484cf70" +
21 | "45cc7")]
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Serilog.Sinks.Amazon.Kinesis.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 |
7 | The periodic batching sink for Serilog
8 | 2.2.0
9 | Serilog Contributors
10 | net45;netstandard2.0;netstandard1.6
11 | true
12 | Serilog.Sinks.Amazon.Kinesis
13 | assets/Serilog.snk
14 | true
15 | true
16 | Serilog.Sinks.AmazonKinesis
17 | serilog;kinesis
18 | http://serilog.net/images/serilog-sink-nuget.png
19 | http://serilog.net
20 | http://www.apache.org/licenses/LICENSE-2.0
21 | https://github.com/troylar/serilog-sinks-amazonkinesis
22 | git
23 |
24 | true
25 | true
26 |
27 |
28 |
29 | TRACE;DEBUG;LIBLOG_PORTABLE
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 4.3.0
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/KinesisLoggerConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.Kinesis;
17 | using Serilog.Configuration;
18 | using Serilog.Core;
19 | using Serilog.Events;
20 | using Serilog.Formatting;
21 | using Serilog.Sinks.Amazon.Kinesis.Common;
22 | using Serilog.Sinks.Amazon.Kinesis.Stream.Sinks;
23 |
24 | namespace Serilog.Sinks.Amazon.Kinesis.Stream
25 | {
26 | ///
27 | /// Adds the WriteTo.AmazonKinesis() extension method to .
28 | ///
29 | public static class KinesisLoggerConfigurationExtensions
30 | {
31 | ///
32 | /// Adds a sink that writes log events as documents to Amazon Kinesis.
33 | ///
34 | /// The logger configuration.
35 | ///
36 | ///
37 | /// Logger configuration, allowing configuration to continue.
38 | /// A required parameter is null.
39 | public static LoggerConfiguration AmazonKinesis(
40 | this LoggerSinkConfiguration loggerConfiguration,
41 | KinesisStreamSinkOptions options,IAmazonKinesis kinesisClient)
42 | {
43 | if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration");
44 | if (options == null) throw new ArgumentNullException("options");
45 | if (kinesisClient == null) throw new ArgumentNullException("kinesisClient");
46 |
47 | ILogEventSink sink;
48 | if (options.BufferBaseFilename == null)
49 | {
50 | sink = new KinesisSink(options, kinesisClient);
51 | }
52 | else
53 | {
54 | sink = new DurableKinesisSink(options, kinesisClient);
55 | }
56 |
57 | return loggerConfiguration.Sink(sink, options.MinimumLogEventLevel ?? LevelAlias.Minimum);
58 | }
59 |
60 |
61 | ///
62 | /// Adds a sink that writes log events as documents to Amazon Kinesis.
63 | ///
64 | /// The logger configuration.
65 | ///
66 | ///
67 | ///
68 | ///
69 | ///
70 | ///
71 | ///
72 | ///
73 | ///
74 | /// Logger configuration, allowing configuration to continue.
75 | ///
76 | public static LoggerConfiguration AmazonKinesis(
77 | this LoggerSinkConfiguration loggerConfiguration,
78 | IAmazonKinesis kinesisClient,
79 | string streamName,
80 | string bufferBaseFilename = null,
81 | int? bufferFileSizeLimitBytes = null,
82 | int? batchPostingLimit = null,
83 | TimeSpan? period = null,
84 | LogEventLevel? minimumLogEventLevel = null,
85 | ITextFormatter customFormatter = null,
86 | EventHandler onLogSendError = null,
87 | bool shared = false)
88 | {
89 | if (streamName == null) throw new ArgumentNullException("streamName");
90 |
91 | var options = new KinesisStreamSinkOptions(streamName: streamName)
92 | {
93 | BufferFileSizeLimitBytes = bufferFileSizeLimitBytes,
94 | BufferBaseFilename = bufferBaseFilename == null ? null : bufferBaseFilename + ".stream",
95 | Period = period ?? KinesisSinkOptionsBase.DefaultPeriod,
96 | BatchPostingLimit = batchPostingLimit ?? KinesisSinkOptionsBase.DefaultBatchPostingLimit,
97 | MinimumLogEventLevel = minimumLogEventLevel ?? LevelAlias.Minimum,
98 | OnLogSendError = onLogSendError,
99 | CustomDurableFormatter = customFormatter,
100 | Shared = shared
101 | };
102 |
103 | return AmazonKinesis(loggerConfiguration, options, kinesisClient);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/DurableKinesisSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.Kinesis;
17 | using Serilog.Core;
18 | using Serilog.Events;
19 | using Serilog.Sinks.Amazon.Kinesis.Common;
20 | using Serilog.Sinks.RollingFile;
21 |
22 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
23 | {
24 | sealed class DurableKinesisSink : ILogEventSink, IDisposable
25 | {
26 | readonly HttpLogShipper _shipper;
27 | readonly RollingFileSink _sink;
28 | EventHandler _logSendErrorHandler;
29 |
30 | public DurableKinesisSink(KinesisStreamSinkOptions options, IAmazonKinesis kinesisClient)
31 | {
32 | var state = new KinesisSinkState(options, kinesisClient);
33 |
34 | if (string.IsNullOrWhiteSpace(options.BufferBaseFilename))
35 | {
36 | throw new ArgumentException("Cannot create the durable Amazon Kinesis sink without a buffer base file name.");
37 | }
38 |
39 | _sink = new RollingFileSink(
40 | options.BufferBaseFilename + "-{Date}.json",
41 | state.DurableFormatter,
42 | options.BufferFileSizeLimitBytes,
43 | null,
44 | shared: options.Shared);
45 |
46 | _shipper = new HttpLogShipper(state);
47 |
48 | _logSendErrorHandler = options.OnLogSendError;
49 | if (_logSendErrorHandler != null)
50 | {
51 | _shipper.LogSendError += _logSendErrorHandler;
52 | }
53 | }
54 |
55 | public void Dispose()
56 | {
57 | _sink.Dispose();
58 | _shipper.Dispose();
59 |
60 | if (_logSendErrorHandler != null)
61 | {
62 | _shipper.LogSendError -= _logSendErrorHandler;
63 | _logSendErrorHandler = null;
64 | }
65 | }
66 |
67 | public void Emit(LogEvent logEvent)
68 | {
69 | _sink.Emit(logEvent);
70 | _shipper.Emit();
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/HttpLogShipper.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Collections.Generic;
17 | using System.IO;
18 | using System.Threading.Tasks;
19 | using Amazon.Kinesis;
20 | using Amazon.Kinesis.Model;
21 | using Serilog.Debugging;
22 | using Serilog.Sinks.Amazon.Kinesis.Common;
23 |
24 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
25 | {
26 | sealed class HttpLogShipper : HttpLogShipperBase, IDisposable
27 | {
28 | readonly IAmazonKinesis _kinesisClient;
29 | readonly Throttle _throttle;
30 |
31 | public HttpLogShipper(KinesisSinkState state) : base(state.Options,
32 | new LogReaderFactory(),
33 | new PersistedBookmarkFactory(),
34 | new LogShipperFileManager()
35 | )
36 | {
37 | _throttle = new Throttle(ShipLogs, state.Options.Period);
38 | _kinesisClient = state.KinesisClient;
39 | }
40 |
41 | public void Emit()
42 | {
43 | _throttle.ThrottleAction();
44 | }
45 |
46 | public void Dispose()
47 | {
48 | _throttle.Flush();
49 | _throttle.Stop();
50 | _throttle.Dispose();
51 | }
52 |
53 | protected override PutRecordsRequestEntry PrepareRecord(MemoryStream stream)
54 | {
55 | return new PutRecordsRequestEntry
56 | {
57 | PartitionKey = Guid.NewGuid().ToString(),
58 | Data = stream
59 | };
60 | }
61 |
62 | protected override PutRecordsResponse SendRecords(List records, out bool successful)
63 | {
64 | var request = new PutRecordsRequest
65 | {
66 | StreamName = _streamName,
67 | Records = records
68 | };
69 |
70 | SelfLog.WriteLine("Writing {0} records to kinesis", records.Count);
71 | var putRecordBatchTask = _kinesisClient.PutRecordsAsync(request);
72 |
73 | successful = putRecordBatchTask.GetAwaiter().GetResult().FailedRecordCount == 0;
74 | return putRecordBatchTask.Result;
75 | }
76 |
77 | protected override void HandleError(PutRecordsResponse response, int originalRecordCount)
78 | {
79 | foreach (var record in response.Records)
80 | {
81 | SelfLog.WriteLine("Kinesis failed to index record in stream '{0}'. {1} {2} ", _streamName, record.ErrorCode, record.ErrorMessage);
82 | }
83 | // fire event
84 | OnLogSendError(new LogSendErrorEventArgs(string.Format("Error writing records to {0} ({1} of {2} records failed)", _streamName, response.FailedRecordCount, originalRecordCount), null));
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/KinesisApi.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Threading;
17 | using System.Threading.Tasks;
18 | using Amazon.Kinesis;
19 | using Amazon.Kinesis.Model;
20 | using Amazon.Runtime;
21 | using Serilog.Debugging;
22 |
23 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
24 | {
25 | ///
26 | /// Utilities to create and delete Amazon Kinesis streams.
27 | ///
28 | public class KinesisApi
29 | {
30 | ///
31 | /// Creates the Amazon Kinesis stream specified and waits for it to become active.
32 | ///
33 | /// The Amazon Kinesis client.
34 | /// The name of the steam to be created.
35 | /// The number of shards the stream should be created with.
36 | public static bool CreateAndWaitForStreamToBecomeAvailable(IAmazonKinesis kinesisClient, string streamName, int shardCount)
37 | {
38 | SelfLog.WriteLine(string.Format("Checking stream '{0}' status.", streamName));
39 |
40 | var stream = DescribeStream(kinesisClient, streamName);
41 | if (stream != null)
42 | {
43 | string state = stream.StreamDescription.StreamStatus;
44 | switch (state)
45 | {
46 | case "DELETING":
47 | {
48 | SelfLog.WriteLine(string.Format("Stream '{0}' is {1}", streamName, state));
49 |
50 | var startTime = DateTime.UtcNow;
51 | var endTime = startTime + TimeSpan.FromSeconds(120);
52 |
53 | while (DateTime.UtcNow < endTime && StreamExists(kinesisClient, streamName))
54 | {
55 | SelfLog.WriteLine(string.Format("... waiting for stream '{0}' to delete ...", streamName));
56 | Thread.Sleep(1000 * 5);
57 | }
58 |
59 | if (StreamExists(kinesisClient, streamName))
60 | {
61 | var error = string.Format("Timed out waiting for stream '{0}' to delete", streamName);
62 | SelfLog.WriteLine(error);
63 | return false;
64 | }
65 |
66 | SelfLog.WriteLine(string.Format("Creating stream '{0}'.", streamName));
67 | var response = CreateStream(kinesisClient, streamName, shardCount);
68 | if (response == null)
69 | {
70 | return false;
71 | }
72 |
73 | break;
74 | }
75 | case "CREATING":
76 | {
77 | break;
78 | }
79 | case "ACTIVE":
80 | case "UPDATING":
81 | {
82 | SelfLog.WriteLine(string.Format("Stream '{0}' is {1}", streamName, state));
83 | return true;
84 | }
85 | default:
86 | {
87 | SelfLog.WriteLine(string.Format("Unknown stream state: {0}", state));
88 | return false;
89 | }
90 | }
91 | }
92 | else
93 | {
94 | SelfLog.WriteLine(string.Format("Creating stream '{0}'.", streamName));
95 | var response = CreateStream(kinesisClient, streamName, shardCount);
96 | if (response == null)
97 | {
98 | return false;
99 | }
100 | }
101 |
102 | {
103 | // Wait for the stream status to become ACTIVE, timeout after 2 minutes
104 | var startTime = DateTime.UtcNow;
105 | var endTime = startTime + TimeSpan.FromSeconds(120);
106 |
107 | while (DateTime.UtcNow < endTime)
108 | {
109 | Thread.Sleep(1000 * 5);
110 |
111 | var response = DescribeStream(kinesisClient, streamName);
112 | if (response != null)
113 | {
114 | string state = response.StreamDescription.StreamStatus;
115 | if (state == "ACTIVE")
116 | {
117 | SelfLog.WriteLine(string.Format("Stream '{0}' is {1}", streamName, state));
118 | return true;
119 | }
120 | }
121 |
122 | SelfLog.WriteLine(string.Format("... waiting for stream {0} to become active ....", streamName));
123 | }
124 |
125 | SelfLog.WriteLine(string.Format("Stream '{0}' never went active.", streamName));
126 | return false;
127 | }
128 | }
129 |
130 | static bool StreamExists(IAmazonKinesis kinesisClient, string streamName)
131 | {
132 | var stream = DescribeStream(kinesisClient, streamName);
133 |
134 | return stream != null;
135 | }
136 |
137 | static CreateStreamResponse CreateStream(IAmazonKinesis kinesisClient, string streamName, int shardCount)
138 | {
139 | var request = new CreateStreamRequest { StreamName = streamName, ShardCount = shardCount };
140 | try
141 | {
142 | var createStreamResponse = kinesisClient.CreateStreamAsync(request);
143 | return createStreamResponse.GetAwaiter().GetResult();
144 | }
145 | catch (AmazonServiceException e)
146 | {
147 | var error = string.Format("Failed to create stream '{0}'. Reason: {1}", streamName, e.Message);
148 | SelfLog.WriteLine(error);
149 |
150 | return null;
151 | }
152 | }
153 |
154 | static DescribeStreamResponse DescribeStream(IAmazonKinesis kinesisClient, string streamName)
155 | {
156 | var request = new DescribeStreamRequest { StreamName = streamName };
157 | try
158 | {
159 | var describeStreamTask = kinesisClient.DescribeStreamAsync(request);
160 | return describeStreamTask.GetAwaiter().GetResult();
161 | }
162 | catch (ResourceNotFoundException)
163 | {
164 | return null;
165 | }
166 | }
167 | }
168 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/KinesisSink.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using System.Collections.Generic;
17 | using System.IO;
18 | using System.Text;
19 | using System.Threading.Tasks;
20 | using Amazon.Kinesis;
21 | using Amazon.Kinesis.Model;
22 | using Serilog.Events;
23 | using Serilog.Sinks.PeriodicBatching;
24 |
25 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
26 | {
27 | ///
28 | /// Writes log events as documents to a Amazon Kinesis.
29 | ///
30 | public class KinesisSink : PeriodicBatchingSink
31 | {
32 | readonly KinesisSinkState _state;
33 | readonly LogEventLevel? _minimumAcceptedLevel;
34 |
35 | ///
36 | /// Construct a sink posting to the specified database.
37 | ///
38 | /// Options for configuring how the sink behaves, may NOT be null.
39 | ///
40 | public KinesisSink(KinesisStreamSinkOptions options,IAmazonKinesis kinesisClient) :
41 | base(options.BatchPostingLimit, options.Period)
42 | {
43 | _state = new KinesisSinkState(options,kinesisClient);
44 |
45 | _minimumAcceptedLevel = _state.Options.MinimumLogEventLevel;
46 | }
47 |
48 | ~KinesisSink()
49 | {
50 | Dispose(true);
51 | }
52 |
53 | ///
54 | /// Emit a batch of log events, running to completion asynchronously.
55 | ///
56 | /// The events to be logged to Kinesis
57 | protected override Task EmitBatchAsync(IEnumerable events)
58 | {
59 | var request = new PutRecordsRequest
60 | {
61 | StreamName = _state.Options.StreamName
62 | };
63 |
64 | foreach (var logEvent in events)
65 | {
66 | var json = new StringWriter();
67 | _state.Formatter.Format(logEvent, json);
68 |
69 | var bytes = Encoding.UTF8.GetBytes(json.ToString());
70 |
71 | var entry = new PutRecordsRequestEntry
72 | {
73 | PartitionKey = Guid.NewGuid().ToString(),
74 | Data = new MemoryStream(bytes),
75 | };
76 |
77 | request.Records.Add(entry);
78 | }
79 | return _state.KinesisClient.PutRecordsAsync(request);
80 | }
81 |
82 |
83 | ///
84 | ///
85 | ///
86 | ///
87 | ///
88 | protected override bool CanInclude(LogEvent evt)
89 | {
90 | return _minimumAcceptedLevel == null ||
91 | (int)_minimumAcceptedLevel <= (int)evt.Level;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/KinesisSinkState.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using System;
16 | using Amazon.Kinesis;
17 | using Serilog.Sinks.Amazon.Kinesis.Common;
18 |
19 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
20 | {
21 | internal class KinesisSinkState : KinesisSinkStateBase
22 | {
23 | public KinesisSinkState(KinesisStreamSinkOptions options, IAmazonKinesis kinesisClient) : base(options)
24 | {
25 | if (kinesisClient == null) throw new ArgumentNullException("kinesisClient");
26 | KinesisClient = kinesisClient;
27 | }
28 |
29 | public IAmazonKinesis KinesisClient { get; }
30 | }
31 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/Stream/Sinks/KinesisStreamSinkOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Serilog Contributors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | using Serilog.Sinks.Amazon.Kinesis.Common;
16 |
17 | namespace Serilog.Sinks.Amazon.Kinesis.Stream.Sinks
18 | {
19 | ///
20 | /// Provides KinesisSink with configurable options
21 | ///
22 | public class KinesisStreamSinkOptions : KinesisSinkOptionsBase
23 | {
24 | ///
25 | /// Configures the Amazon Kinesis sink.
26 | ///
27 | /// The name of the Kinesis stream.
28 | public KinesisStreamSinkOptions(string streamName) : base(streamName)
29 | {
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Amazon.Kinesis/assets/Serilog.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serilog-archive/serilog-sinks-amazonkinesis/555981e1135a9ca804b843b8faf5a74cc7755412/src/Serilog.Sinks.Amazon.Kinesis/assets/Serilog.snk
--------------------------------------------------------------------------------
/src/Serilog.Sinks.AmazonKinesis.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Serilog.Sinks.AmazonKinesis
5 | $version$
6 | Jake Scott, Justin Thirkell, Alexander Savchuk
7 | Serilog event sink that writes to Amazon Kinesis.
8 | en-US
9 | http://serilog.net
10 | http://www.apache.org/licenses/LICENSE-2.0
11 | http://serilog.net/images/serilog-sink-nuget.png
12 | false
13 | serilog logging aws amazon kinesis structured logging
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/HttpLogShipperBaseTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using Moq;
5 | using NUnit.Framework;
6 | using Ploeh.AutoFixture;
7 | using Ploeh.AutoFixture.AutoMoq;
8 | using Serilog.Sinks.Amazon.Kinesis.Common;
9 | using System.Collections.Generic;
10 |
11 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
12 | {
13 | [TestFixture]
14 | abstract class HttpLogShipperBaseTestBase
15 | {
16 | private MockRepository _mockRepository;
17 |
18 | private LogShipperSUT Target { get; set; }
19 |
20 | private Mock Options { get; set; }
21 | private Mock LogReaderFactory { get; set; }
22 | private Mock PersistedBookmarkFactory { get; set; }
23 | private Mock PersistedBookmark { get; set; }
24 | private Mock LogShipperFileManager { get; set; }
25 | private Mock LogShipperDelegator { get; set; }
26 | private EventHandler TargetOnLogSendError { get; set; }
27 |
28 | protected string LogFileNamePrefix { get; private set; }
29 | protected string LogFolder { get; private set; }
30 | protected int BatchPostingLimit { get; private set; }
31 |
32 | protected string[] LogFiles { get; private set; }
33 | protected int SentBatches { get; private set; }
34 | protected int SentRecords { get; private set; }
35 | protected int FailedBatches { get; private set; }
36 | protected int FailedRecords { get; private set; }
37 |
38 | protected string CurrentLogFileName { get; private set; }
39 | protected long CurrentLogFilePosition { get; private set; }
40 |
41 | protected IFixture Fixture { get; private set; }
42 |
43 | [SetUp]
44 | public void SetUp()
45 | {
46 | CurrentLogFileName = null;
47 | CurrentLogFilePosition = 0;
48 |
49 | TargetOnLogSendError = DefaultTargetOnLogSendError;
50 |
51 | _mockRepository = new MockRepository(MockBehavior.Strict);
52 |
53 | Fixture = new Fixture().Customize(
54 | new AutoMoqCustomization()
55 | );
56 |
57 | BatchPostingLimit = Fixture.Create();
58 | LogFolder = Path.GetDirectoryName(Path.GetTempPath());
59 | LogFileNamePrefix = Guid.NewGuid().ToString("N");
60 |
61 |
62 | LogReaderFactory = _mockRepository.Create();
63 | Fixture.Inject(LogReaderFactory.Object);
64 |
65 | PersistedBookmarkFactory = _mockRepository.Create();
66 | Fixture.Inject(PersistedBookmarkFactory.Object);
67 |
68 | LogShipperFileManager = _mockRepository.Create();
69 | Fixture.Inject(LogShipperFileManager.Object);
70 |
71 | SentBatches = SentRecords = 0;
72 | FailedBatches = FailedRecords = 0;
73 | LogShipperDelegator = _mockRepository.Create();
74 | Fixture.Inject(LogShipperDelegator.Object);
75 |
76 | LogShipperDelegator.Setup(x => x.PrepareRecord(It.Is(s => s.Length > 0)))
77 | .Returns((MemoryStream s) => new string('a', (int)s.Length));
78 |
79 | SetUpSinkOptions();
80 | }
81 |
82 | private void SetUpSinkOptions()
83 | {
84 | Options = _mockRepository.Create();
85 | Fixture.Inject(Options.Object);
86 |
87 | Options.SetupGet(x => x.BufferBaseFilename)
88 | .Returns(Path.Combine(LogFolder, LogFileNamePrefix));
89 | Options.SetupGet(x => x.StreamName)
90 | .Returns(Fixture.Create());
91 | Options.SetupGet(x => x.BatchPostingLimit)
92 | .Returns(BatchPostingLimit);
93 | }
94 |
95 | protected void GivenSendIsSuccessful()
96 | {
97 | var success = true;
98 | LogShipperDelegator.Setup(
99 | x => x.SendRecords(It.Is>(s => s.Count > 0 && s.Count <= Options.Object.BatchPostingLimit), out success)
100 | )
101 | .Returns(Fixture.Create())
102 | .Callback((List batch, bool b) =>
103 | {
104 | SentBatches++;
105 | SentRecords += batch.Count;
106 | });
107 | }
108 |
109 | protected void GivenSendIsFailed()
110 | {
111 | var success = false;
112 | var response = Fixture.Create();
113 | LogShipperDelegator.Setup(
114 | x => x.SendRecords(It.Is>(s => s.Count > 0 && s.Count <= Options.Object.BatchPostingLimit), out success)
115 | )
116 | .Returns(response);
117 |
118 | LogShipperDelegator.Setup(
119 | x => x.HandleError(response, It.IsAny())
120 | )
121 | .Callback((string resp, int recs) =>
122 | {
123 | FailedBatches++;
124 | FailedRecords += recs;
125 | });
126 |
127 | }
128 |
129 | protected void GivenLogFilesInDirectory(int files = 5)
130 | {
131 | LogFiles = Fixture.CreateMany(files)
132 | .Select(x => Path.Combine(LogFolder, LogFileNamePrefix + x + ".json"))
133 | .OrderBy(x => x)
134 | .ToArray();
135 |
136 | LogShipperFileManager
137 | .Setup(x => x.GetFiles(
138 | It.Is(s => s == LogFolder),
139 | It.Is(s => s == LogFileNamePrefix + "*.json")
140 | )
141 | )
142 | .Returns(() => LogFiles);
143 | }
144 |
145 | protected void GivenFileDeleteSucceeds(string filePath)
146 | {
147 | LogShipperFileManager.Setup(x => x.LockAndDeleteFile(filePath)).Callback((string file) =>
148 | {
149 | LogFiles = LogFiles.Where(x => !string.Equals(x, file, StringComparison.OrdinalIgnoreCase)).ToArray();
150 | });
151 | }
152 |
153 | protected void GivenFileCannotBeLocked(string logFileName)
154 | {
155 | LogShipperFileManager
156 | .Setup(x => x.GetFileLengthExclusiveAccess(logFileName))
157 | .Throws();
158 | }
159 |
160 | protected void GivenLockedFileLength(string logFileName, long length)
161 | {
162 | LogShipperFileManager
163 | .Setup(x => x.GetFileLengthExclusiveAccess(logFileName))
164 | .Returns(length);
165 | }
166 |
167 | protected void GivenPersistedBookmarkIsLocked()
168 | {
169 | PersistedBookmarkFactory
170 | .Setup(x => x.Create(It.Is(s => s == Options.Object.BufferBaseFilename + ".bookmark")))
171 | .Throws();
172 | }
173 |
174 | protected void GivenPersistedBookmarkFilePermissionsError()
175 | {
176 | PersistedBookmarkFactory
177 | .Setup(x => x.Create(It.Is(s => s == Options.Object.BufferBaseFilename + ".bookmark")))
178 | .Throws();
179 | }
180 |
181 | protected void GivenPersistedBookmark(string logFileName = null, long position = 0)
182 | {
183 | CurrentLogFileName = logFileName;
184 | CurrentLogFilePosition = position;
185 |
186 | PersistedBookmark = _mockRepository.Create();
187 | PersistedBookmark.Setup(x => x.Dispose()).Callback(() => PersistedBookmark.Reset());
188 | PersistedBookmark.SetupGet(x => x.FileName).Returns(() => CurrentLogFileName);
189 | PersistedBookmark.SetupGet(x => x.Position).Returns(() => CurrentLogFilePosition);
190 | PersistedBookmark.Setup(x => x.UpdatePosition(It.IsAny()))
191 | .Callback((long pos) => { CurrentLogFilePosition = pos; });
192 | PersistedBookmark.Setup(x => x.UpdateFileNameAndPosition(It.IsAny(), It.IsAny()))
193 | .Callback((string fileName, long pos) =>
194 | {
195 | CurrentLogFileName = fileName;
196 | CurrentLogFilePosition = pos;
197 | });
198 |
199 | PersistedBookmarkFactory
200 | .Setup(
201 | x => x.Create(It.Is(s => s == Options.Object.BufferBaseFilename + ".bookmark"))
202 | )
203 | .Returns(PersistedBookmark.Object);
204 | }
205 |
206 | protected void GivenLogReaderCreateIOError(string fileName, long position)
207 | {
208 | LogReaderFactory.Setup(x => x.Create(fileName, position)).Throws();
209 | }
210 |
211 | protected void GivenLogReader(string logFileName, long length, int maxStreams)
212 | {
213 | LogReaderFactory.Setup(x => x.Create(logFileName, It.IsInRange(0L, Math.Max(CurrentLogFilePosition, length), Range.Inclusive)))
214 | .Returns((string fileName, long position) =>
215 | {
216 | var internalPosition = position > length ? length : position;
217 | var streamsLeft = maxStreams;
218 | var reader = _mockRepository.Create();
219 | reader.SetupGet(x => x.Position).Returns(() => internalPosition);
220 | reader.Setup(x => x.ReadLine()).Returns(() =>
221 | {
222 | internalPosition++;
223 | streamsLeft--;
224 | if (internalPosition > length || streamsLeft < 0)
225 | {
226 | internalPosition = length;
227 | return new MemoryStream();
228 | }
229 | return new MemoryStream(new byte[1]);
230 | });
231 | reader.Setup(x => x.Dispose()).Callback(() => { reader.Reset(); });
232 | return reader.Object;
233 | });
234 | }
235 |
236 | protected void GivenOnLogSendErrorHandler(EventHandler handler)
237 | {
238 | TargetOnLogSendError = handler;
239 | }
240 |
241 | protected void WhenLogShipperIsCalled()
242 | {
243 | Target = Fixture.Create();
244 | Target.LogSendError += TargetOnLogSendError;
245 |
246 | Target.ShipIt();
247 | }
248 |
249 | private void DefaultTargetOnLogSendError(object sender, LogSendErrorEventArgs logSendErrorEventArgs)
250 | {
251 | throw logSendErrorEventArgs.Exception;
252 | }
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/LogSendErrorEventTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
6 | {
7 | class LogSendErrorEventTests : HttpLogShipperBaseTestBase
8 | {
9 | [Test]
10 | public void WhenNonIOExceptionOccurs_ThenLogSendErrorEventFires()
11 | {
12 | GivenPersistedBookmark();
13 | GivenPersistedBookmarkFilePermissionsError();
14 |
15 | Exception exception = null;
16 | string message = null;
17 | GivenOnLogSendErrorHandler((sender, args) =>
18 | {
19 | exception = args.Exception;
20 | message = args.Message;
21 | });
22 |
23 | WhenLogShipperIsCalled();
24 |
25 | this.ShouldSatisfyAllConditions(
26 | () => exception.ShouldNotBeNull(),
27 | () => message.ShouldNotBeNullOrWhiteSpace()
28 | );
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/LogShipperSUT.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using Serilog.Sinks.Amazon.Kinesis.Common;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
6 | {
7 | interface ILogShipperProtectedDelegator
8 | {
9 | string PrepareRecord(MemoryStream stream);
10 | string SendRecords(List records, out bool successful);
11 | void HandleError(string response, int originalRecordCount);
12 | }
13 |
14 | class LogShipperSUT : HttpLogShipperBase
15 | {
16 | private readonly ILogShipperProtectedDelegator _delegator;
17 |
18 | public LogShipperSUT(
19 | ILogShipperProtectedDelegator delegator,
20 | ILogShipperOptions options,
21 | ILogReaderFactory logReaderFactory,
22 | IPersistedBookmarkFactory persistedBookmarkFactory,
23 | ILogShipperFileManager fileManager
24 | ) : base(options, logReaderFactory, persistedBookmarkFactory, fileManager)
25 | {
26 | _delegator = delegator;
27 | }
28 |
29 | public void ShipIt()
30 | {
31 | base.ShipLogs();
32 | }
33 |
34 | protected override string PrepareRecord(MemoryStream stream)
35 | {
36 | return _delegator.PrepareRecord(stream);
37 | }
38 |
39 | protected override string SendRecords(List records, out bool successful)
40 | {
41 | return _delegator.SendRecords(records, out successful);
42 | }
43 |
44 | protected override void HandleError(string response, int originalRecordCount)
45 | {
46 | _delegator.HandleError(response, originalRecordCount);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/WhenBookmarkCannotBeCreated.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 |
3 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
4 | {
5 | class WhenBookmarkCannotBeCreated : HttpLogShipperBaseTestBase
6 | {
7 | [Test]
8 | public void ThenGracefullyComplete()
9 | {
10 | GivenPersistedBookmarkIsLocked();
11 |
12 | WhenLogShipperIsCalled();
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/WhenLogFilesFound.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using NUnit.Framework;
4 | using Ploeh.AutoFixture;
5 | using Shouldly;
6 |
7 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
8 | {
9 | class WhenLogFilesFound : HttpLogShipperBaseTestBase
10 | {
11 | [Test]
12 | public void WithBookmarkIsGreaterThanAllFiles_ThenFilesAreDeleted()
13 | {
14 | GivenLogFilesInDirectory();
15 | Array.ForEach(LogFiles, GivenFileDeleteSucceeds);
16 | var bookmarkedFile = LogFiles.Max() + "z";
17 |
18 | GivenPersistedBookmark(bookmarkedFile, Fixture.Create());
19 |
20 | WhenLogShipperIsCalled();
21 |
22 | LogFiles.ShouldBeEmpty("No one shall remain!");
23 | }
24 |
25 | [Test]
26 | public void WithBookmarkedLogCannotBeOpened_ThenPreviousFilesAreDeletedButNotLast()
27 | {
28 | GivenLogFilesInDirectory();
29 | Array.ForEach(LogFiles.Take(LogFiles.Length - 1).ToArray(), GivenFileDeleteSucceeds);
30 | var bookmarkedFile = LogFiles.Last();
31 | var bookmarkedPosition = Fixture.Create();
32 |
33 | GivenPersistedBookmark(bookmarkedFile, bookmarkedPosition);
34 | GivenLogReaderCreateIOError(CurrentLogFileName, CurrentLogFilePosition);
35 |
36 | WhenLogShipperIsCalled();
37 |
38 | this.ShouldSatisfyAllConditions(
39 | () => CurrentLogFileName.ShouldBe(bookmarkedFile, "Bookmarked log file name should not change"),
40 | () => CurrentLogFilePosition.ShouldBe(bookmarkedPosition, "Bookmarked position should not change"),
41 | () => LogFiles.ShouldBe(new[] { bookmarkedFile }, "Only one shall remain!")
42 | );
43 | }
44 |
45 | [Test]
46 | public void WithBookmarkedLogAtTheEnd_ThenPreviousFilesAreDeletedButNotLast()
47 | {
48 | GivenLogFilesInDirectory();
49 | Array.ForEach(LogFiles.Take(LogFiles.Length - 1).ToArray(), GivenFileDeleteSucceeds);
50 |
51 | var bookmarkedFile = LogFiles.Last();
52 | var bookmarkedPosition = Fixture.Create();
53 | GivenPersistedBookmark(bookmarkedFile, bookmarkedPosition);
54 | GivenLogReader(CurrentLogFileName, CurrentLogFilePosition, 0);
55 |
56 | WhenLogShipperIsCalled();
57 |
58 | this.ShouldSatisfyAllConditions(
59 | () => CurrentLogFileName.ShouldBe(bookmarkedFile, "Bookmarked log file name should not change"),
60 | () => CurrentLogFilePosition.ShouldBe(bookmarkedPosition, "Bookmarked position should not change"),
61 | () => LogFiles.ShouldBe(new[] { bookmarkedFile }, "Only one shall remain!")
62 | );
63 | }
64 |
65 | [Test]
66 | public void WithBookmarkedLogAtTheEndOfFirstFile_ThenAllNextFilesAreRead()
67 | {
68 | GivenLogFilesInDirectory(files: 2);
69 | var initialFile = LogFiles[0];
70 | var otherFile = LogFiles[1];
71 | var batchCount = Fixture.Create();
72 | var otherFileLength = BatchPostingLimit * batchCount;
73 |
74 | GivenFileDeleteSucceeds(initialFile);
75 | GivenPersistedBookmark(initialFile, Fixture.Create());
76 |
77 | GivenLockedFileLength(initialFile, length: CurrentLogFilePosition);
78 | GivenLockedFileLength(otherFile, length: otherFileLength);
79 |
80 | GivenLogReader(initialFile, length: CurrentLogFilePosition, maxStreams: int.MaxValue);
81 | GivenLogReader(otherFile, length: otherFileLength, maxStreams: int.MaxValue);
82 |
83 | GivenSendIsSuccessful();
84 |
85 | WhenLogShipperIsCalled();
86 |
87 | this.ShouldSatisfyAllConditions(
88 | () => LogFiles.ShouldBe(new[] { otherFile }, "Only one shall remain!"),
89 | () => CurrentLogFileName.ShouldBe(otherFile),
90 | () => CurrentLogFilePosition.ShouldBe(otherFileLength),
91 | () => SentBatches.ShouldBe(batchCount),
92 | () => SentRecords.ShouldBe(otherFileLength)
93 | );
94 | }
95 |
96 | [Test]
97 | public void WithFailureLockingFileAndGettingLength_ThenProcessingStopsInTheEndOfTheFile()
98 | {
99 | GivenLogFilesInDirectory(files: 2);
100 | var initialFile = LogFiles[0];
101 | var initialPosition = Fixture.Create();
102 | var otherFile = LogFiles[1];
103 |
104 | GivenPersistedBookmark(initialFile, initialPosition);
105 | GivenFileCannotBeLocked(initialFile);
106 | GivenLogReader(initialFile, length: initialPosition, maxStreams: 0);
107 |
108 | WhenLogShipperIsCalled();
109 |
110 | this.ShouldSatisfyAllConditions(
111 | () => LogFiles.ShouldBe(new[] { initialFile, otherFile }, "No files should be removed."),
112 | () => CurrentLogFileName.ShouldBe(initialFile),
113 | () => CurrentLogFilePosition.ShouldBe(initialPosition)
114 | );
115 | }
116 |
117 | [Test]
118 | public void WithSendFailure_ThenBookmarkIsNotChanged()
119 | {
120 | GivenLogFilesInDirectory(files: 2);
121 | var allFiles = LogFiles.ToArray();
122 | var initialFile = LogFiles[0];
123 |
124 | GivenPersistedBookmark(initialFile, 0);
125 | GivenLogReader(initialFile, length: BatchPostingLimit, maxStreams: BatchPostingLimit);
126 | GivenSendIsFailed();
127 |
128 | WhenLogShipperIsCalled();
129 |
130 | LogFiles.ShouldBe(allFiles, "Nothing shall be deleted.");
131 |
132 | this.ShouldSatisfyAllConditions(
133 | () => CurrentLogFileName.ShouldBe(initialFile),
134 | () => CurrentLogFilePosition.ShouldBe(0),
135 | () => SentBatches.ShouldBe(0),
136 | () => SentRecords.ShouldBe(0),
137 | () => FailedBatches.ShouldBe(1),
138 | () => FailedRecords.ShouldBe(BatchPostingLimit)
139 | );
140 | }
141 |
142 | [Test]
143 | public void WhenLogFileSizeIsLessThanBookmarkPosition_ThenFileIsReadFromTheBeginning()
144 | {
145 | GivenLogFilesInDirectory(1);
146 | var initialFile = LogFiles[0];
147 | var batchesCount = Fixture.Create();
148 | var realFileLength = batchesCount * BatchPostingLimit;
149 | var bookmarkedPosition = realFileLength + Fixture.Create();
150 |
151 | GivenPersistedBookmark(initialFile, position: bookmarkedPosition);
152 | GivenLockedFileLength(initialFile, length: realFileLength);
153 | GivenLogReader(initialFile, length: realFileLength, maxStreams: int.MaxValue);
154 | GivenSendIsSuccessful();
155 |
156 | WhenLogShipperIsCalled();
157 |
158 | this.ShouldSatisfyAllConditions(
159 | () => CurrentLogFileName.ShouldBe(initialFile),
160 | () => CurrentLogFilePosition.ShouldBe(realFileLength),
161 | () => SentBatches.ShouldBe(batchesCount),
162 | () => SentRecords.ShouldBe(realFileLength)
163 | );
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/HttpLogShipperTests/WhenNoLogFilesFound.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Ploeh.AutoFixture;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.HttpLogShipperTests
6 | {
7 | class WhenNoLogFilesFound : HttpLogShipperBaseTestBase
8 | {
9 | [Test]
10 | public void AndBookmarkHasNoData()
11 | {
12 | GivenPersistedBookmark(logFileName: null, position: 0);
13 | GivenLogFilesInDirectory(files: 0);
14 |
15 | WhenLogShipperIsCalled();
16 |
17 | this.ShouldSatisfyAllConditions(
18 | "Shipper does not progress",
19 | () => CurrentLogFileName.ShouldBeNull(),
20 | () => CurrentLogFilePosition.ShouldBe(0)
21 | );
22 | }
23 |
24 | [Test]
25 | public void AndBookmarkHasData()
26 | {
27 | GivenPersistedBookmark(logFileName: Fixture.Create(), position: Fixture.Create());
28 | GivenLogFilesInDirectory(files: 0);
29 |
30 | WhenLogShipperIsCalled();
31 |
32 | this.ShouldSatisfyAllConditions(
33 | "Shipper does not progress and resets bookmark data",
34 | () => CurrentLogFileName.ShouldBeNull(),
35 | () => CurrentLogFilePosition.ShouldBe(0)
36 | );
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Integration/DurableKinesisFirehoseSinkTests/DurableKinesisFirehoseSinkTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Amazon.KinesisFirehose;
7 | using Amazon.KinesisFirehose.Model;
8 | using Moq;
9 | using NUnit.Framework;
10 | using Ploeh.AutoFixture;
11 | using Serilog.Core;
12 | using Serilog.Sinks.Amazon.Kinesis.Common;
13 | using Serilog.Sinks.Amazon.Kinesis.Firehose;
14 |
15 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.Integration.DurableKinesisFirehoseSinkTests
16 | {
17 | [TestFixture(Category = TestFixtureCategory.Integration)]
18 | abstract class DurableKinesisFirehoseSinkTestBase
19 | {
20 | protected Fixture Fixture { get; private set; }
21 | protected Logger Logger { get; private set; }
22 | protected IAmazonKinesisFirehose Client { get { return ClientMock.Object; } }
23 | protected Mock ClientMock { get; private set; }
24 | protected EventHandler OnLogSendError { get; private set; }
25 | protected string LogPath { get; private set; }
26 | protected string BufferBaseFileName { get; private set; }
27 | protected string StreamName { get; private set; }
28 | protected TimeSpan ThrottleTime { get; private set; }
29 | protected MemoryStream DataSent { get; private set; }
30 |
31 | [TestFixtureSetUp]
32 | public void TestFixtureSetUp()
33 | {
34 | Fixture = new Fixture();
35 | LogPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
36 | Directory.CreateDirectory(LogPath);
37 |
38 | BufferBaseFileName = Path.Combine(LogPath, "prefix");
39 | StreamName = Fixture.Create();
40 | ThrottleTime = TimeSpan.FromSeconds(2);
41 | DataSent = new MemoryStream();
42 | }
43 |
44 | protected void GivenKinesisClient()
45 | {
46 | ClientMock = new Mock(MockBehavior.Loose);
47 | ClientMock.Setup(
48 | x => x.PutRecordBatchAsync(It.IsAny(), It.IsAny())
49 | )
50 | .Callback((PutRecordBatchRequest request, CancellationToken token) =>
51 | {
52 | request.Records.ForEach(r => r.Data.WriteTo(DataSent));
53 | })
54 | .Returns(Task.FromResult(new PutRecordBatchResponse() { FailedPutCount = 0 }));
55 | }
56 |
57 | protected void WhenLoggerCreated()
58 | {
59 | var loggerConfig = new LoggerConfiguration()
60 | .WriteTo.Trace()
61 | .MinimumLevel.Verbose();
62 |
63 | loggerConfig.WriteTo.AmazonKinesisFirehose(
64 | kinesisFirehoseClient: Client,
65 | streamName: StreamName,
66 | period: ThrottleTime,
67 | bufferBaseFilename: BufferBaseFileName,
68 | onLogSendError: OnLogSendError
69 | );
70 |
71 | Logger = loggerConfig.CreateLogger();
72 | }
73 |
74 | [TestFixtureTearDown]
75 | public void TestFixtureTearDown()
76 | {
77 | Directory.Delete(LogPath, true);
78 | DataSent?.Dispose();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Integration/DurableKinesisFirehoseSinkTests/WhenLogAndWaitEnough.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Threading;
5 | using NUnit.Framework;
6 | using Ploeh.AutoFixture;
7 | using Shouldly;
8 |
9 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.Integration.DurableKinesisFirehoseSinkTests
10 | {
11 | class WhenLogAndWaitEnough : DurableKinesisFirehoseSinkTestBase
12 | {
13 | [Test]
14 | public void ThenAllDataWillBeSent()
15 | {
16 | GivenKinesisClient();
17 | WhenLoggerCreated();
18 |
19 | var messages = Fixture.CreateMany(100).ToList();
20 | foreach (var message in messages)
21 | {
22 | Logger.Information(message);
23 | Thread.Sleep(TimeSpan.FromMilliseconds(ThrottleTime.TotalMilliseconds / 30));
24 | }
25 |
26 | Thread.Sleep(ThrottleTime.Add(ThrottleTime));
27 | ((IDisposable)Logger)?.Dispose();
28 | DataSent.Position = 0;
29 | var data = new StreamReader(DataSent).ReadToEnd();
30 |
31 | messages.ShouldAllBe(msg => data.Contains(msg));
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Integration/DurableKinesisSinkTests/DurableKinesisSinkTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Amazon.Kinesis;
7 | using Amazon.Kinesis.Model;
8 | using Moq;
9 | using NUnit.Framework;
10 | using Ploeh.AutoFixture;
11 | using Serilog.Core;
12 | using Serilog.Sinks.Amazon.Kinesis.Common;
13 | using Serilog.Sinks.Amazon.Kinesis.Stream;
14 |
15 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.Integration.DurableKinesisSinkTests
16 | {
17 | [TestFixture(Category = TestFixtureCategory.Integration)]
18 | abstract class DurableKinesisSinkTestBase
19 | {
20 | protected Fixture Fixture { get; private set; }
21 | protected Logger Logger { get; private set; }
22 | protected IAmazonKinesis Client { get { return ClientMock.Object; } }
23 | protected Mock ClientMock { get; private set; }
24 | protected EventHandler OnLogSendError { get; private set; }
25 | protected string LogPath { get; private set; }
26 | protected string BufferBaseFileName { get; private set; }
27 | protected string StreamName { get; private set; }
28 | protected TimeSpan ThrottleTime { get; private set; }
29 | protected MemoryStream DataSent { get; private set; }
30 |
31 | [TestFixtureSetUp]
32 | public void TestFixtureSetUp()
33 | {
34 | Fixture = new Fixture();
35 | LogPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
36 | Directory.CreateDirectory(LogPath);
37 |
38 | BufferBaseFileName = Path.Combine(LogPath, "prefix");
39 | StreamName = Fixture.Create();
40 | ThrottleTime = TimeSpan.FromSeconds(2);
41 | DataSent = new MemoryStream();
42 | }
43 |
44 | protected void GivenKinesisClient()
45 | {
46 | ClientMock = new Mock(MockBehavior.Loose);
47 | ClientMock.Setup(
48 | x => x.PutRecordsAsync(It.IsAny(), It.IsAny())
49 | )
50 | .Callback((PutRecordsRequest request, CancellationToken token) =>
51 | {
52 | request.Records.ForEach(r => r.Data.WriteTo(DataSent));
53 | })
54 | .Returns(Task.FromResult(new PutRecordsResponse() { FailedRecordCount = 0 }));
55 |
56 | }
57 |
58 | protected void WhenLoggerCreated()
59 | {
60 | var loggerConfig = new LoggerConfiguration()
61 | .WriteTo.Trace()
62 | .MinimumLevel.Verbose();
63 |
64 | loggerConfig.WriteTo.AmazonKinesis(
65 | kinesisClient: Client,
66 | streamName: StreamName,
67 | period: ThrottleTime,
68 | bufferBaseFilename: BufferBaseFileName,
69 | onLogSendError: OnLogSendError
70 | );
71 |
72 | Logger = loggerConfig.CreateLogger();
73 | }
74 |
75 | [TestFixtureTearDown]
76 | public void TestFixtureTearDown()
77 | {
78 | Directory.Delete(LogPath, true);
79 | DataSent?.Dispose();
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Integration/DurableKinesisSinkTests/WhenLogAndWaitEnough.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading;
6 | using NUnit.Framework;
7 | using Ploeh.AutoFixture;
8 | using Shouldly;
9 |
10 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.Integration.DurableKinesisSinkTests
11 | {
12 | class WhenLogAndWaitEnough : DurableKinesisSinkTestBase
13 | {
14 | [Test]
15 | public void ThenAllDataWillBeSent()
16 | {
17 | GivenKinesisClient();
18 | WhenLoggerCreated();
19 |
20 | var messages = Fixture.CreateMany(100).ToList();
21 | foreach (var message in messages)
22 | {
23 | Logger.Information(message);
24 | Thread.Sleep(TimeSpan.FromMilliseconds(ThrottleTime.TotalMilliseconds / 30));
25 | }
26 |
27 | Thread.Sleep(ThrottleTime.Add(ThrottleTime));
28 | Logger.Dispose();
29 | DataSent.Position = 0;
30 | var data = new StreamReader(DataSent).ReadToEnd();
31 |
32 | messages.ShouldAllBe(msg => data.Contains(msg));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogReaderTests/LogReaderTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using NUnit.Framework;
7 | using Ploeh.AutoFixture;
8 | using Serilog.Sinks.Amazon.Kinesis.Common;
9 |
10 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogReaderTests
11 | {
12 | [TestFixture]
13 | abstract class LogReaderTestBase
14 | {
15 | private Random _random = new Random();
16 | protected Fixture Fixture { get; } = new Fixture();
17 | protected ILogReader Target { get; private set; }
18 | protected long InitialPosition { get; private set; }
19 | protected string LogFileName { get; private set; }
20 | protected byte[] RawContent { get; private set; }
21 | protected string StringContent { get; private set; }
22 | protected string[] NormalisedContent { get; private set; }
23 | protected MemoryStream[] ReadContent { get; private set; }
24 |
25 | [TearDown]
26 | public void TearDown()
27 | {
28 | if (Target != null)
29 | {
30 | Target.Dispose();
31 | }
32 | if (!string.IsNullOrEmpty(LogFileName))
33 | {
34 | File.Delete(LogFileName);
35 | }
36 | if (ReadContent != null)
37 | {
38 | foreach (var memoryStream in ReadContent)
39 | {
40 | memoryStream.Dispose();
41 | }
42 | ReadContent = null;
43 | }
44 | }
45 |
46 | protected void GivenLogFileDoesNotExist()
47 | {
48 | LogFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
49 | }
50 |
51 | protected void GivenNoContent()
52 | {
53 | NormalisedContent = new string[0];
54 | StringContent = string.Empty;
55 | RawContent = new byte[0];
56 | }
57 |
58 |
59 |
60 | private readonly char[] WhiteSpaceChars = GetWhiteSpaceChars().ToArray();
61 |
62 | private static IEnumerable GetWhiteSpaceChars()
63 | {
64 | for (char c = (char)0; c < 128; c++) // not all unicode characters, just ASCII subset
65 | {
66 | if ((char.IsControl(c) || char.IsWhiteSpace(c)) && c != '\r' && c != '\n')
67 | {
68 | yield return c;
69 | }
70 | }
71 | }
72 |
73 | private string CreateRandomWhiteSpaceString(int minLength, int maxLength)
74 | {
75 | var len = _random.Next(minLength, maxLength + 1);
76 | return new string(
77 | Enumerable.Range(0, len).Select(_ => WhiteSpaceChars[_random.Next(WhiteSpaceChars.Length)]).ToArray()
78 | );
79 | }
80 |
81 | protected void GivenRandomTextContent(string lineBreak, int emptyLinesBetweenLines = 0, int leadingEmptyLines = 0, int trailingEmptyLines = 0)
82 | {
83 | NormalisedContent = Fixture.CreateMany(100).ToArray();
84 |
85 | var prefix = Enumerable.Range(0, leadingEmptyLines).Select(_ => CreateRandomWhiteSpaceString(0, 5));
86 | var suffix = Enumerable.Range(0, trailingEmptyLines).Select(_ => CreateRandomWhiteSpaceString(0, 5));
87 |
88 | Func createEmptyLines = () => string.Join(lineBreak,
89 | Enumerable.Range(0, emptyLinesBetweenLines).Select(_ => CreateRandomWhiteSpaceString(0, 5)));
90 |
91 | var lines = NormalisedContent.Select(s =>
92 | {
93 | var emptyLines = createEmptyLines();
94 | if (!string.IsNullOrEmpty(emptyLines))
95 | emptyLines = lineBreak + emptyLines;
96 |
97 | return string.Concat(
98 | CreateRandomWhiteSpaceString(0, 5),
99 | s,
100 | emptyLines
101 | );
102 | });
103 |
104 | StringContent = string.Join(lineBreak, prefix.Concat(lines).Concat(suffix));
105 | }
106 |
107 | protected void GivenUTF8WithBOMEncoding()
108 | {
109 | var encoding = new UTF8Encoding(true);
110 | RawContent = encoding.GetBytes(StringContent);
111 | }
112 |
113 | protected void GivenUTF8NoBOMEcoding()
114 | {
115 | var encoding = new UTF8Encoding(false);
116 | RawContent = encoding.GetBytes(StringContent);
117 | }
118 |
119 | protected void GivenRandomBytesContent()
120 | {
121 | RawContent = Fixture.CreateMany(10000).ToArray();
122 | }
123 |
124 | protected void GivenLogFileExistWithContent()
125 | {
126 | LogFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
127 | File.WriteAllBytes(LogFileName, RawContent);
128 | }
129 |
130 | protected void GivenInitialPosition(long position)
131 | {
132 | InitialPosition = position;
133 | }
134 |
135 | protected void WhenLogReaderIsCreated()
136 | {
137 | Target = LogReader.Create(LogFileName, InitialPosition);
138 | }
139 |
140 | protected void WhenAllDataIsRead()
141 | {
142 | var content = new List();
143 | do
144 | {
145 | var stream = Target.ReadLine();
146 | if (stream.Length == 0) { stream.Dispose(); break; }
147 | content.Add(stream);
148 | } while (true);
149 | ReadContent = content.ToArray();
150 | }
151 |
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogReaderTests/WhenLogFileExists.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Text;
3 | using NUnit.Framework;
4 | using Ploeh.AutoFixture;
5 | using Shouldly;
6 |
7 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogReaderTests
8 | {
9 | class WhenLogFileExists : LogReaderTestBase
10 | {
11 | [Test]
12 | public void WithNoContent_ThenNothingIsRead()
13 | {
14 | GivenNoContent();
15 | GivenUTF8NoBOMEcoding();
16 | GivenLogFileExistWithContent();
17 | GivenInitialPosition(0);
18 |
19 | WhenLogReaderIsCreated();
20 | WhenAllDataIsRead();
21 |
22 | this.ShouldSatisfyAllConditions(
23 | () => Target.Position.ShouldBe(0),
24 | () => ReadContent.ShouldBeEmpty()
25 | );
26 | }
27 |
28 | [Test]
29 | public void WithInitialPositionBeyondFileEnd_ThenPositionIsSetToLogLength()
30 | {
31 | GivenRandomTextContent("\n");
32 | GivenUTF8WithBOMEncoding();
33 | GivenLogFileExistWithContent();
34 | GivenInitialPosition(RawContent.Length + Fixture.Create());
35 |
36 | WhenLogReaderIsCreated();
37 |
38 | Target.Position.ShouldBe(RawContent.Length);
39 | }
40 |
41 | [Test]
42 | public void WithInitialPositionAtFileEnd_ThenNothingIsRead()
43 | {
44 | GivenRandomTextContent("\n");
45 | GivenUTF8WithBOMEncoding();
46 | GivenLogFileExistWithContent();
47 | GivenInitialPosition(RawContent.Length + Fixture.Create());
48 |
49 | WhenLogReaderIsCreated();
50 | WhenAllDataIsRead();
51 |
52 | this.ShouldSatisfyAllConditions(
53 | () => Target.Position.ShouldBe(RawContent.Length),
54 | () => ReadContent.ShouldBeEmpty()
55 | );
56 | }
57 |
58 | [TestCase("\n", 0, 0, 0)]
59 | [TestCase("\r\n", 0, 0, 0)]
60 | [TestCase("\n\r", 0, 0, 0)]
61 | [TestCase("\r", 0, 0, 0)]
62 | [TestCase("\r\n", 3, 1, 1)]
63 | [TestCase("\n", 5, 3, 3)]
64 | public void WithRandomTextContentNoBOM_ThenItCanBeRead(
65 | string lineBreak,
66 | int emptyLinesBetweenLines,
67 | int leadingEmptyLines,
68 | int trailingEmptyLines
69 | )
70 | {
71 | GivenRandomTextContent(lineBreak, emptyLinesBetweenLines, leadingEmptyLines, trailingEmptyLines);
72 | GivenUTF8NoBOMEcoding();
73 | GivenLogFileExistWithContent();
74 | GivenInitialPosition(0);
75 |
76 | WhenLogReaderIsCreated();
77 | WhenAllDataIsRead();
78 |
79 | Target.Position.ShouldBe(RawContent.Length);
80 | ReadContent.Length.ShouldBe(NormalisedContent.Length);
81 | ReadContent.Select(s => Encoding.UTF8.GetString(s.ToArray())).ShouldBe(NormalisedContent);
82 | }
83 |
84 | [TestCase("\n", 0, 0, 0)]
85 | [TestCase("\r", 0, 1, 1)]
86 | [TestCase("\r\n", 2, 10, 100)]
87 | [TestCase("\n\r", 3, 1, 0)]
88 | public void WithRandomTextContentAndBOM_ThenItCanBeRead(
89 | string lineBreak,
90 | int emptyLinesBetweenLines,
91 | int leadingEmptyLines,
92 | int trailingEmptyLines
93 | )
94 | {
95 | GivenRandomTextContent(lineBreak, emptyLinesBetweenLines, leadingEmptyLines, trailingEmptyLines);
96 | GivenUTF8WithBOMEncoding();
97 | GivenLogFileExistWithContent();
98 | GivenInitialPosition(0);
99 |
100 | WhenLogReaderIsCreated();
101 | WhenAllDataIsRead();
102 |
103 | this.ShouldSatisfyAllConditions(
104 | () => Target.Position.ShouldBe(RawContent.Length),
105 | () => ReadContent.Select(s => Encoding.UTF8.GetString(s.ToArray())).ShouldBe(NormalisedContent)
106 | );
107 | }
108 |
109 | [Test]
110 | public void WithRandomBinaryContent_ThenStillCanRead()
111 | {
112 | GivenRandomBytesContent();
113 | GivenLogFileExistWithContent();
114 | GivenInitialPosition(0);
115 |
116 | WhenLogReaderIsCreated();
117 | WhenAllDataIsRead();
118 |
119 | ReadContent.ShouldNotBeEmpty();
120 | }
121 | }
122 | }
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogReaderTests/WhenNoLogFileExists.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NUnit.Framework;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogReaderTests
6 | {
7 | class WhenNoLogFileExists : LogReaderTestBase
8 | {
9 | [Test]
10 | public void ThenExceptionIsThrown()
11 | {
12 | GivenLogFileDoesNotExist();
13 | GivenInitialPosition(0);
14 |
15 | Should.Throw(
16 | () => WhenLogReaderIsCreated()
17 | );
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogShipperFileManagerTests/FileTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using NUnit.Framework;
4 | using Ploeh.AutoFixture;
5 | using Serilog.Sinks.Amazon.Kinesis.Common;
6 |
7 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogShipperFileManagerTests
8 | {
9 | [TestFixture]
10 | abstract class FileTestBase
11 | {
12 | protected ILogShipperFileManager Target { get; private set; }
13 | protected string FileName { get; private set; }
14 | protected Fixture Fixture { get; private set; }
15 |
16 | [TestFixtureSetUp]
17 | public void TestFixtureSetUp()
18 | {
19 | Fixture = new Fixture();
20 | Target = new LogShipperFileManager();
21 | }
22 |
23 | [SetUp]
24 | public void SetUp()
25 | {
26 | FileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
27 | }
28 |
29 | [TearDown]
30 | public void TearDown()
31 | {
32 | File.Delete(FileName);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogShipperFileManagerTests/WhenGetFileLengthExclusiveAccess.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NUnit.Framework;
3 | using Shouldly;
4 | using Ploeh.AutoFixture;
5 |
6 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogShipperFileManagerTests
7 | {
8 | class WhenGetFileLengthExclusiveAccess : FileTestBase
9 | {
10 | [Test]
11 | public void GivenFileDoesNotExist_ThenIOException()
12 | {
13 | Should.Throw(
14 | () => Target.GetFileLengthExclusiveAccess(FileName)
15 | );
16 | }
17 |
18 | [Test]
19 | public void GivenFileExistsAndNotOpened_ThenCorrectLengthIsReturned()
20 | {
21 | var length = Fixture.Create();
22 | File.WriteAllBytes(FileName, new byte[length]);
23 | Target.GetFileLengthExclusiveAccess(FileName).ShouldBe(length);
24 | }
25 |
26 | [Test]
27 | public void GivenFileExistsAndIsOpenedForWriting_ThenIOException()
28 | {
29 | File.WriteAllBytes(FileName, new byte[Fixture.Create()]);
30 | using (File.Open(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
31 | {
32 | Should.Throw(
33 | () => Target.GetFileLengthExclusiveAccess(FileName)
34 | );
35 | }
36 | }
37 |
38 | [Test]
39 | public void GivenFileExistsAndOpenedForReading_ThenCorrectLengthIsReturned()
40 | {
41 | var length = Fixture.Create();
42 | File.WriteAllBytes(FileName, new byte[length]);
43 | using (File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
44 | {
45 | Target.GetFileLengthExclusiveAccess(FileName).ShouldBe(length);
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/LogShipperFileManagerTests/WhenLockAndDeleteFile.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NUnit.Framework;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.LogShipperFileManagerTests
6 | {
7 | [TestFixture]
8 | class WhenLockAndDeleteFile : FileTestBase
9 | {
10 | [Test]
11 | public void GivenFileDoesNotExist_ThenIOException()
12 | {
13 | Should.Throw(
14 | () => Target.LockAndDeleteFile(FileName)
15 | );
16 | }
17 |
18 | [TestCase(FileAccess.Read, FileShare.Read, TestName = "File is opened with Read access and Read share")]
19 | [TestCase(FileAccess.Write, FileShare.ReadWrite, TestName = "File is opened with Write access and Read/Write share")]
20 | [TestCase(FileAccess.Read, FileShare.Delete, TestName = "File is opened with Read access and Delete share")]
21 | [TestCase(FileAccess.Write, FileShare.Delete, TestName = "File is opened with Write access and Delete share")]
22 | public void GivenFileIsOpened_ThenIOException(
23 | FileAccess fileAccess,
24 | FileShare fileShare
25 | )
26 | {
27 | File.WriteAllBytes(FileName, new byte[42]);
28 | using (File.Open(FileName, FileMode.OpenOrCreate, fileAccess, fileShare))
29 | {
30 | Should.Throw(
31 | () => Target.LockAndDeleteFile(FileName)
32 | );
33 | }
34 | }
35 |
36 | [Test]
37 | public void GivenFileIsNotOpened_ThenDeleteSucceeds()
38 | {
39 | File.WriteAllBytes(FileName, new byte[42]);
40 | Target.LockAndDeleteFile(FileName);
41 |
42 | File.Exists(FileName).ShouldBeFalse();
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/PersistedBookmarkTests/PersistedBookmarkTestBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography;
4 | using NUnit.Framework;
5 | using Serilog.Sinks.Amazon.Kinesis.Common;
6 |
7 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.PersistedBookmarkTests
8 | {
9 | [TestFixture]
10 | abstract class PersistedBookmarkTestBase
11 | {
12 | protected IPersistedBookmark Target { get; private set; }
13 | protected string BookmarkFileName { get; private set; }
14 | protected string FileName { get; private set; }
15 | protected long Position { get; private set; }
16 |
17 | [TearDown]
18 | public void TearDown()
19 | {
20 | if (Target != null)
21 | {
22 | Target.Dispose();
23 | }
24 | if (!string.IsNullOrEmpty(BookmarkFileName))
25 | {
26 | File.Delete(BookmarkFileName);
27 | }
28 | }
29 |
30 | protected void GivenFileDoesNotExist()
31 | {
32 | BookmarkFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
33 | }
34 |
35 | protected void GivenFileExist()
36 | {
37 | BookmarkFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
38 | File.WriteAllText(BookmarkFileName, "");
39 | }
40 |
41 | protected void GivenFileContainsGarbage(int dataLength)
42 | {
43 | var bytes = new byte[dataLength];
44 | using (var rnd = RandomNumberGenerator.Create())
45 | {
46 | rnd.GetBytes(bytes);
47 | }
48 | File.WriteAllBytes(BookmarkFileName, bytes);
49 | }
50 |
51 | protected void WhenBookmarkIsCreated()
52 | {
53 | Target = PersistedBookmark.Create(BookmarkFileName);
54 | }
55 |
56 | protected void WhenBookmarkIsClosed()
57 | {
58 | Target.Dispose();
59 | Target = null;
60 | }
61 |
62 | protected void WhenUpdatedWithFileNameAndPosition()
63 | {
64 | FileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
65 | Position = new Random().Next(0, int.MaxValue);
66 | Target.UpdateFileNameAndPosition(FileName, Position);
67 | }
68 |
69 | protected void WhenUpdatedWithPosition()
70 | {
71 | Position = Target.Position + new Random().Next(0, int.MaxValue);
72 | Target.UpdatePosition(Position);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/PersistedBookmarkTests/WhenBookmarkExistsAndContainsGarbage.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Shouldly;
3 |
4 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.PersistedBookmarkTests
5 | {
6 | class WhenBookmarkExistsAndContainsGarbage : PersistedBookmarkTestBase
7 | {
8 | [TestCase(0)]
9 | [TestCase(1)]
10 | [TestCase(3)]
11 | [TestCase(4)]
12 | [TestCase(10)]
13 | [TestCase(64)]
14 | [TestCase(100)]
15 | public void ThenFileNameAndPositionAreEmpty(int garbageLength)
16 | {
17 | GivenFileExist();
18 | GivenFileContainsGarbage(garbageLength);
19 | WhenBookmarkIsCreated();
20 |
21 | Target.ShouldSatisfyAllConditions(
22 | () => Target.Position.ShouldBe(0),
23 | () => Target.FileName.ShouldBeNull()
24 | );
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/PersistedBookmarkTests/WhenFileNameAndPositionAreUpdated.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using Shouldly;
3 |
4 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.PersistedBookmarkTests
5 | {
6 | class WhenFileNameAndPositionAreUpdated : PersistedBookmarkTestBase
7 | {
8 | [TestCase(true, false, TestName = "Given empty file exists")]
9 | [TestCase(true, true, TestName = "Given bookmark with garbage exists")]
10 | [TestCase(false, false, TestName = "Given bookmark does not exist")]
11 | public void AfterOpening_ThenCanReadSameFileNameAndPosition(
12 | bool fileExists,
13 | bool containsGarbage
14 | )
15 | {
16 | if (fileExists) { GivenFileExist(); } else { GivenFileDoesNotExist(); }
17 | if (containsGarbage) GivenFileContainsGarbage(1000);
18 |
19 | WhenBookmarkIsCreated();
20 | WhenUpdatedWithFileNameAndPosition();
21 |
22 | Target.ShouldSatisfyAllConditions(
23 | () => Target.Position.ShouldBe(Position),
24 | () => Target.FileName.ShouldBe(FileName)
25 | );
26 | }
27 |
28 | [TestCase(true, false, TestName = "Given empty file exists")]
29 | [TestCase(true, true, TestName = "Given bookmark with garbage exists")]
30 | [TestCase(false, false, TestName = "Given bookmark does not exist")]
31 | public void AfterClosedAndReOpened_ThenCanReadSameFileNameAndPosition(
32 | bool fileExists,
33 | bool containsGarbage
34 | )
35 | {
36 | if (fileExists) { GivenFileExist(); } else { GivenFileDoesNotExist(); }
37 | if (containsGarbage) GivenFileContainsGarbage(1000);
38 |
39 | WhenBookmarkIsCreated();
40 | WhenUpdatedWithFileNameAndPosition();
41 | WhenBookmarkIsClosed();
42 | WhenBookmarkIsCreated();
43 |
44 | Target.ShouldSatisfyAllConditions(
45 | () => Target.Position.ShouldBe(Position),
46 | () => Target.FileName.ShouldBe(FileName)
47 | );
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/PersistedBookmarkTests/WhenNoBookmarkExists.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using NUnit.Framework;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.PersistedBookmarkTests
6 | {
7 | class WhenNoBookmarkExists : PersistedBookmarkTestBase
8 | {
9 | [Test]
10 | public void ThenFileIsCreated()
11 | {
12 | GivenFileDoesNotExist();
13 | WhenBookmarkIsCreated();
14 |
15 | File.Exists(BookmarkFileName).ShouldBeTrue();
16 | }
17 |
18 | [Test]
19 | public void ThenFileNameAndPositionAreEmpty()
20 | {
21 | GivenFileDoesNotExist();
22 | WhenBookmarkIsCreated();
23 |
24 | Target.ShouldSatisfyAllConditions(
25 | () => Target.Position.ShouldBe(0),
26 | () => Target.FileName.ShouldBeNull()
27 | );
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/PersistedBookmarkTests/WhenPositionIsUpdated.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using Shouldly;
4 |
5 | namespace Serilog.Sinks.Amazon.Kinesis.Tests.PersistedBookmarkTests
6 | {
7 | class WhenPositionIsUpdated : PersistedBookmarkTestBase
8 | {
9 | [Test]
10 | public void AfterFileNameAndPositionUpdated_ThenReadSameValue()
11 | {
12 | GivenFileDoesNotExist();
13 | WhenBookmarkIsCreated();
14 | WhenUpdatedWithFileNameAndPosition();
15 | WhenUpdatedWithPosition();
16 |
17 | Target.Position.ShouldBe(Position);
18 | }
19 |
20 | [Test]
21 | public void AfterOpeningNonEmptyBookmark_ThenReadTheSameValue()
22 | {
23 | GivenFileDoesNotExist();
24 | WhenBookmarkIsCreated();
25 | WhenUpdatedWithFileNameAndPosition();
26 | WhenBookmarkIsClosed();
27 | WhenBookmarkIsCreated();
28 |
29 | WhenUpdatedWithPosition();
30 |
31 | Target.Position.ShouldBe(Position);
32 | }
33 |
34 | [Test]
35 | public void ForEmptyBookmark_ThenThrowException()
36 | {
37 | GivenFileDoesNotExist();
38 | WhenBookmarkIsCreated();
39 |
40 | Should.Throw(
41 | () => WhenUpdatedWithPosition()
42 | );
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, " +
4 | "PublicKey=" +
5 | "0024000004800000940000000602000000240000525341310004000001000100"+
6 | "c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cf"+
7 | "c0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550"+
8 | "e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cc"+
9 | "eaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
10 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=00240000048000009400000006020000002" +
11 | "40000525341310004000001000100c547ca" +
12 | "c37abd99c8db225ef2f6c8a3602f3b3606c" +
13 | "c9891605d02baa56104f4cfc0734aa39b93" +
14 | "bf7852f7d9266654753cc297e7d2edfe0ba" +
15 | "c1cdcf9f717241550e0a7b191195b7667bb" +
16 | "4f64bcb8e2121380fd1d9d46ad2d92d2d15" +
17 | "605093924cceaf74c4861eff62abf69b929" +
18 | "1ed0a340e113be11e6a7d3113e92484cf70" +
19 | "45cc7")]
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Serilog.Sinks.Amazon.Kinesis.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {B26D2F37-234B-438D-84BE-83BF9E0A255C}
8 | Library
9 | Properties
10 | Serilog.Sinks.Amazon.Kinesis.Tests
11 | Serilog.Sinks.Amazon.Kinesis.Tests
12 | v4.5
13 | 512
14 | ../../assets/Serilog.snk
15 | true
16 |
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | false
27 | ../../assets/Serilog.snk
28 | true
29 |
30 |
31 | pdbonly
32 | true
33 | bin\Release\
34 | TRACE
35 | prompt
36 | 4
37 | false
38 | ../../assets/Serilog.snk
39 | true
40 |
41 |
42 | true
43 |
44 |
45 | Serilog.snk
46 |
47 |
48 |
49 | ..\..\packages\AWSSDK.Core.3.3.21.6\lib\net45\AWSSDK.Core.dll
50 |
51 |
52 | ..\..\packages\AWSSDK.Kinesis.3.3.4\lib\net45\AWSSDK.Kinesis.dll
53 |
54 |
55 | ..\..\packages\AWSSDK.KinesisFirehose.3.3.3\lib\net45\AWSSDK.KinesisFirehose.dll
56 |
57 |
58 | ..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll
59 |
60 |
61 | ..\..\packages\Moq.4.8.0\lib\net45\Moq.dll
62 |
63 |
64 | ..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll
65 |
66 |
67 | ..\..\packages\AutoFixture.3.51.0\lib\net40\Ploeh.AutoFixture.dll
68 |
69 |
70 | ..\..\packages\AutoFixture.AutoMoq.3.51.0\lib\net40\Ploeh.AutoFixture.AutoMoq.dll
71 |
72 |
73 | ..\..\packages\Serilog.2.6.0\lib\net45\Serilog.dll
74 |
75 |
76 | ..\..\packages\Serilog.Sinks.Trace.2.1.0\lib\net45\Serilog.Sinks.Trace.dll
77 | True
78 |
79 |
80 | ..\..\packages\Shouldly.2.8.3\lib\net40\Shouldly.dll
81 |
82 |
83 |
84 |
85 |
86 | ..\..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {9cddc147-93bb-47dc-899c-b41384d7ae23}
122 | Serilog.Sinks.Amazon.Kinesis
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
138 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Serilog.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serilog-archive/serilog-sinks-amazonkinesis/555981e1135a9ca804b843b8faf5a74cc7755412/tests/Serilog.Sinks.Amazon.Kinesis.Tests/Serilog.snk
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/TestFixtureCategory.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog.Sinks.Amazon.Kinesis.Tests
2 | {
3 | public static class TestFixtureCategory
4 | {
5 | public const string Unit = "Unit";
6 | public const string Integration = "Integration";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/Serilog.Sinks.Amazon.Kinesis.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------