Skip to content

Commit 71e8bd5

Browse files
authored
Sanitize overlapping property names (#97)
* Added sanitizing for overlapping property names * Property name sanitizing test added * Updated README.MD * Added README.MD to package
1 parent 472f0eb commit 71e8bd5

File tree

5 files changed

+73
-12
lines changed

5 files changed

+73
-12
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Serilog.Sinks.Grafana.Loki
22

3+
[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua/)
34
[![Build status](https://github.com/mishamyte/serilog-sinks-grafana-loki/workflows/CI/badge.svg)](https://github.com/mishamyte/serilog-sinks-grafana-loki/actions?query=workflow%3ACI)
45
[![NuGet version](https://img.shields.io/nuget/v/Serilog.Sinks.Grafana.Loki)](https://www.nuget.org/packages/Serilog.Sinks.Grafana.Loki)
56
[![Latest release](https://img.shields.io/github/v/release/mishamyte/serilog-sinks-grafana-loki?include_prereleases)](https://github.com/mishamyte/serilog-sinks-grafana-loki/releases)
@@ -160,4 +161,4 @@ Example configuration:
160161
```
161162

162163
### Inspiration and Credits
163-
- [Serilog.Sinks.Loki](https://github.com/JosephWoodward/Serilog-Sinks-Loki)
164+
- [Serilog.Sinks.Loki](https://github.com/JosephWoodward/Serilog-Sinks-Loki)

src/Serilog.Sinks.Grafana.Loki/LokiJsonTextFormatter.cs

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,14 @@ namespace Serilog.Sinks.Grafana.Loki
2424
/// <summary>
2525
/// Used to serialize a log event to a json format that loki 2.0 can parse using the json parser ( | json ), more information can be found here https://grafana.com/blog/2020/10/28/loki-2.0-released-transform-logs-as-youre-querying-them-and-set-up-alerts-within-loki/
2626
/// </summary>
27-
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration", Justification = "Reviewed")]
27+
[SuppressMessage(
28+
"ReSharper",
29+
"PossibleMultipleEnumeration",
30+
Justification = "Reviewed")]
2831
public class LokiJsonTextFormatter : ITextFormatter, ILabelAwareTextFormatter
2932
{
33+
private static readonly string[] ReservedKeywords = { "Message", "MessageTemplate", "Renderings", "level", "Exception" };
34+
3035
private readonly JsonValueFormatter _valueFormatter;
3136

3237
/// <summary>
@@ -52,7 +57,10 @@ public LokiJsonTextFormatter()
5257
/// <param name="labels">
5358
/// List of labels that should not be written as json fields.
5459
/// </param>
55-
public void Format(LogEvent logEvent, TextWriter output, IEnumerable<string> labels)
60+
public void Format(
61+
LogEvent logEvent,
62+
TextWriter output,
63+
IEnumerable<string> labels)
5664
{
5765
if (logEvent == null)
5866
{
@@ -98,12 +106,15 @@ public void Format(LogEvent logEvent, TextWriter output, IEnumerable<string> lab
98106
if (logEvent.Exception != null)
99107
{
100108
output.Write(",\"Exception\":");
101-
SerializeException(output, logEvent.Exception, 1);
109+
SerializeException(
110+
output,
111+
logEvent.Exception,
112+
1);
102113
}
103114

104115
foreach (var property in logEvent.Properties)
105116
{
106-
var name = property.Key;
117+
var name = GetSanitizedPropertyName(property.Key);
107118
if (labels.Contains(name))
108119
{
109120
continue;
@@ -120,7 +131,20 @@ public void Format(LogEvent logEvent, TextWriter output, IEnumerable<string> lab
120131

121132
/// <inheritdoc/>
122133
[Obsolete("Use \"Format(LogEvent logEvent, TextWriter output, IEnumerable<string> labels)\" instead!")]
123-
public void Format(LogEvent logEvent, TextWriter output) => Format(logEvent, output, Enumerable.Empty<string>());
134+
public void Format(LogEvent logEvent, TextWriter output) => Format(
135+
logEvent,
136+
output,
137+
Enumerable.Empty<string>());
138+
139+
/// <summary>
140+
/// Used to sanitize property name to avoid conflict with reserved keywords.
141+
/// Appends _ to the property name if it matches with reserved keyword.
142+
/// </summary>
143+
/// <param name="propertyName">
144+
/// Name of property to sanitize
145+
/// </param>
146+
protected virtual string GetSanitizedPropertyName(string propertyName) =>
147+
ReservedKeywords.Contains(propertyName) ? $"_{propertyName}" : propertyName;
124148

125149
/// <summary>
126150
/// Used to serialize exceptions, can be overridden when inheriting to change the format.
@@ -134,17 +158,23 @@ public void Format(LogEvent logEvent, TextWriter output, IEnumerable<string> lab
134158
/// <param name="level">
135159
/// The current nesting level of the exception.
136160
/// </param>
137-
protected virtual void SerializeException(TextWriter output, Exception exception, int level)
161+
protected virtual void SerializeException(
162+
TextWriter output,
163+
Exception exception,
164+
int level)
138165
{
139166
if (level == 4)
140167
{
141168
JsonValueFormatter.WriteQuotedJsonString(exception.ToString(), output);
169+
142170
return;
143171
}
144172

145173
output.Write("{\"Type\":");
146174
var typeNamespace = exception.GetType().Namespace;
147-
var typeName = typeNamespace != null && typeNamespace.StartsWith("System.") ? exception.GetType().Name : exception.GetType().ToString();
175+
var typeName = typeNamespace != null && typeNamespace.StartsWith("System.")
176+
? exception.GetType().Name
177+
: exception.GetType().ToString();
148178
JsonValueFormatter.WriteQuotedJsonString(typeName, output);
149179

150180
if (!string.IsNullOrWhiteSpace(exception.Message))
@@ -166,7 +196,10 @@ protected virtual void SerializeException(TextWriter output, Exception exception
166196
for (var i = 0; i < count; i++)
167197
{
168198
var isLast = i == count - 1;
169-
SerializeException(output, aggregateException.InnerExceptions[i], level + 1);
199+
SerializeException(
200+
output,
201+
aggregateException.InnerExceptions[i],
202+
level + 1);
170203
if (!isLast)
171204
{
172205
output.Write(',');
@@ -178,10 +211,13 @@ protected virtual void SerializeException(TextWriter output, Exception exception
178211
else if (exception.InnerException != null)
179212
{
180213
output.Write(",\"InnerException\":");
181-
SerializeException(output, exception.InnerException, level + 1);
214+
SerializeException(
215+
output,
216+
exception.InnerException,
217+
level + 1);
182218
}
183219

184220
output.Write('}');
185221
}
186222
}
187-
}
223+
}

src/Serilog.Sinks.Grafana.Loki/Serilog.Sinks.Grafana.Loki.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<PackageId>Serilog.Sinks.Grafana.Loki</PackageId>
1818
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1919
<PackageProjectUrl>https://github.com/mishamyte/serilog-sinks-grafana-loki</PackageProjectUrl>
20+
<PackageReadmeFile>README.md</PackageReadmeFile>
2021
<PackageReleaseNotes>For release notes, please see the change log on GitHub.</PackageReleaseNotes>
2122
<PackageTags>serilog;loki;grafana</PackageTags>
2223
</PropertyGroup>
@@ -28,7 +29,8 @@
2829

2930
<ItemGroup>
3031
<None Include="..\..\LICENSE" Pack="true" Visible="false" PackagePath="" />
32+
<None Include="..\..\README.md" Pack="true" Visible="false" PackagePath="" />
3133
<None Include="..\..\assets\serilog-sink-nuget.png" Pack="true" Visible="false" PackagePath="" />
3234
</ItemGroup>
3335

34-
</Project>
36+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"streams":[{"stream":{"Message":"Ukraine!"},"values":[["<unixepochinnanoseconds>","{\u0022Message\u0022:\u0022This is \\\u0022Ukraine!\\\u0022\u0022,\u0022MessageTemplate\u0022:\u0022This is {Message}\u0022,\u0022level\u0022:\u0022info\u0022,\u0022_Message\u0022:\u0022Ukraine!\u0022}"]]}]}

test/Serilog.Sinks.Grafana.Loki.Tests/IntegrationTests/LokiJsonTextFormatterRequestPayloadTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,26 @@ public void MessagePropertyForInternalTimestampShouldBeCreated()
211211
});
212212
});
213213
}
214+
215+
[Fact]
216+
public void PropertyNameEqualToReservedKeywordShouldBeSanitized()
217+
{
218+
var logger = new LoggerConfiguration()
219+
.WriteTo.GrafanaLoki(
220+
"https://loki:3100",
221+
outputTemplate: OutputTemplate,
222+
textFormatter: new LokiJsonTextFormatter(),
223+
httpClient: _client)
224+
.CreateLogger();
225+
226+
logger.Information("This is {Message}", "Ukraine!");
227+
logger.Dispose();
228+
229+
_client.Content.ShouldMatchApproved(c =>
230+
{
231+
c.SubFolder(ApprovalsFolderName);
232+
c.WithScrubber(s => Regex.Replace(s, "\"[0-9]{19}\"", "\"<unixepochinnanoseconds>\""));
233+
});
234+
}
214235
}
215236
}

0 commit comments

Comments
 (0)