Skip to content
This repository was archived by the owner on Jan 17, 2026. It is now read-only.

Commit 126cb7d

Browse files
snakefootmarkmcdowell
authored andcommitted
feat: EnableJsonLayout for using NLog Layout for generating document
1 parent e81abf0 commit 126cb7d

2 files changed

Lines changed: 130 additions & 41 deletions

File tree

src/NLog.Targets.ElasticSearch.Tests/IntegrationTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,41 @@ public void SimpleLogTest()
8080
LogManager.Flush();
8181
}
8282

83+
[Fact(Skip = "Integration")]
84+
public void SimpleJsonLayoutTest()
85+
{
86+
var elasticTarget = new ElasticSearchTarget();
87+
elasticTarget.EnableJsonLayout = true;
88+
elasticTarget.Layout = new JsonLayout()
89+
{
90+
MaxRecursionLimit = 10,
91+
IncludeAllProperties = true,
92+
Attributes =
93+
{
94+
new JsonAttribute("timestamp", "${date:universaltime=true:format=o}"),
95+
new JsonAttribute("lvl", "${level}"),
96+
new JsonAttribute("msg", "${message}"),
97+
new JsonAttribute("logger", "${logger}"),
98+
new JsonAttribute("threadid", "${threadid}", false), // Skip quotes for integer-value
99+
}
100+
};
101+
102+
var rule = new LoggingRule("*", elasticTarget);
103+
rule.EnableLoggingForLevel(LogLevel.Info);
104+
105+
var config = new LoggingConfiguration();
106+
config.LoggingRules.Add(rule);
107+
108+
LogManager.ThrowExceptions = true;
109+
LogManager.Configuration = config;
110+
111+
var logger = LogManager.GetLogger("Example");
112+
113+
logger.Info("Hello elasticsearch");
114+
115+
LogManager.Flush();
116+
}
117+
83118
[Fact(Skip = "Integration")]
84119
public void ExceptionTest()
85120
{

src/NLog.Targets.ElasticSearch/ElasticSearchTarget.cs

Lines changed: 95 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class ElasticSearchTarget : TargetWithLayout, IElasticSearchTarget
3232
private JsonSerializer JsonSerializer => _jsonSerializer ?? (_jsonSerializer = JsonSerializer.CreateDefault(_jsonSerializerSettings.Value));
3333
private JsonSerializer JsonSerializerFlat => _flatJsonSerializer ?? (_flatJsonSerializer = JsonSerializer.CreateDefault(_flatSerializerSettings.Value));
3434

35+
private JsonLayout _documentInfoJsonLayout;
36+
3537
/// <summary>
3638
/// Gets or sets a connection string name to retrieve the Uri from.
3739
///
@@ -177,6 +179,11 @@ public string CloudId
177179
/// </summary>
178180
public int MaxRecursionLimit { get; set; } = -1;
179181

182+
/// <summary>
183+
/// Take the raw output from configured JsonLayout and send as document (Instead of creating expando-object for document serialization)
184+
/// </summary>
185+
public bool EnableJsonLayout { get; set; }
186+
180187
/// <summary>
181188
/// Initializes a new instance of the <see cref="ElasticSearchTarget"/> class.
182189
/// </summary>
@@ -265,6 +272,28 @@ protected override void InitializeTarget()
265272

266273
if (!string.IsNullOrEmpty(ExcludedProperties))
267274
_excludedProperties = new HashSet<string>(ExcludedProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
275+
276+
if (EnableJsonLayout)
277+
{
278+
if (Layout is SimpleLayout)
279+
{
280+
InternalLogger.Info("ElasticSearch: Layout-property has type SimpleLayout, instead of the expected JsonLayout");
281+
}
282+
283+
_documentInfoJsonLayout = new JsonLayout()
284+
{
285+
Attributes = {
286+
new JsonAttribute("index", new JsonLayout()
287+
{
288+
Attributes = {
289+
new JsonAttribute("_index", Index) { EscapeForwardSlash = false },
290+
new JsonAttribute("_type", DocumentType) { EscapeForwardSlash = false },
291+
new JsonAttribute("pipeline", Pipeline) { EscapeForwardSlash = false },
292+
}
293+
}, encode: false)
294+
}
295+
};
296+
}
268297
}
269298

270299
/// <inheritdoc />
@@ -283,7 +312,7 @@ private void SendBatch(ICollection<AsyncLogEventInfo> logEvents)
283312
{
284313
try
285314
{
286-
var payload = FormPayload(logEvents);
315+
var payload = EnableJsonLayout ? FromPayloadWithJsonLayout(logEvents) : FormPayload(logEvents);
287316

288317
var result = _client.Bulk<BytesResponse>(payload);
289318

@@ -321,6 +350,24 @@ private void SendBatch(ICollection<AsyncLogEventInfo> logEvents)
321350
}
322351
}
323352

353+
private PostData FromPayloadWithJsonLayout(ICollection<AsyncLogEventInfo> logEvents)
354+
{
355+
var payload = new List<string>(logEvents.Count * 2); // documentInfo + document
356+
357+
foreach (var ev in logEvents)
358+
{
359+
var logEvent = ev.LogEvent;
360+
361+
var documentInfo = RenderLogEvent(_documentInfoJsonLayout, logEvent);
362+
var document = RenderLogEvent(Layout, logEvent);
363+
364+
payload.Add(documentInfo);
365+
payload.Add(document);
366+
}
367+
368+
return PostData.MultiJson(payload);
369+
}
370+
324371
private PostData FormPayload(ICollection<AsyncLogEventInfo> logEvents)
325372
{
326373
var payload = new List<object>(logEvents.Count * 2); // documentInfo + document
@@ -329,65 +376,72 @@ private PostData FormPayload(ICollection<AsyncLogEventInfo> logEvents)
329376
{
330377
var logEvent = ev.LogEvent;
331378

332-
var document = new Dictionary<string, object>
379+
var index = RenderLogEvent(Index, logEvent).ToLowerInvariant();
380+
var type = RenderLogEvent(DocumentType, logEvent);
381+
var pipeLine = RenderLogEvent(Pipeline, logEvent);
382+
383+
var documentInfo = GenerateDocumentInfo(index, type, pipeLine);
384+
var document = GenerateDocumentProperties(logEvent);
385+
386+
payload.Add(documentInfo);
387+
payload.Add(document);
388+
}
389+
390+
return PostData.MultiJson(payload);
391+
}
392+
393+
private Dictionary<string, object> GenerateDocumentProperties(LogEventInfo logEvent)
394+
{
395+
var document = new Dictionary<string, object>
333396
{
334397
{"@timestamp", logEvent.TimeStamp},
335398
{"level", logEvent.Level.Name},
336399
{"message", RenderLogEvent(Layout, logEvent)}
337400
};
338401

339-
foreach (var field in Fields)
340-
{
341-
var renderedField = RenderLogEvent(field.Layout, logEvent);
402+
foreach (var field in Fields)
403+
{
404+
var renderedField = RenderLogEvent(field.Layout, logEvent);
342405

343-
if (string.IsNullOrWhiteSpace(renderedField))
344-
continue;
406+
if (string.IsNullOrWhiteSpace(renderedField))
407+
continue;
345408

346-
try
347-
{
348-
document[field.Name] = renderedField.ToSystemType(field.LayoutType, logEvent.FormatProvider, JsonSerializer);
349-
}
350-
catch (Exception ex)
351-
{
352-
_jsonSerializer = null; // Reset as it might now be in bad state
353-
InternalLogger.Error(ex, "ElasticSearch: Error while formatting field: {0}", field.Name);
354-
}
409+
try
410+
{
411+
document[field.Name] = renderedField.ToSystemType(field.LayoutType, logEvent.FormatProvider, JsonSerializer);
355412
}
356-
357-
if (logEvent.Exception != null && !document.ContainsKey("exception"))
413+
catch (Exception ex)
358414
{
359-
document.Add("exception", FormatValueSafe(logEvent.Exception, "exception"));
415+
_jsonSerializer = null; // Reset as it might now be in bad state
416+
InternalLogger.Error(ex, "ElasticSearch: Error while formatting field: {0}", field.Name);
360417
}
418+
}
419+
420+
if (logEvent.Exception != null && !document.ContainsKey("exception"))
421+
{
422+
document.Add("exception", FormatValueSafe(logEvent.Exception, "exception"));
423+
}
361424

362-
if (IncludeAllProperties && logEvent.HasProperties)
425+
if (IncludeAllProperties && logEvent.HasProperties)
426+
{
427+
foreach (var p in logEvent.Properties)
363428
{
364-
foreach (var p in logEvent.Properties)
365-
{
366-
var propertyKey = p.Key.ToString();
367-
if (_excludedProperties.Contains(propertyKey))
368-
continue;
429+
var propertyKey = p.Key.ToString();
430+
if (_excludedProperties.Contains(propertyKey))
431+
continue;
369432

433+
if (document.ContainsKey(propertyKey))
434+
{
435+
propertyKey += "_1";
370436
if (document.ContainsKey(propertyKey))
371-
{
372-
propertyKey += "_1";
373-
if (document.ContainsKey(propertyKey))
374-
continue;
375-
}
376-
377-
document[propertyKey] = FormatValueSafe(p.Value, propertyKey);
437+
continue;
378438
}
379-
}
380439

381-
var index = RenderLogEvent(Index, logEvent).ToLowerInvariant();
382-
var type = RenderLogEvent(DocumentType, logEvent);
383-
var pipeLine = RenderLogEvent(Pipeline, logEvent);
384-
385-
var documentInfo = GenerateDocumentInfo(index, type, pipeLine);
386-
payload.Add(documentInfo);
387-
payload.Add(document);
440+
document[propertyKey] = FormatValueSafe(p.Value, propertyKey);
441+
}
388442
}
389443

390-
return PostData.MultiJson(payload);
444+
return document;
391445
}
392446

393447
private static object GenerateDocumentInfo(string index, string type, string pipeLine)

0 commit comments

Comments
 (0)