├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── Delete-BIN-OBJ-Folders.bat
├── Directory.Build.props
├── Directory.Build.targets
├── LICENSE
├── NuGet.config
├── NuGetPackageVerifier.json
├── README.md
├── docs
├── .env
├── docker-compose.yml
├── elasticsearch.md
└── img
│ ├── kibana_empty.png
│ ├── kibana_index.png
│ ├── kibana_initdata.png
│ └── kinaba_discover.png
├── samples
└── SampleApp
│ ├── LoggerExtensions.cs
│ ├── Program.cs
│ ├── SampleApp.csproj
│ └── logging.json
├── src
├── Zero.Logging.Batching
│ ├── BatchLoggerConfigureOptions.cs
│ ├── BatchingLogger.cs
│ ├── BatchingLoggerOptions.cs
│ ├── BatchingLoggerProvider.cs
│ ├── LogMessage.cs
│ └── Zero.Logging.Batching.csproj
├── Zero.Logging.Elasticsearch
│ ├── ElasticsearchHelper.cs
│ ├── EsLogger.cs
│ ├── EsLoggerFactoryExtensions.cs
│ ├── EsLoggerOptions.cs
│ ├── EsLoggerOptionsSetup.cs
│ ├── EsLoggerProvider.cs
│ └── Zero.Logging.Elasticsearch.csproj
└── Zero.Logging.File
│ ├── FileLoggerFactoryExtensions.cs
│ ├── FileLoggerOptions.cs
│ ├── FileLoggerOptionsSetup.cs
│ ├── FileLoggerProvider.cs
│ ├── RollingIntervalEnum.cs
│ ├── RollingIntervalExtensions.cs
│ └── Zero.Logging.File.csproj
├── test
└── Zero.Logging.Tests
│ ├── BigDataTests.cs
│ ├── Zero.Logging.Tests.csproj
│ └── nlog.config
├── version.props
└── zero-logging.sln
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceRoot}/samples/SampleApp/bin/Debug/netcoreapp2.0/SampleApp.dll",
14 | "args": [],
15 | "cwd": "${workspaceRoot}/samples/SampleApp",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": ".NET Core Attach",
23 | "type": "coreclr",
24 | "request": "attach",
25 | "processId": "${command:pickProcess}"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 | "command": "dotnet",
4 | "isShellCommand": true,
5 | "args": [],
6 | "tasks": [
7 | {
8 | "taskName": "build",
9 | "args": [
10 | "${workspaceRoot}/samples/SampleApp/SampleApp.csproj"
11 | ],
12 | "isBuildCommand": true,
13 | "problemMatcher": "$msCompile"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/Delete-BIN-OBJ-Folders.bat:
--------------------------------------------------------------------------------
1 | @ECHO off
2 | cls
3 |
4 | ECHO Deleting all BIN and OBJ folders...
5 | ECHO.
6 |
7 | FOR /d /r . %%d in (bin,obj) DO (
8 | IF EXIST "%%d" (
9 | ECHO %%d | FIND /I "\node_modules\" > Nul && (
10 | ECHO.Skipping: %%d
11 | ) || (
12 | ECHO.Deleting: %%d
13 | rd /s/q "%%d"
14 | )
15 | )
16 | )
17 |
18 | ECHO.
19 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit.
20 | pause > nul
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Zero Logging
6 | RainingNight
7 | RainingNight
8 | Copyright © RainingNight
9 | https://raw.githubusercontent.com/RainingNight/zero-logging/dev/LICENSE
10 |
11 | MIT
12 |
16 | $(NoWarn);NU5125
17 |
18 | $(NoWarn);NU5105
19 |
20 | https://github.com/rainingnight/zero-logging
21 |
22 | https://github.com/rainingnight/zero-logging
23 | git
24 |
25 |
26 |
27 |
28 | true
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.1.3
4 | 2.2.0
5 | 2.0.3
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 RainingNight
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/NuGetPackageVerifier.json:
--------------------------------------------------------------------------------
1 | {
2 | "Default": {
3 | "rules": [
4 | "DefaultCompositeRule"
5 | ]
6 | }
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zero-logging
2 |
3 | Zero logger provider for [Microsoft.Extensions.Logging](https://github.com/aspnet/Logging), the logging subsystem used by ASP.NET Core.
4 |
5 | ## Logging in Elasticsearch
6 |
7 | PLEASE Read [Zero.Logging.Elasticsearch](https://github.com/RainingNight/zero-logging/blob/dev/docs/elasticsearch.md).
8 |
9 | ## Logging in File
10 |
11 | ### Install
12 |
13 | **First**, install the _Zero.Logging.File_ [NuGet package](https://www.nuget.org/packages/Zero.Logging.File) into your app:
14 |
15 | ```powershell
16 | dotnet add package Zero.Logging.File --version 1.0.0-alpha3-20180228
17 | ```
18 |
19 | ### Configure
20 |
21 | **Next**, add file section config in appsettings.json:
22 |
23 | ```json
24 | {
25 | "Logging": {
26 | "IncludeScopes": false,
27 | "Console": {
28 | "LogLevel": {
29 | "Default": "Warning"
30 | }
31 | },
32 | "File": {
33 | "LogLevel": {
34 | "Default": "Error"
35 | },
36 | "RollingInterval": "Minute"
37 | }
38 | }
39 | }
40 | ```
41 |
42 | **Finally**, in your application's _Program.cs_ file, configure _Zeor.Logging.File_ first:
43 |
44 | ```csharp
45 | public static IWebHost BuildWebHost(string[] args) =>
46 | WebHost.CreateDefaultBuilder(args)
47 | .ConfigureLogging((hostingContext, logging) =>
48 | {
49 | logging.AddFile();
50 | })
51 | .UseStartup()
52 | .Build();
53 | ```
54 |
55 | ### Demonstrate
56 |
57 | Call logging methods on that logger object:
58 |
59 | ```csharp
60 | public class ValuesController : Controller
61 | {
62 | private readonly ILogger _logger;
63 |
64 | public ValuesController(ILogger logger)
65 | {
66 | _logger = logger;
67 | }
68 |
69 | [HttpGet]
70 | public void Get()
71 | {
72 | _logger.LogTrace("Log Trace.");
73 | _logger.LogInformation("Log Information.");
74 | _logger.LogDebug("Log Debug.");
75 | try
76 | {
77 | throw new Exception("Boom");
78 | }
79 | catch (Exception ex)
80 | {
81 | _logger.LogCritical(1, ex, "Unexpected critical error starting application");
82 | _logger.LogError(1, ex, "Unexpected error");
83 | _logger.LogWarning(1, ex, "Unexpected warning");
84 | }
85 | }
86 | }
87 | ```
88 |
89 | That's it! With the level bumped up a little you will see log output like:
90 |
91 | ```text
92 | # logs/log-201802271502.txt
93 |
94 | 2018-02-27 15:02:40.608 +08:00 [Critical] WebApplication1.Controllers.ValuesController: Unexpected critical error starting application
95 | System.Exception: Boom
96 | at WebApplication1.Controllers.ValuesController.Get() in C:\Users\rainging\source\repos\WebApplication1\WebApplication1\Controllers\ValuesController.cs:line 28
97 | 2018-02-27 15:02:40.631 +08:00 [Error] WebApplication1.Controllers.ValuesController: Unexpected error
98 | System.Exception: Boom
99 | at WebApplication1.Controllers.ValuesController.Get() in C:\Users\rainging\source\repos\WebApplication1\WebApplication1\Controllers\ValuesController.cs:line 28
100 | ```
101 |
102 |
--------------------------------------------------------------------------------
/docs/.env:
--------------------------------------------------------------------------------
1 | TAG=6.2.2
2 | ELASTIC_VERSION=6.2.2
3 | ELASTIC_PASSWORD=Qwer1234
--------------------------------------------------------------------------------
/docs/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | elasticsearch:
4 | image: docker.elastic.co/elasticsearch/elasticsearch:${TAG}
5 | container_name: elasticsearch
6 | environment:
7 | - http.host=0.0.0.0
8 | - transport.host=127.0.0.1
9 | - ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
10 | ports:
11 | - 9200:9200
12 | networks:
13 | - stack
14 |
15 | kibana:
16 | image: docker.elastic.co/kibana/kibana:${TAG}
17 | container_name: kibana
18 | environment:
19 | - ELASTICSEARCH_USERNAME=kibana
20 | - ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
21 | ports:
22 | - 5601:5601
23 | networks:
24 | - stack
25 | depends_on:
26 | - elasticsearch
27 |
28 | networks:
29 | stack:
30 | driver: bridge
31 |
--------------------------------------------------------------------------------
/docs/elasticsearch.md:
--------------------------------------------------------------------------------
1 | # Logging in Elasticsearch with Kibana
2 |
3 | ## 使用Docker部署Elasticsearch和Kibana
4 |
5 | ELKstack是Elasticsearch、Logstash、Kibana三个开源软件的组合,是当今最为流行的统一日志分析平台。对于它们的介绍,网上非常之多,这里就不再多说。
6 |
7 | 在本文中只使用了`Elasticsearch`和`Kibana`,前者是分布式搜索系统,后者是一个可视化平台,使用docker来部署非常简单:
8 |
9 | ### 部署Elasticsearch
10 |
11 | 如下,绑定端口`9200`,并将容器命名为`elasticsearch`:
12 |
13 | ```bash
14 | docker run --name=elasticsearch -d -p 9200:9200 -e "http.host=0.0.0.0" -e "transport.host=127.0.0.1" docker.elastic.co/elasticsearch/elasticsearch:6.2.2
15 | ```
16 |
17 | 然后在浏览器中打开 [http://localhost:9200/](http://localhost:9200/),输出如下:
18 |
19 | ```json
20 | {
21 | "name": "qFQvLqr",
22 | "cluster_name": "docker-cluster",
23 | "cluster_uuid": "bdc5YhZlQHu0mCN7acNKBw",
24 | "version": {
25 | "number": "6.2.2",
26 | "build_hash": "10b1edd",
27 | "build_date": "2018-02-16T21:01:30.685723Z",
28 | "build_snapshot": false,
29 | "lucene_version": "7.2.1",
30 | "minimum_wire_compatibility_version": "5.6.0",
31 | "minimum_index_compatibility_version": "5.0.0"
32 | },
33 | "tagline": "You Know, for Search"
34 | }
35 | ```
36 |
37 | ### 部署Kibana
38 |
39 | Kibana的部署依赖于Elasticsearch:
40 |
41 | ```bash
42 | docker run --name=kibana --link=elasticsearch -d -p 5601:5601 docker.elastic.co/kibana/kibana:6.2.2
43 | ```
44 |
45 | 主要注意的是,在这里使用了`--link=elasticsearch`来链接到*elasticsearch*容器,如果要使用外部的elasticsearch服务,可以使用`-e "elasticsearch.url=http://changeme:9200"`来指定。
46 |
47 | 然后在浏览器中打开 [http://localhost:5601/](http://localhost:5601/):
48 |
49 | 
50 |
51 | 如上,部署成功,不过还没有任何数据。
52 |
53 | ### 使用docker-compose部署
54 |
55 | 当需要部署多个相关的服务时,更加推荐使用**docker-compose**来部署:
56 |
57 | 首先,我们创建一个`docker-compose.yml`文件:
58 |
59 | ```yml
60 | version: '3'
61 | services:
62 | elasticsearch:
63 | image: docker.elastic.co/elasticsearch/elasticsearch:${TAG}
64 | container_name: elasticsearch
65 | environment:
66 | - http.host=0.0.0.0
67 | - transport.host=127.0.0.1
68 | - ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
69 | ports:
70 | - 9200:9200
71 | networks:
72 | - stack
73 |
74 | kibana:
75 | image: docker.elastic.co/kibana/kibana:${TAG}
76 | container_name: kibana
77 | environment:
78 | - ELASTICSEARCH_USERNAME=kibana
79 | - ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
80 | ports:
81 | - 5601:5601
82 | networks:
83 | - stack
84 | depends_on:
85 | - elasticsearch
86 |
87 | networks:
88 | stack:
89 | driver: bridge
90 | ```
91 |
92 | 如上,我们定义了`TAG`和`ELASTIC_PASSWORD`两个环境变量,方便在部署时候灵活的指定版本号和密码。
93 |
94 | 为方便测试部署,我们可以定义一个默认的环境变量文件`.env`:
95 |
96 | ```env
97 | TAG=6.2.2
98 | ELASTIC_PASSWORD=Qwer1234
99 | ```
100 |
101 | 然后,直接运行如下命令即可:
102 |
103 | ```bash
104 | docker-compose up
105 | ```
106 |
107 | 接下来,将日志写入到Elasticsearch。
108 |
109 | ## 记录日志到Elasticsearch
110 |
111 | 我们创建一个 ASP.NET Core WebApi 项目,添加如下Package:
112 |
113 | ```bash
114 | dotnet add package Zero.Logging.Elasticsearch --version 1.0.0-alpha3-20180228
115 | ```
116 |
117 | ### 添加ElasticsearchProvider
118 |
119 | 然后在`Program.cs`文件中使用`AddElasticsearch`扩展方法为日志系统添加`ElasticsearchProvider`:
120 |
121 | ```csharp
122 | public static IWebHost BuildWebHost(string[] args) =>
123 | WebHost.CreateDefaultBuilder(args)
124 | .ConfigureLogging((hostingContext, logging) =>
125 | {
126 | logging.AddElasticsearch();
127 | })
128 | .UseStartup()
129 | .Build();
130 | ```
131 |
132 | ### 记录日志
133 |
134 | 对于日志的记录则不需要任何的修改:
135 |
136 | ```csharp
137 | public class ValuesController : Controller
138 | {
139 | private readonly ILogger _logger;
140 |
141 | public ValuesController(ILogger logger)
142 | {
143 | _logger = logger;
144 | }
145 |
146 | [HttpGet]
147 | public void Get()
148 | {
149 | _logger.LogTrace("Log Trace.");
150 | _logger.LogInformation("Log Information.");
151 | _logger.LogDebug("Log Debug.");
152 | try
153 | {
154 | throw new Exception("Boom");
155 | }
156 | catch (Exception ex)
157 | {
158 | _logger.LogCritical(1, ex, "Unexpected critical error starting application");
159 | _logger.LogError(1, ex, "Unexpected error");
160 | _logger.LogWarning(1, ex, "Unexpected warning");
161 | }
162 | }
163 | }
164 | ```
165 |
166 | ### 在Kibana查看
167 |
168 | 刷新浏览器,显示如下:
169 |
170 | 
171 |
172 | 在Index pattern中输入`logstash-*`,点击下一步:
173 |
174 | 
175 |
176 | 如上,选择`timestamp`,创建索引,最终显示如下:
177 |
178 | 
179 |
180 | ### 配置
181 |
182 | 如上一行代码,零配置完成Elasticsearch的写入,默认使用的Elasticsearch地址为`http://localhost:9200`,如果我们需要额外的配置也很简单,有如下两种方式:
183 |
184 | #### 使用配置文件进行配置
185 |
186 | 在`appsettings.json`中添加如下配置:
187 |
188 | ```json
189 | {
190 | "Logging": {
191 | "IncludeScopes": false,
192 | "Console": {
193 | "LogLevel": {
194 | "Default": "Warning"
195 | }
196 | },
197 | "Elasticsearch": {
198 | "LogLevel": {
199 | "Default": "Information"
200 | },
201 | "ElasticsearchUrl": "http://changeme:9200",
202 | "AutoRegisterTemplate": true
203 | }
204 | }
205 | }
206 | ```
207 |
208 | #### 使用代码进行配置
209 |
210 | ```csharp
211 | WebHost.CreateDefaultBuilder(args)
212 | .ConfigureLogging((hostingContext, logging) =>
213 | {
214 | logging.AddFile().AddElasticsearch(o =>
215 | {
216 | o.PipelineName = "http://changeme:9200";
217 | o.AutoRegisterTemplate = true;
218 | });
219 | })
220 | ```
221 |
222 | > 需要注意,如果使用代码的方式进行配置,则配置文件中的配置不再生效。
223 |
224 | 更多的配置信息参见 [EsLoggerOptions](https://github.com/RainingNight/zero-logging/blob/dev/src/Zero.Logging.Elasticsearch/EsLoggerOptions.cs)。
--------------------------------------------------------------------------------
/docs/img/kibana_empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RainingNight/zero-logging/873be46fd023ec467cfb2d6bafa5c07c784c7269/docs/img/kibana_empty.png
--------------------------------------------------------------------------------
/docs/img/kibana_index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RainingNight/zero-logging/873be46fd023ec467cfb2d6bafa5c07c784c7269/docs/img/kibana_index.png
--------------------------------------------------------------------------------
/docs/img/kibana_initdata.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RainingNight/zero-logging/873be46fd023ec467cfb2d6bafa5c07c784c7269/docs/img/kibana_initdata.png
--------------------------------------------------------------------------------
/docs/img/kinaba_discover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RainingNight/zero-logging/873be46fd023ec467cfb2d6bafa5c07c784c7269/docs/img/kinaba_discover.png
--------------------------------------------------------------------------------
/samples/SampleApp/LoggerExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace SampleApp
8 | {
9 | internal static class LoggerExtensions
10 | {
11 | private static Func _purchaseOrderScope;
12 | private static Action _programStarting;
13 | private static Action _programStopping;
14 |
15 | static LoggerExtensions()
16 | {
17 | _purchaseOrderScope = LoggerMessage.DefineScope("PO:{PurchaseOrder}");
18 | _programStarting = LoggerMessage.Define(LogLevel.Information, 1, "Starting at '{StartTime}' and 0x{Hello:X} is hex of 42");
19 | _programStopping = LoggerMessage.Define(LogLevel.Information, 2, "Stopping at '{StopTime}'");
20 | }
21 |
22 | public static IDisposable PurchaseOrderScope(this ILogger logger, string purchaseOrder)
23 | {
24 | return _purchaseOrderScope(logger, purchaseOrder);
25 | }
26 |
27 | public static void ProgramStarting(this ILogger logger, DateTimeOffset startTime, int hello, Exception exception = null)
28 | {
29 | _programStarting(logger, startTime, hello, exception);
30 | }
31 |
32 | public static void ProgramStopping(this ILogger logger, DateTimeOffset stopTime, Exception exception = null)
33 | {
34 | _programStopping(logger, stopTime, exception);
35 | }
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/samples/SampleApp/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.IO;
6 | using Microsoft.Extensions.Configuration;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Logging;
9 | using ILogger = Microsoft.Extensions.Logging.ILogger;
10 |
11 | namespace SampleApp
12 | {
13 | public class Program
14 | {
15 | private readonly ILogger _logger;
16 |
17 | public Program()
18 | {
19 | var loggingConfiguration = new ConfigurationBuilder()
20 | .SetBasePath(Directory.GetCurrentDirectory())
21 | .AddJsonFile("logging.json", optional: false, reloadOnChange: true)
22 | .Build();
23 |
24 | // A Web App based program would configure logging via the WebHostBuilder.
25 | // Create a logger factory with filters that can be applied across all logger providers.
26 | var serviceCollection = new ServiceCollection()
27 | .AddLogging(builder =>
28 | {
29 | builder
30 | .AddConfiguration(loggingConfiguration.GetSection("Logging"))
31 | .AddFilter("Microsoft", LogLevel.Warning)
32 | .AddFilter("System", LogLevel.Warning)
33 | .AddFilter("SampleApp.Program", LogLevel.Debug)
34 | //.AddConsole()
35 | .AddFile()
36 | .AddElasticsearch();
37 | });
38 |
39 | // providers may be added to a LoggerFactory before any loggers are created
40 |
41 |
42 | var serviceProvider = serviceCollection.BuildServiceProvider();
43 | // getting the logger using the class's name is conventional
44 | _logger = serviceProvider.GetRequiredService>();
45 | }
46 |
47 | public static void Main(string[] args)
48 | {
49 | new Program().Execute(args);
50 | }
51 |
52 | public void Execute(string[] args)
53 | {
54 | _logger.LogDebug("Begin logging....");
55 |
56 | _logger.LogInformation("Starting");
57 |
58 | var startTime = DateTimeOffset.Now;
59 | _logger.LogInformation(1, "Started at '{StartTime}' and 0x{Hello:X} is hex of 42", startTime, 42);
60 | // or
61 | _logger.ProgramStarting(startTime, 42);
62 |
63 | using (_logger.PurchaseOrderScope("00655321"))
64 | {
65 | try
66 | {
67 | throw new Exception("Boom");
68 | }
69 | catch (Exception ex)
70 | {
71 | _logger.LogCritical(1, ex, "Unexpected critical error starting application");
72 | _logger.LogError(1, ex, "Unexpected error");
73 | _logger.LogWarning(1, ex, "Unexpected warning");
74 | }
75 |
76 | using (_logger.BeginScope("Main"))
77 | {
78 |
79 | _logger.LogInformation("Waiting for user input");
80 |
81 | string input;
82 | do
83 | {
84 | Console.WriteLine("Enter some test to log more, or 'quit' to exit.");
85 | input = Console.ReadLine();
86 |
87 | _logger.LogInformation("User typed '{input}' on the command line", input);
88 | _logger.LogWarning("The time is now {Time}, it's getting late!", DateTimeOffset.Now);
89 | }
90 | while (input != "quit");
91 | }
92 | }
93 |
94 | var endTime = DateTimeOffset.Now;
95 | _logger.LogInformation(2, "Stopping at '{StopTime}'", endTime);
96 | // or
97 | _logger.ProgramStopping(endTime);
98 |
99 | _logger.LogInformation("Stopping");
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/samples/SampleApp/SampleApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2
5 | Exe
6 |
7 |
8 |
9 |
10 | PreserveNewest
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/samples/SampleApp/logging.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | },
8 | "Console": {
9 | "IncludeScopes": "true"
10 | },
11 | "File": {
12 | // 按分钟滚动写入
13 | "RollingInterval": "Minute",
14 | "LogLevel": {
15 | "Default": "Information"
16 | }
17 | },
18 | "Elasticsearch": {
19 | "ElasticsearchUrl": "http://localhost:9200",
20 | "UserName": "",
21 | "Password": ""
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/BatchLoggerConfigureOptions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Microsoft.Extensions.Options;
3 |
4 | namespace Zero.Logging.Batching
5 | {
6 | public class BatchLoggerConfigureOptions : IConfigureOptions
7 | {
8 | private readonly IConfiguration _configuration;
9 | private readonly string _isEnabledKey;
10 |
11 | public BatchLoggerConfigureOptions(IConfiguration configuration, string isEnabledKey)
12 | {
13 | _configuration = configuration;
14 | _isEnabledKey = isEnabledKey;
15 | }
16 |
17 | public void Configure(BatchingLoggerOptions options)
18 | {
19 | options.IsEnabled = TextToBoolean(_configuration.GetSection(_isEnabledKey)?.Value);
20 | }
21 |
22 | private static bool TextToBoolean(string text)
23 | {
24 | if (string.IsNullOrEmpty(text) ||
25 | !bool.TryParse(text, out var result))
26 | {
27 | result = false;
28 | }
29 |
30 | return result;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/BatchingLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace Zero.Logging.Batching
6 | {
7 | public class BatchingLogger : ILogger
8 | {
9 | private readonly BatchingLoggerProvider _provider;
10 | private readonly string _category;
11 |
12 | public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName)
13 | {
14 | _provider = loggerProvider;
15 | _category = categoryName;
16 | }
17 |
18 | public IDisposable BeginScope(TState state)
19 | {
20 | return null;
21 | }
22 |
23 | public bool IsEnabled(LogLevel logLevel)
24 | {
25 | return _provider.IsEnabled;
26 | }
27 |
28 | public void Log(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
29 | {
30 | if (!IsEnabled(logLevel))
31 | {
32 | return;
33 | }
34 |
35 | var builder = new StringBuilder();
36 | builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"));
37 | builder.Append(" [");
38 | builder.Append(logLevel.ToString());
39 | builder.Append("] ");
40 | builder.Append(_category);
41 | builder.Append(": ");
42 | builder.AppendLine(formatter(state, exception));
43 |
44 | if (exception != null)
45 | {
46 | builder.AppendLine(exception.ToString());
47 | }
48 |
49 | _provider.AddMessage(timestamp, builder.ToString());
50 | }
51 |
52 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
53 | {
54 | Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/BatchingLoggerOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Zero.Logging.Batching
4 | {
5 | public class BatchingLoggerOptions
6 | {
7 | private int? _batchSize = 32;
8 | private int? _backgroundQueueSize = 1000;
9 | private TimeSpan _flushPeriod = TimeSpan.FromSeconds(1);
10 |
11 | ///
12 | /// Gets or sets the period after which logs will be flushed to the store.
13 | ///
14 | public TimeSpan FlushPeriod
15 | {
16 | get { return _flushPeriod; }
17 | set
18 | {
19 | if (value <= TimeSpan.Zero)
20 | {
21 | throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
22 | }
23 | _flushPeriod = value;
24 | }
25 | }
26 |
27 | ///
28 | /// Gets or sets the maximum size of the background log message queue or null for no limit.
29 | /// After maximum queue size is reached log event sink would start blocking.
30 | /// Defaults to 1000.
31 | ///
32 | public int? BackgroundQueueSize
33 | {
34 | get { return _backgroundQueueSize; }
35 | set
36 | {
37 | if (value < 0)
38 | {
39 | throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BackgroundQueueSize)} must be non-negative.");
40 | }
41 | _backgroundQueueSize = value;
42 | }
43 | }
44 |
45 | ///
46 | /// Gets or sets a maximum number of events to include in a single batch or null for no limit.
47 | ///
48 | public int? BatchSize
49 | {
50 | get { return _batchSize; }
51 | set
52 | {
53 | if (value <= 0)
54 | {
55 | throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(BatchSize)} must be positive.");
56 | }
57 | _batchSize = value;
58 | }
59 | }
60 |
61 | ///
62 | /// Gets or sets value indicating if logger accepts and queues writes.
63 | ///
64 | public bool IsEnabled { get; set; } = true;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/BatchingLoggerProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace Zero.Logging.Batching
10 | {
11 | public abstract class BatchingLoggerProvider : ILoggerProvider
12 | {
13 | private readonly List _currentBatch = new List();
14 | private readonly TimeSpan _interval;
15 | private readonly int? _queueSize;
16 | private readonly int? _batchSize;
17 | private readonly IDisposable _optionsChangeToken;
18 |
19 | private BlockingCollection _messageQueue;
20 | private Task _outputTask;
21 | private CancellationTokenSource _cancellationTokenSource;
22 |
23 | protected BatchingLoggerProvider(IOptionsMonitor options)
24 | {
25 | // NOTE: Only IsEnabled is monitored
26 |
27 | var loggerOptions = options.CurrentValue;
28 | if (loggerOptions.BatchSize <= 0)
29 | {
30 | throw new ArgumentOutOfRangeException(nameof(loggerOptions.BatchSize), $"{nameof(loggerOptions.BatchSize)} must be a positive number.");
31 | }
32 | if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
33 | {
34 | throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
35 | }
36 |
37 | _interval = loggerOptions.FlushPeriod;
38 | _batchSize = loggerOptions.BatchSize;
39 | _queueSize = loggerOptions.BackgroundQueueSize;
40 |
41 | _optionsChangeToken = options.OnChange(UpdateOptions);
42 | UpdateOptions(options.CurrentValue);
43 | }
44 |
45 | public bool IsEnabled { get; private set; }
46 |
47 | private void UpdateOptions(BatchingLoggerOptions options)
48 | {
49 | var oldIsEnabled = IsEnabled;
50 | IsEnabled = options.IsEnabled;
51 | if (oldIsEnabled != IsEnabled)
52 | {
53 | if (IsEnabled)
54 | {
55 | Start();
56 | }
57 | else
58 | {
59 | Stop();
60 | }
61 | }
62 |
63 | }
64 |
65 | protected abstract Task WriteMessagesAsync(IEnumerable messages, CancellationToken token);
66 |
67 | private async Task ProcessLogQueue(object state)
68 | {
69 | while (!_cancellationTokenSource.IsCancellationRequested)
70 | {
71 | var limit = _batchSize ?? int.MaxValue;
72 |
73 | while (limit > 0 && _messageQueue.TryTake(out var message))
74 | {
75 | _currentBatch.Add(message);
76 | limit--;
77 | }
78 |
79 | if (_currentBatch.Count > 0)
80 | {
81 | try
82 | {
83 | await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token);
84 | }
85 | catch
86 | {
87 | // ignored
88 | }
89 |
90 | _currentBatch.Clear();
91 | }
92 |
93 | await IntervalAsync(_interval, _cancellationTokenSource.Token);
94 | }
95 | }
96 |
97 | protected virtual Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
98 | {
99 | return Task.Delay(interval, cancellationToken);
100 | }
101 |
102 | public void AddMessage(DateTimeOffset timestamp, string message)
103 | {
104 | if (!_messageQueue.IsAddingCompleted)
105 | {
106 | try
107 | {
108 | _messageQueue.Add(new LogMessage { Message = message, Timestamp = timestamp }, _cancellationTokenSource.Token);
109 | }
110 | catch
111 | {
112 | //cancellation token canceled or CompleteAdding called
113 | }
114 | }
115 | }
116 |
117 | private void Start()
118 | {
119 | _messageQueue = _queueSize == null ?
120 | new BlockingCollection(new ConcurrentQueue()) :
121 | new BlockingCollection(new ConcurrentQueue(), _queueSize.Value);
122 |
123 | _cancellationTokenSource = new CancellationTokenSource();
124 | _outputTask = Task.Factory.StartNew(ProcessLogQueue, null, TaskCreationOptions.LongRunning);
125 | }
126 |
127 | private void Stop()
128 | {
129 | _cancellationTokenSource.Cancel();
130 | _messageQueue.CompleteAdding();
131 |
132 | try
133 | {
134 | _outputTask.Wait(_interval);
135 | }
136 | catch (TaskCanceledException)
137 | {
138 | }
139 | catch (AggregateException ex) when (ex.InnerExceptions.Count == 1 && ex.InnerExceptions[0] is TaskCanceledException)
140 | {
141 | }
142 | }
143 |
144 | public void Dispose()
145 | {
146 | _optionsChangeToken?.Dispose();
147 | if (IsEnabled)
148 | {
149 | Stop();
150 | }
151 | }
152 |
153 | public virtual ILogger CreateLogger(string categoryName)
154 | {
155 | return new BatchingLogger(this, categoryName);
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/LogMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Zero.Logging.Batching
4 | {
5 | public struct LogMessage
6 | {
7 | public DateTimeOffset Timestamp { get; set; }
8 |
9 | public string Message { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Batching/Zero.Logging.Batching.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Zero file logger common lib.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Zero.Logging.Elasticsearch/ElasticsearchHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.RegularExpressions;
5 | using Elasticsearch.Net;
6 | using Zero.Logging.Batching;
7 |
8 | namespace Zero.Logging.Elasticsearch
9 | {
10 | internal class ElasticsearchHelper
11 | {
12 | private readonly ElasticLowLevelClient _client;
13 |
14 | private readonly Func _indexDecider;
15 | private readonly bool _registerTemplateOnStartup;
16 | private readonly string _templateName;
17 | private readonly string _templateMatchString;
18 |
19 | private static readonly Regex _indexFormatRegex = new Regex(@"^(.*)(?:\{0\:.+\})(.*)$");
20 |
21 | public static ElasticsearchHelper Create(EsLoggerOptions options)
22 | {
23 | if (options == null)
24 | throw new ArgumentNullException(nameof(options));
25 | return new ElasticsearchHelper(options);
26 | }
27 |
28 | private ElasticsearchHelper(EsLoggerOptions options)
29 | {
30 | if (string.IsNullOrWhiteSpace(options.ElasticsearchUrl)) throw new ArgumentException("options.ElasticsearchUrl");
31 | if (string.IsNullOrWhiteSpace(options.IndexFormat)) throw new ArgumentException("options.IndexFormat");
32 | if (string.IsNullOrWhiteSpace(options.TypeName)) throw new ArgumentException("options.TypeName");
33 | if (string.IsNullOrWhiteSpace(options.TemplateName)) throw new ArgumentException("options.TemplateName");
34 |
35 | _templateName = options.TemplateName;
36 | _templateMatchString = _indexFormatRegex.Replace(options.IndexFormat, @"$1*$2");
37 | _indexDecider = options.IndexDecider ?? (logMsg => string.Format(options.IndexFormat, logMsg.Timestamp));
38 |
39 | Options = options;
40 |
41 | IConnectionPool pool;
42 | if (options.ElasticsearchUrl.Contains(";"))
43 | {
44 | pool = new StaticConnectionPool(options.ElasticsearchUrl.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(_ => new Uri(_)));
45 | }
46 | else
47 | {
48 | pool = new SingleNodeConnectionPool(new Uri(options.ElasticsearchUrl));
49 | }
50 |
51 | var configuration = new ConnectionConfiguration(pool, options.Connection, options.Serializer).RequestTimeout(options.ConnectionTimeout);
52 |
53 | if (!string.IsNullOrEmpty(options.UserName) && !string.IsNullOrEmpty(options.Password))
54 | {
55 | configuration.BasicAuthentication(options.UserName, options.Password);
56 | }
57 |
58 | if (options.ModifyConnectionSettings != null) configuration = options.ModifyConnectionSettings(configuration);
59 |
60 | configuration.ThrowExceptions();
61 |
62 | _client = new ElasticLowLevelClient(configuration);
63 |
64 | _registerTemplateOnStartup = options.AutoRegisterTemplate;
65 | TemplateRegistrationSuccess = !_registerTemplateOnStartup;
66 | }
67 |
68 | public EsLoggerOptions Options { get; }
69 |
70 | public IElasticLowLevelClient Client => _client;
71 |
72 | public bool TemplateRegistrationSuccess { get; private set; }
73 |
74 |
75 | public string Serialize(object o)
76 | {
77 | return _client.Serializer.SerializeToString(o, SerializationFormatting.None);
78 | }
79 |
80 | public string GetIndexForEvent(LogMessage e, DateTimeOffset offset)
81 | {
82 | if (!TemplateRegistrationSuccess && Options.RegisterTemplateFailure == RegisterTemplateRecovery.IndexToDeadletterIndex)
83 | {
84 | return string.Format(Options.DeadLetterIndexName, offset);
85 | }
86 | return _indexDecider(e);
87 | }
88 |
89 | public void RegisterTemplateIfNeeded()
90 | {
91 | if (!_registerTemplateOnStartup) return;
92 |
93 | try
94 | {
95 | if (!Options.OverwriteTemplate)
96 | {
97 | var templateExistsResponse = _client.IndicesExistsTemplateForAll(_templateName);
98 | if (templateExistsResponse.HttpStatusCode == 200)
99 | {
100 | TemplateRegistrationSuccess = true;
101 |
102 | return;
103 | }
104 | }
105 |
106 | var result = _client.IndicesPutTemplateForAll(_templateName, GetTempatePostData());
107 |
108 | if (!result.Success)
109 | {
110 | ((IElasticsearchResponse)result).TryGetServerErrorReason(out var serverError);
111 | Console.WriteLine("Unable to create the template. {0}", serverError);
112 | TemplateRegistrationSuccess = false;
113 | }
114 | else
115 | TemplateRegistrationSuccess = true;
116 |
117 | }
118 | catch (Exception ex)
119 | {
120 | TemplateRegistrationSuccess = false;
121 | Console.WriteLine("Failed to create the template. {0}", ex);
122 |
123 | if (Options.RegisterTemplateFailure == RegisterTemplateRecovery.Throw)
124 | throw;
125 | }
126 | }
127 |
128 | private PostData GetTempatePostData()
129 | {
130 | //PostData no longer exposes an implict cast from object. Previously it supported that and would inspect the object Type to
131 | //determine if it it was a litteral string to write directly or if it was an object that it needed to serialse. Now the onus is
132 | //on us to tell it what type we are passing otherwise if the user specified the template as a json string it would be serialised again.
133 | var template = GetTemplateData();
134 | if (template is string)
135 | {
136 | return PostData.String((string)template);
137 | }
138 | else
139 | {
140 | return PostData.Serializable(template);
141 | }
142 | }
143 |
144 | private object GetTemplateData()
145 | {
146 | if (Options.GetTemplateContent != null)
147 | return Options.GetTemplateContent();
148 |
149 | var settings = new Dictionary
150 | {
151 | {"index.refresh_interval", "5s"}
152 | };
153 |
154 | if (Options.NumberOfShards.HasValue)
155 | settings.Add("number_of_shards", Options.NumberOfShards.Value.ToString());
156 |
157 | if (Options.NumberOfReplicas.HasValue)
158 | settings.Add("number_of_replicas", Options.NumberOfReplicas.Value.ToString());
159 |
160 | return GetTemplateESv6(settings, _templateMatchString);
161 |
162 | }
163 |
164 | private static object GetTemplateESv6(Dictionary settings, string templateMatchString)
165 | {
166 | return new
167 | {
168 | template = templateMatchString,
169 | settings,
170 | mappings = new
171 | {
172 | _default_ = new
173 | {
174 | dynamic_templates = new List