Skip to content
This repository was archived by the owner on Jun 1, 2024. It is now read-only.

Commit 96de06e

Browse files
authored
Merge dev to master (#168)
* Explain use of `AutoRegisterTemplateVersion` in the README (#157) It took me a while to figure out that the reason the sink failed to start with a somewhat cryptic "Failed to execute" error was that, while I had updated all libraries to ES6-compatibile versions, I had not set the `AutoRegisterTemplateVersion` option and so the template sent was not ES6-compatible (it used "string"). This PR explains this snag in the readme. For a future PR, would it make sense to deprecate the manual option and automatically detect the ES version when attempting to register a template? It can be fetched by a GET request at the root URL, which returns: ``` { "name" : "8hV7EYy", "cluster_name" : "docker-cluster", "cluster_uuid" : "bC1bTjVuTiyWoaprIIZIIw", "version" : { "number" : "6.2.1", "build_hash" : "7299dc3", "build_date" : "2018-02-07T19:34:26.990113Z", "build_snapshot" : false, "lucene_version" : "7.2.1", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search" } ``` * Added some explanation See #165 * Render message by default #158, Could disable MessageTemplate #159 + Tests (#160) * Updated readme * Expose interface-typed options via appsettings (#162) * Expose interface-typed settings via appsettings * Amend readme to show changes for json appsettings as well * Updated changelog
1 parent 486ee2c commit 96de06e

File tree

8 files changed

+402
-24
lines changed

8 files changed

+402
-24
lines changed

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
== Changelog
22

3+
6.3
4+
* Render message by default (#160).
5+
* Expose interface-typed options via appsettings (#162)
6+
37
6.2
48
* Extra overload added to support more settings via AppSettings reader. (#150)
59

README.md

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Register the sink in code or using the appSettings reader (from v2.0.42+) as sho
2828
var loggerConfig = new LoggerConfiguration()
2929
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200") ){
3030
AutoRegisterTemplate = true,
31+
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv6
3132
});
3233
```
3334

@@ -49,10 +50,27 @@ This example shows the options that are currently available when using the appSe
4950
<add key="serilog:write-to:Elasticsearch.bufferFileSizeLimitBytes" value="5242880"/>
5051
<add key="serilog:write-to:Elasticsearch.bufferLogShippingInterval" value="5000"/>
5152
<add key="serilog:write-to:Elasticsearch.connectionGlobalHeaders" value="Authorization=Bearer SOME-TOKEN;OtherHeader=OTHER-HEADER-VALUE" />
53+
<add key="serilog:write-to:Elasticsearch.connectionTimeout" value="5" />
54+
<add key="serilog:write-to:Elasticsearch.emitEventFailure" value="WriteToSelfLog" />
55+
<add key="serilog:write-to:Elasticsearch.queueSizeLimit" value="100000" />
56+
<add key="serilog:write-to:Elasticsearch.autoRegisterTemplate" value="true" />
57+
<add key="serilog:write-to:Elasticsearch.autoRegisterTemplateVersion" value="ESv2" />
58+
<add key="serilog:write-to:Elasticsearch.overwriteTemplate" value="false" />
59+
<add key="serilog:write-to:Elasticsearch.registerTemplateFailure" value="IndexAnyway" />
60+
<add key="serilog:write-to:Elasticsearch.deadLetterIndexName" value="deadletter-{0:yyyy.MM}" />
61+
<add key="serilog:write-to:Elasticsearch.numberOfShards" value="20" />
62+
<add key="serilog:write-to:Elasticsearch.numberOfReplicas" value="10" />
63+
<add key="serilog:write-to:Elasticsearch.formatProvider" value="My.Namespace.MyFormatProvider, My.Assembly.Name" />
64+
<add key="serilog:write-to:Elasticsearch.connection" value="My.Namespace.MyConnection, My.Assembly.Name" />
65+
<add key="serilog:write-to:Elasticsearch.serializer" value="My.Namespace.MySerializer, My.Assembly.Name" />
66+
<add key="serilog:write-to:Elasticsearch.connectionPool" value="My.Namespace.MyConnectionPool, My.Assembly.Name" />
67+
<add key="serilog:write-to:Elasticsearch.customFormatter" value="My.Namespace.MyCustomFormatter, My.Assembly.Name" />
68+
<add key="serilog:write-to:Elasticsearch.customDurableFormatter" value="My.Namespace.MyCustomDurableFormatter, My.Assembly.Name" />
69+
<add key="serilog:write-to:Elasticsearch.failureSink" value="My.Namespace.MyFailureSink, My.Assembly.Name" />
5270
</appSettings>
5371
```
5472

55-
With the appSettings configuration the `nodeUris` property is required. Multiple nodes can be specified using `,` or `;` to seperate them. All other properties are optional.
73+
With the appSettings configuration the `nodeUris` property is required. Multiple nodes can be specified using `,` or `;` to seperate them. All other properties are optional. Also required is the '<add key="serilog:using" value="Serilog.Sinks.Elasticsearch"/>' setting to include this sink. All other properties are optional. If you do not explicitly specify an indexFormat-setting, a generic index such as 'logstash-[current_date]' will be used automatically.
5674

5775
And start writing your events using Serilog.
5876

@@ -123,7 +141,24 @@ In your `appsettings.json` file, under the `Serilog` node, :
123141
"bufferBaseFilename": "C:/Temp/LogDigipolis/docker-elk-serilog-web-buffer",
124142
"bufferFileSizeLimitBytes": 5242880,
125143
"bufferLogShippingInterval": 5000,
126-
"connectionGlobalHeaders" :"Authorization=Bearer SOME-TOKEN;OtherHeader=OTHER-HEADER-VALUE"
144+
"connectionGlobalHeaders" :"Authorization=Bearer SOME-TOKEN;OtherHeader=OTHER-HEADER-VALUE",
145+
"connectionTimeout": 5,
146+
"emitEventFailure": "WriteToSelfLog",
147+
"queueSizeLimit": "100000",
148+
"autoRegisterTemplate": true,
149+
"autoRegisterTemplateVersion": "ESv2",
150+
"overwriteTemplate": false,
151+
"registerTemplateFailure": "IndexAnyway",
152+
"deadLetterIndexName": "deadletter-{0:yyyy.MM}",
153+
"numberOfShards": 20,
154+
"numberOfReplicas": 10,
155+
"formatProvider": "My.Namespace.MyFormatProvider, My.Assembly.Name",
156+
"connection": "My.Namespace.MyConnection, My.Assembly.Name",
157+
"serializer": "My.Namespace.MySerializer, My.Assembly.Name",
158+
"connectionPool": "My.Namespace.MyConnectionPool, My.Assembly.Name",
159+
"customFormatter": "My.Namespace.MyCustomFormatter, My.Assembly.Name",
160+
"customDurableFormatter": "My.Namespace.MyCustomDurableFormatter, My.Assembly.Name",
161+
"failureSink": "My.Namespace.MyFailureSink, My.Assembly.Name"
127162
}
128163
}]
129164
}
@@ -165,7 +200,9 @@ Since version 5.5 you can use the RegisterTemplateFailure option. Set it to one
165200

166201
### Breaking changes for version 6
167202

168-
Starting from version 6, the sink has been upgraded to work with Elasticsearch 6.0 and has support for the new templates used by ES 6.
203+
Starting from version 6, the sink has been upgraded to work with Elasticsearch 6.0 and has support for the new templates used by ES 6.
204+
205+
If you use the `AutoRegisterTemplate` option, you need to set the `AutoRegisterTemplateVersion` option to `ESv6` in order to generate default templates that are compatible with the breaking changes in ES 6.
169206

170207
### Breaking changes for version 4
171208

src/Serilog.Sinks.Elasticsearch/LoggerConfigurationElasticSearchExtensions.cs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
using Serilog.Sinks.Elasticsearch;
2323
using System.Collections.Specialized;
2424
using System.ComponentModel;
25+
using Elasticsearch.Net;
26+
using Serilog.Formatting;
2527

2628
namespace Serilog
2729
{
@@ -237,5 +239,162 @@ public static LoggerConfiguration Elasticsearch(
237239

238240
return Elasticsearch(loggerSinkConfiguration, options);
239241
}
242+
243+
/// <summary>
244+
/// Overload to allow basic configuration through AppSettings.
245+
/// </summary>
246+
/// <param name="loggerSinkConfiguration">Options for the sink.</param>
247+
/// <param name="nodeUris">A comma or semi column separated list of URIs for Elasticsearch nodes.</param>
248+
/// <param name="indexFormat"><see cref="ElasticsearchSinkOptions.IndexFormat"/></param>
249+
/// <param name="templateName"><see cref="ElasticsearchSinkOptions.TemplateName"/></param>
250+
/// <param name="typeName"><see cref="ElasticsearchSinkOptions.TypeName"/></param>
251+
/// <param name="batchPostingLimit"><see cref="ElasticsearchSinkOptions.BatchPostingLimit"/></param>
252+
/// <param name="period"><see cref="ElasticsearchSinkOptions.Period"/></param>
253+
/// <param name="inlineFields"><see cref="ElasticsearchSinkOptions.InlineFields"/></param>
254+
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
255+
/// <param name="levelSwitch">A switch allowing the pass-through minimum level to be changed at runtime.</param>
256+
/// <param name="bufferBaseFilename"><see cref="ElasticsearchSinkOptions.BufferBaseFilename"/></param>
257+
/// <param name="bufferFileSizeLimitBytes"><see cref="ElasticsearchSinkOptions.BufferFileSizeLimitBytes"/></param>
258+
/// <param name="bufferLogShippingInterval"><see cref="ElasticsearchSinkOptions.BufferLogShippingInterval"/></param>
259+
/// <param name="connectionGlobalHeaders">A comma or semi column separated list of key value pairs of headers to be added to each elastic http request</param>
260+
/// <param name="connectionTimeout"><see cref="ElasticsearchSinkOptions.ConnectionTimeout"/>The connection timeout (in seconds) when sending bulk operations to elasticsearch (defaults to 5).</param>
261+
/// <param name="emitEventFailure"><see cref="ElasticsearchSinkOptions.EmitEventFailure"/>Specifies how failing emits should be handled.</param>
262+
/// <param name="queueSizeLimit"><see cref="ElasticsearchSinkOptions.QueueSizeLimit"/>The maximum number of events that will be held in-memory while waiting to ship them to Elasticsearch. Beyond this limit, events will be dropped. The default is 100,000. Has no effect on durable log shipping.</param>
263+
/// <param name="pipelineName"><see cref="ElasticsearchSinkOptions.PipelineName"/>Name the Pipeline where log events are sent to sink. Please note that the Pipeline should be existing before the usage starts.</param>
264+
/// <param name="autoRegisterTemplate"><see cref="ElasticsearchSinkOptions.AutoRegisterTemplate"/>When set to true the sink will register an index template for the logs in elasticsearch.</param>
265+
/// <param name="autoRegisterTemplateVersion"><see cref="ElasticsearchSinkOptions.AutoRegisterTemplateVersion"/>When using the AutoRegisterTemplate feature, this allows to set the Elasticsearch version. Depending on the version, a template will be selected. Defaults to pre 5.0.</param>
266+
/// <param name="overwriteTemplate"><see cref="ElasticsearchSinkOptions.OverwriteTemplate"/>When using the AutoRegisterTemplate feature, this allows you to overwrite the template in Elasticsearch if it already exists. Defaults to false</param>
267+
/// <param name="registerTemplateFailure"><see cref="ElasticsearchSinkOptions.RegisterTemplateFailure"/>Specifies the option on how to handle failures when writing the template to Elasticsearch. This is only applicable when using the AutoRegisterTemplate option.</param>
268+
/// <param name="deadLetterIndexName"><see cref="ElasticsearchSinkOptions.DeadLetterIndexName"/>Optionally set this value to the name of the index that should be used when the template cannot be written to ES.</param>
269+
/// <param name="numberOfShards"><see cref="ElasticsearchSinkOptions.NumberOfShards"/>The default number of shards.</param>
270+
/// <param name="numberOfReplicas"><see cref="ElasticsearchSinkOptions.NumberOfReplicas"/>The default number of replicas.</param>
271+
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
272+
/// <param name="connection">Allows you to override the connection used to communicate with elasticsearch.</param>
273+
/// <param name="serializer">When passing a serializer unknown object will be serialized to object instead of relying on their ToString representation</param>
274+
/// <param name="connectionPool">The connectionpool describing the cluster to write event to</param>
275+
/// <param name="customFormatter">Customizes the formatter used when converting log events into ElasticSearch documents. Please note that the formatter output must be valid JSON :)</param>
276+
/// <param name="customDurableFormatter">Customizes the formatter used when converting log events into the durable sink. Please note that the formatter output must be valid JSON :)</param>
277+
/// <param name="failureSink">Sink to use when Elasticsearch is unable to accept the events. This is optionally and depends on the EmitEventFailure setting.</param>
278+
/// <returns>LoggerConfiguration object</returns>
279+
/// <exception cref="ArgumentNullException"><paramref name="nodeUris"/> is <see langword="null" />.</exception>
280+
public static LoggerConfiguration Elasticsearch(
281+
this LoggerSinkConfiguration loggerSinkConfiguration,
282+
string nodeUris,
283+
string indexFormat = null,
284+
string templateName = null,
285+
string typeName = "logevent",
286+
int batchPostingLimit = 50,
287+
int period = 2,
288+
bool inlineFields = false,
289+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
290+
string bufferBaseFilename = null,
291+
long? bufferFileSizeLimitBytes = null,
292+
long bufferLogShippingInterval = 5000,
293+
string connectionGlobalHeaders = null,
294+
LoggingLevelSwitch levelSwitch = null,
295+
int connectionTimeout = 5,
296+
EmitEventFailureHandling emitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
297+
int queueSizeLimit = 100000,
298+
string pipelineName = null,
299+
bool autoRegisterTemplate = false,
300+
AutoRegisterTemplateVersion autoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv2,
301+
bool overwriteTemplate = false,
302+
RegisterTemplateRecovery registerTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
303+
string deadLetterIndexName = null,
304+
int? numberOfShards = null,
305+
int? numberOfReplicas = null,
306+
IFormatProvider formatProvider = null,
307+
IConnection connection = null,
308+
IElasticsearchSerializer serializer = null,
309+
IConnectionPool connectionPool = null,
310+
ITextFormatter customFormatter = null,
311+
ITextFormatter customDurableFormatter = null,
312+
ILogEventSink failureSink = null)
313+
{
314+
if (string.IsNullOrEmpty(nodeUris))
315+
throw new ArgumentNullException(nameof(nodeUris), "No Elasticsearch node(s) specified.");
316+
317+
IEnumerable<Uri> nodes = nodeUris
318+
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
319+
.Select(uriString => new Uri(uriString));
320+
321+
var options = connectionPool == null ? new ElasticsearchSinkOptions(nodes) : new ElasticsearchSinkOptions(connectionPool);
322+
323+
if (!string.IsNullOrWhiteSpace(indexFormat))
324+
{
325+
options.IndexFormat = indexFormat;
326+
}
327+
328+
if (!string.IsNullOrWhiteSpace(templateName))
329+
{
330+
options.AutoRegisterTemplate = true;
331+
options.TemplateName = templateName;
332+
}
333+
334+
if (!string.IsNullOrWhiteSpace(typeName))
335+
{
336+
options.TypeName = typeName;
337+
}
338+
339+
options.BatchPostingLimit = batchPostingLimit;
340+
options.Period = TimeSpan.FromSeconds(period);
341+
options.InlineFields = inlineFields;
342+
options.MinimumLogEventLevel = restrictedToMinimumLevel;
343+
options.LevelSwitch = levelSwitch;
344+
345+
if (!string.IsNullOrWhiteSpace(bufferBaseFilename))
346+
{
347+
Path.GetFullPath(bufferBaseFilename); // validate path
348+
options.BufferBaseFilename = bufferBaseFilename;
349+
}
350+
351+
if (bufferFileSizeLimitBytes.HasValue)
352+
{
353+
options.BufferFileSizeLimitBytes = bufferFileSizeLimitBytes.Value;
354+
}
355+
356+
options.BufferLogShippingInterval = TimeSpan.FromMilliseconds(bufferLogShippingInterval);
357+
358+
if (!string.IsNullOrWhiteSpace(connectionGlobalHeaders))
359+
{
360+
NameValueCollection headers = new NameValueCollection();
361+
connectionGlobalHeaders
362+
.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
363+
.ToList()
364+
.ForEach(headerValueStr =>
365+
{
366+
var headerValue = headerValueStr.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
367+
headers.Add(headerValue[0], headerValue[1]);
368+
});
369+
370+
options.ModifyConnectionSettings = (c) => c.GlobalHeaders(headers);
371+
}
372+
373+
options.ConnectionTimeout = TimeSpan.FromSeconds(connectionTimeout);
374+
options.EmitEventFailure = emitEventFailure;
375+
options.QueueSizeLimit = queueSizeLimit;
376+
options.PipelineName = pipelineName;
377+
378+
options.AutoRegisterTemplate = autoRegisterTemplate;
379+
options.AutoRegisterTemplateVersion = autoRegisterTemplateVersion;
380+
options.RegisterTemplateFailure = registerTemplateFailure;
381+
options.OverwriteTemplate = overwriteTemplate;
382+
options.NumberOfShards = numberOfShards;
383+
options.NumberOfReplicas = numberOfReplicas;
384+
385+
if (!string.IsNullOrWhiteSpace(deadLetterIndexName))
386+
{
387+
options.DeadLetterIndexName = deadLetterIndexName;
388+
}
389+
390+
options.FormatProvider = formatProvider;
391+
options.FailureSink = failureSink;
392+
options.Connection = connection;
393+
options.CustomFormatter = customFormatter;
394+
options.CustomDurableFormatter = customDurableFormatter;
395+
options.Serializer = serializer;
396+
397+
return Elasticsearch(loggerSinkConfiguration, options);
398+
}
240399
}
241400
}

src/Serilog.Sinks.Elasticsearch/Sinks/ElasticSearch/DefaultJsonFormatter.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public abstract class DefaultJsonFormatter : ITextFormatter
3535
readonly bool _omitEnclosingObject;
3636
readonly string _closingDelimiter;
3737
readonly bool _renderMessage;
38+
readonly bool _renderMessageTemplate;
3839
readonly IFormatProvider _formatProvider;
3940
readonly IDictionary<Type, Action<object, bool, TextWriter>> _literalWriters;
4041

@@ -50,15 +51,19 @@ public abstract class DefaultJsonFormatter : ITextFormatter
5051
/// <param name="renderMessage">If true, the message will be rendered and written to the output as a
5152
/// property named RenderedMessage.</param>
5253
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
54+
/// <param name="renderMessageTemplate">If true, the message template will be rendered and written to the output as a
55+
/// property named RenderedMessageTemplate.</param>
5356
protected DefaultJsonFormatter(
5457
bool omitEnclosingObject = false,
5558
string closingDelimiter = null,
56-
bool renderMessage = false,
57-
IFormatProvider formatProvider = null)
59+
bool renderMessage = true,
60+
IFormatProvider formatProvider = null,
61+
bool renderMessageTemplate = true)
5862
{
5963
_omitEnclosingObject = omitEnclosingObject;
6064
_closingDelimiter = closingDelimiter ?? Environment.NewLine;
6165
_renderMessage = renderMessage;
66+
_renderMessageTemplate = renderMessageTemplate;
6267
_formatProvider = formatProvider;
6368

6469
_literalWriters = new Dictionary<Type, Action<object, bool, TextWriter>>
@@ -102,7 +107,12 @@ public void Format(LogEvent logEvent, TextWriter output)
102107
var delim = "";
103108
WriteTimestamp(logEvent.Timestamp, ref delim, output);
104109
WriteLevel(logEvent.Level, ref delim, output);
105-
WriteMessageTemplate(logEvent.MessageTemplate.Text, ref delim, output);
110+
111+
if(_renderMessageTemplate)
112+
{
113+
WriteMessageTemplate(logEvent.MessageTemplate.Text, ref delim, output);
114+
}
115+
106116
if (_renderMessage)
107117
{
108118
var message = logEvent.RenderMessage(_formatProvider);

0 commit comments

Comments
 (0)