Skip to content

Commit c3e75ef

Browse files
committed
Add files missing from the last commit
1 parent 71604ec commit c3e75ef

File tree

7 files changed

+285
-0
lines changed

7 files changed

+285
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ForCanBeConvertedToForeach/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>

src/Serilog.Formatting.Compact/Formatting/Compact/CompactJsonFormatter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using Serilog.Events;
1919
using Serilog.Formatting.Json;
2020
using Serilog.Parsing;
21+
using Serilog.Formatting.Compact.Tests.Support;
2122

2223
namespace Serilog.Formatting.Compact
2324
{
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2016 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+
17+
namespace Serilog.Formatting.Compact
18+
{
19+
/// <summary>
20+
/// Hash functions for message templates. See <see cref="Compute"/>.
21+
/// </summary>
22+
public static class EventIdHash
23+
{
24+
/// <summary>
25+
/// Compute a 32-bit hash of the provided <paramref name="messageTemplate"/>. The
26+
/// resulting hash value can be uses as an event id in lieu of transmitting the
27+
/// full template string.
28+
/// </summary>
29+
/// <param name="messageTemplate">A message template.</param>
30+
/// <returns>A 32-bit hash of the template.</returns>
31+
public static uint Compute(string messageTemplate)
32+
{
33+
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));
34+
35+
// Jenkins one-at-a-time https://en.wikipedia.org/wiki/Jenkins_hash_function
36+
unchecked
37+
{
38+
uint hash = 0;
39+
for (var i = 0; i < messageTemplate.Length; ++i)
40+
{
41+
hash += messageTemplate[i];
42+
hash += (hash << 10);
43+
hash ^= (hash >> 6);
44+
}
45+
hash += (hash << 3);
46+
hash ^= (hash >> 11);
47+
hash += (hash << 15);
48+
return hash;
49+
}
50+
}
51+
}
52+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2016 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.IO;
17+
using Serilog.Events;
18+
using Serilog.Formatting.Json;
19+
20+
namespace Serilog.Formatting.Compact
21+
{
22+
/// <summary>
23+
/// An <see cref="ITextFormatter"/> that writes events in a compact JSON format, for consumption in environments
24+
/// without message template support. Message templates are rendered into text and a hashed event id is included.
25+
/// </summary>
26+
public class RenderedCompactJsonFormatter : ITextFormatter
27+
{
28+
readonly JsonValueFormatter _valueFormatter;
29+
30+
/// <summary>
31+
/// Construct a <see cref="CompactJsonFormatter"/>, optionally supplying a formatter for
32+
/// <see cref="LogEventPropertyValue"/>s on the event.
33+
/// </summary>
34+
/// <param name="valueFormatter">A value formatter, or null.</param>
35+
public RenderedCompactJsonFormatter(JsonValueFormatter valueFormatter = null)
36+
{
37+
_valueFormatter = valueFormatter ?? new JsonValueFormatter(typeTagName: "$type");
38+
}
39+
40+
/// <summary>
41+
/// Format the log event into the output. Subsequent events will be newline-delimited.
42+
/// </summary>
43+
/// <param name="logEvent">The event to format.</param>
44+
/// <param name="output">The output.</param>
45+
public void Format(LogEvent logEvent, TextWriter output)
46+
{
47+
FormatEvent(logEvent, output, _valueFormatter);
48+
output.WriteLine();
49+
}
50+
51+
/// <summary>
52+
/// Format the log event into the output.
53+
/// </summary>
54+
/// <param name="logEvent">The event to format.</param>
55+
/// <param name="output">The output.</param>
56+
/// <param name="valueFormatter">A value formatter for <see cref="LogEventPropertyValue"/>s on the event.</param>
57+
public static void FormatEvent(LogEvent logEvent, TextWriter output, JsonValueFormatter valueFormatter)
58+
{
59+
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
60+
if (output == null) throw new ArgumentNullException(nameof(output));
61+
if (valueFormatter == null) throw new ArgumentNullException(nameof(valueFormatter));
62+
63+
output.Write("{\"@t\":\"");
64+
output.Write(logEvent.Timestamp.ToString("O"));
65+
output.Write("\",\"@m\":");
66+
var message = logEvent.MessageTemplate.Render(logEvent.Properties);
67+
JsonValueFormatter.WriteQuotedJsonString(message, output);
68+
output.Write(",\"@i\":\"");
69+
var id = EventIdHash.Compute(logEvent.MessageTemplate.Text);
70+
output.Write(id.ToString("x8"));
71+
output.Write('"');
72+
73+
if (logEvent.Level != LogEventLevel.Information)
74+
{
75+
output.Write(",\"@l\":\"");
76+
output.Write(logEvent.Level);
77+
output.Write('\"');
78+
}
79+
80+
if (logEvent.Exception != null)
81+
{
82+
output.Write(",\"@x\":");
83+
JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
84+
}
85+
86+
foreach (var property in logEvent.Properties)
87+
{
88+
var name = property.Key;
89+
if (name.Length > 0 && name[0] == '@')
90+
{
91+
// Escape first '@' by doubling
92+
name = '@' + name;
93+
}
94+
95+
output.Write(',');
96+
JsonValueFormatter.WriteQuotedJsonString(name, output);
97+
output.Write(':');
98+
valueFormatter.Format(property.Value, output);
99+
}
100+
101+
output.Write('}');
102+
}
103+
}
104+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Xunit;
2+
3+
namespace Serilog.Formatting.Compact.Tests
4+
{
5+
public class EventIdHashTests
6+
{
7+
[Fact]
8+
public void HashingIsConsistent()
9+
{
10+
var h1 = EventIdHash.Compute("Template 1");
11+
var h2 = EventIdHash.Compute("Template 1");
12+
Assert.Equal(h1, h2);
13+
}
14+
15+
[Fact]
16+
public void DistinctHashesAreComputed()
17+
{
18+
var h1 = EventIdHash.Compute("Template 1");
19+
var h2 = EventIdHash.Compute("Template 2");
20+
Assert.NotEqual(h1, h2);
21+
}
22+
}
23+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System;
2+
using Newtonsoft.Json.Linq;
3+
using Xunit;
4+
using Serilog.Formatting.Compact.Tests.Support;
5+
6+
7+
namespace Serilog.Formatting.Compact.Tests
8+
{
9+
public class RenderedCompactJsonFormatterTests
10+
{
11+
JObject AssertValidJson(Action<ILogger> act)
12+
{
13+
return Assertions.AssertValidJson(new RenderedCompactJsonFormatter(), act);
14+
}
15+
16+
[Fact]
17+
public void AnEmptyEventIsValidJson()
18+
{
19+
AssertValidJson(log => log.Information("No properties"));
20+
}
21+
22+
[Fact]
23+
public void AMinimalEventIsValidJson()
24+
{
25+
var jobject = AssertValidJson(log => log.Information("One {Property}", 42));
26+
27+
JToken m;
28+
Assert.True(jobject.TryGetValue("@m", out m));
29+
Assert.Equal("One 42", m.ToObject<string>());
30+
31+
JToken i;
32+
Assert.True(jobject.TryGetValue("@i", out i));
33+
Assert.Equal(EventIdHash.Compute("One {Property}").ToString("x8"), i.ToObject<string>());
34+
35+
}
36+
37+
[Fact]
38+
public void MultiplePropertiesAreDelimited()
39+
{
40+
AssertValidJson(log => log.Information("Property {First} and {Second}", "One", "Two"));
41+
}
42+
43+
[Fact]
44+
public void ExceptionsAreFormattedToValidJson()
45+
{
46+
AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception"));
47+
}
48+
49+
[Fact]
50+
public void ExceptionAndPropertiesAreValidJson()
51+
{
52+
AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception and {Property}", 42));
53+
}
54+
55+
[Fact]
56+
public void RenderingsAreValidJson()
57+
{
58+
AssertValidJson(log => log.Information("One {Rendering:x8}", 42));
59+
}
60+
61+
[Fact]
62+
public void MultipleRenderingsAreDelimited()
63+
{
64+
AssertValidJson(log => log.Information("Rendering {First:x8} and {Second:x8}", 1, 2));
65+
}
66+
67+
[Fact]
68+
public void AtPrefixedPropertyNamesAreEscaped()
69+
{
70+
// Not possible in message templates, but accepted this way
71+
var jobject = AssertValidJson(log => log.ForContext("@Mistake", 42)
72+
.Information("Hello"));
73+
74+
JToken val;
75+
Assert.True(jobject.TryGetValue("@@Mistake", out val));
76+
Assert.Equal(42, val.ToObject<int>());
77+
}
78+
}
79+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.IO;
3+
using Newtonsoft.Json.Linq;
4+
5+
namespace Serilog.Formatting.Compact.Tests.Support
6+
{
7+
static class Assertions
8+
{
9+
public static JObject AssertValidJson(ITextFormatter formatter, Action<ILogger> act)
10+
{
11+
var output = new StringWriter();
12+
var log = new LoggerConfiguration()
13+
.WriteTo.Sink(new TextWriterSink(output, formatter))
14+
.CreateLogger();
15+
16+
act(log);
17+
18+
var json = output.ToString();
19+
20+
// Unfortunately this will not detect all JSON formatting issues; better than nothing however.
21+
return JObject.Parse(json);
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)