Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/161 subsecond decimals #172

Merged
merged 9 commits into from Mar 26, 2024
Expand Up @@ -31,6 +31,7 @@ public class CompactSplunkJsonFormatter : ITextFormatter
private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(typeTagName: "$type");
private readonly string _suffix;
private readonly bool _renderTemplate;
private readonly SubSecondPrecision _subSecondPrecision;

/// <summary>
/// Construct a <see cref="CompactSplunkJsonFormatter"/>.
Expand All @@ -40,9 +41,19 @@ public class CompactSplunkJsonFormatter : ITextFormatter
/// <param name="host">The host of the event</param>
/// <param name="index">The Splunk index to log to</param>
/// <param name="renderTemplate">If true, the template used will be rendered and written to the output as a property named MessageTemplate</param>
public CompactSplunkJsonFormatter(bool renderTemplate = false, string source = null, string sourceType = null, string host = null, string index = null)
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>

public CompactSplunkJsonFormatter(
bool renderTemplate = false,
string source = null,
string sourceType = null,
string host = null,
string index = null,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
_renderTemplate = renderTemplate;
_subSecondPrecision = subSecondPrecision;

var suffixWriter = new StringWriter();
suffixWriter.Write("}"); // Terminates "event"

Expand Down Expand Up @@ -80,7 +91,7 @@ public void Format(LogEvent logEvent, TextWriter output)
if (output == null) throw new ArgumentNullException(nameof(output));

output.Write("{\"time\":\"");
output.Write(logEvent.Timestamp.ToEpoch().ToString(CultureInfo.InvariantCulture));
output.Write(logEvent.Timestamp.ToEpoch(_subSecondPrecision).ToString(CultureInfo.InvariantCulture));
output.Write("\",\"event\":{\"@l\":\"");
output.Write(logEvent.Level);
output.Write('"');
Expand Down
19 changes: 14 additions & 5 deletions src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs
Expand Up @@ -16,19 +16,28 @@ namespace Serilog.Sinks.Splunk
{
using System;

internal static class EpochExtensions
/// <summary>
/// Provides extension methods for DateTimeOffset to convert to epoch time.
/// </summary>
public static class EpochExtensions
{
private static DateTimeOffset Epoch = new DateTimeOffset(1970,1,1,0,0,0,TimeSpan.Zero);
private static DateTimeOffset Epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);

public static double ToEpoch(this DateTimeOffset value)
/// <summary>
/// Converts a DateTimeOffset value to epoch time with specified sub-second precision.
/// </summary>
/// <param name="value">The DateTimeOffset value to convert.</param>
/// <param name="subSecondPrecision">The precision of sub-second time measurements.</param>
/// <returns>The epoch time representation of the DateTimeOffset value.</returns>
public static double ToEpoch(this DateTimeOffset value, SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
// From Splunk HTTP Collector Protocol
// The default time format is epoch time format, in the format <sec>.<ms>.
// For example, 1433188255.500 indicates 1433188255 seconds and 500 milliseconds after epoch,
// or Monday, June 1, 2015, at 7:50:55 PM GMT.
// See: http://dev.splunk.com/view/SP-CAAAE6P
// See: https://docs.splunk.com/Documentation/Splunk/9.2.0/SearchReference/Commontimeformatvariables

return Math.Round((value - Epoch).TotalSeconds, 3, MidpointRounding.AwayFromZero);
return Math.Round((value - Epoch).TotalSeconds, (int)subSecondPrecision, MidpointRounding.AwayFromZero);
}
}
}
35 changes: 24 additions & 11 deletions src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs
Expand Up @@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Sinks.PeriodicBatching;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Sinks.PeriodicBatching;

namespace Serilog.Sinks.Splunk
{
Expand Down Expand Up @@ -56,17 +56,23 @@ public class EventCollectorSink : IBatchedLogEventSink
/// <param name="eventCollectorToken">The token to use when authenticating with the event collector</param>
/// <param name="formatProvider">The format provider used when rendering the message</param>
/// <param name="renderTemplate">Whether to render the message template</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
public EventCollectorSink(
string splunkHost,
string eventCollectorToken,
IFormatProvider formatProvider = null,
bool renderTemplate = true)
bool renderTemplate = true,
bool renderMessage = true,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
: this(
splunkHost,
eventCollectorToken,
null, null, null, null, null,
formatProvider,
renderTemplate)
renderTemplate,
renderMessage,
subSecondPrecision: subSecondPrecision)
{
}

Expand All @@ -83,6 +89,8 @@ public class EventCollectorSink : IBatchedLogEventSink
/// <param name="sourceType">The source type of the event</param>
/// <param name="host">The host of the event</param>
/// <param name="messageHandler">The handler used to send HTTP requests</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
public EventCollectorSink(
string splunkHost,
string eventCollectorToken,
Expand All @@ -93,13 +101,14 @@ public class EventCollectorSink : IBatchedLogEventSink
string index,
IFormatProvider formatProvider = null,
bool renderTemplate = true,
HttpMessageHandler messageHandler = null)
bool renderMessage = true,
HttpMessageHandler messageHandler = null,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
: this(
splunkHost,
eventCollectorToken,
uriPath,

new SplunkJsonFormatter(renderTemplate, formatProvider, source, sourceType, host, index),
new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, subSecondPrecision: subSecondPrecision),
messageHandler)
{
}
Expand All @@ -118,6 +127,8 @@ public class EventCollectorSink : IBatchedLogEventSink
/// <param name="sourceType">The source type of the event</param>
/// <param name="host">The host of the event</param>
/// <param name="messageHandler">The handler used to send HTTP requests</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
public EventCollectorSink(
string splunkHost,
string eventCollectorToken,
Expand All @@ -129,13 +140,15 @@ public class EventCollectorSink : IBatchedLogEventSink
CustomFields fields,
IFormatProvider formatProvider = null,
bool renderTemplate = true,
HttpMessageHandler messageHandler = null)
bool renderMessage = true,
HttpMessageHandler messageHandler = null,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
// TODO here is the jsonformatter creation. We must make way to test output of jsonformatter.
: this(
splunkHost,
eventCollectorToken,
uriPath,
new SplunkJsonFormatter(renderTemplate, formatProvider, source, sourceType, host, index, fields),
new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, fields, subSecondPrecision: subSecondPrecision),
messageHandler)
{
}
Expand Down
31 changes: 24 additions & 7 deletions src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs
Expand Up @@ -32,6 +32,8 @@ public class SplunkJsonFormatter : ITextFormatter

private readonly bool _renderTemplate;
private readonly IFormatProvider _formatProvider;
private readonly SubSecondPrecision _subSecondPrecision;
private readonly bool _renderMessage;
private readonly string _suffix;

/// <inheritdoc />
Expand All @@ -40,10 +42,12 @@ public class SplunkJsonFormatter : ITextFormatter
/// </summary>
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="renderTemplate">If true, the template used will be rendered and written to the output as a property named MessageTemplate</param>
/// <param name="renderMessage">Removes the "RenderedMessage" parameter from output JSON message.</param>
public SplunkJsonFormatter(
bool renderTemplate,
bool renderMessage,
IFormatProvider formatProvider)
: this(renderTemplate, formatProvider, null, null, null, null)
: this(renderTemplate, renderMessage, formatProvider, null, null, null, null)
{
}

Expand All @@ -56,14 +60,18 @@ public class SplunkJsonFormatter : ITextFormatter
/// <param name="source">The source of the event</param>
/// <param name="sourceType">The source type of the event</param>
/// <param name="host">The host of the event</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
/// <param name="renderMessage">Removes the "RenderedMessage" parameter from output JSON message.</param>
public SplunkJsonFormatter(
bool renderTemplate,
bool renderMessage,
IFormatProvider formatProvider,
string source,
string sourceType,
string host,
string index)
: this(renderTemplate, formatProvider, source, sourceType, host, index, null)
string index,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
: this(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, null, subSecondPrecision: subSecondPrecision)
{
}

Expand All @@ -77,17 +85,23 @@ public class SplunkJsonFormatter : ITextFormatter
/// <param name="sourceType">The source type of the event</param>
/// <param name="host">The host of the event</param>
/// <param name="customFields">Object that describes extra splunk fields that should be indexed with event see: http://dev.splunk.com/view/event-collector/SP-CAAAFB6 </param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
public SplunkJsonFormatter(
bool renderTemplate,
bool renderMessage,
IFormatProvider formatProvider,
string source,
string sourceType,
string host,
string index,
CustomFields customFields)
CustomFields customFields,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
_renderTemplate = renderTemplate;
_formatProvider = formatProvider;
_subSecondPrecision = subSecondPrecision;
_renderMessage = renderMessage;

using (var suffixWriter = new StringWriter())
{
Expand Down Expand Up @@ -157,7 +171,7 @@ public void Format(LogEvent logEvent, TextWriter output)
if (output == null) throw new ArgumentNullException(nameof(output));

output.Write("{\"time\":\"");
output.Write(logEvent.Timestamp.ToEpoch().ToString(CultureInfo.InvariantCulture));
output.Write(logEvent.Timestamp.ToEpoch(_subSecondPrecision).ToString(CultureInfo.InvariantCulture));
output.Write("\",\"event\":{\"Level\":\"");
output.Write(logEvent.Level);
output.Write('"');
Expand All @@ -168,8 +182,11 @@ public void Format(LogEvent logEvent, TextWriter output)
JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
}

output.Write(",\"RenderedMessage\":");
JsonValueFormatter.WriteQuotedJsonString(logEvent.RenderMessage(_formatProvider), output);
if (!_renderMessage)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this logic be the other way round? ie if (_renderMessage) to match the _renderTemplate logic above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. I removed the !

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the fix

{
output.Write(",\"RenderedMessage\":");
JsonValueFormatter.WriteQuotedJsonString(logEvent.RenderMessage(_formatProvider), output);
}

if (logEvent.Exception != null)
{
Expand Down
37 changes: 37 additions & 0 deletions src/Serilog.Sinks.Splunk/Sinks/Splunk/SubSecondPrecision.cs
@@ -0,0 +1,37 @@
// Copyright 2016 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Serilog.Sinks.Splunk
{
/// <summary>
/// Enum representing the precision of sub-second time measurements.
/// </summary>
public enum SubSecondPrecision
{
/// <summary>
/// Represents precision in milliseconds. Value corresponds to the number of decimal places.
/// </summary>
Milliseconds = 3,

/// <summary>
/// Represents precision in microseconds. Value corresponds to the number of decimal places.
/// </summary>
Microseconds = 6,

/// <summary>
/// Represents precision in nanoseconds. Value corresponds to the number of decimal places.
/// </summary>
Nanoseconds = 9
}
}
24 changes: 18 additions & 6 deletions src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs
Expand Up @@ -17,7 +17,6 @@
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Formatting.Json;
using Serilog.Sinks.PeriodicBatching;
using Serilog.Sinks.Splunk;
using System;
Expand All @@ -44,11 +43,13 @@ public static class SplunkLoggingConfigurationExtensions
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="renderTemplate">If true, the message template will be rendered</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
/// <param name="batchIntervalInSeconds">The interval in seconds that the queue should be instpected for batching</param>
/// <param name="batchSizeLimit">The size of the batch</param>
/// <param name="queueLimit">Maximum number of events in the queue</param>
/// <param name="messageHandler">The handler used to send HTTP requests</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level to be changed at runtime.</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
/// <returns></returns>
public static LoggerConfiguration EventCollector(
this LoggerSinkConfiguration configuration,
Expand All @@ -62,11 +63,13 @@ public static class SplunkLoggingConfigurationExtensions
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
IFormatProvider formatProvider = null,
bool renderTemplate = true,
bool renderMessage = true,
int batchIntervalInSeconds = 2,
int batchSizeLimit = 100,
int? queueLimit = null,
HttpMessageHandler messageHandler = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));

Expand All @@ -88,7 +91,9 @@ public static class SplunkLoggingConfigurationExtensions
index,
formatProvider,
renderTemplate,
messageHandler);
renderMessage,
subSecondPrecision: subSecondPrecision);

var batchingSink = new PeriodicBatchingSink(eventCollectorSink, batchingOptions);

return configuration.Sink(batchingSink, restrictedToMinimumLevel, levelSwitch);
Expand All @@ -103,7 +108,6 @@ public static class SplunkLoggingConfigurationExtensions
/// <param name="jsonFormatter">The text formatter used to render log events into a JSON format for consumption by Splunk</param>
/// <param name="uriPath">Change the default endpoint of the Event Collector e.g. services/collector/event</param>
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>

/// <param name="batchIntervalInSeconds">The interval in seconds that the queue should be instpected for batching</param>
/// <param name="batchSizeLimit">The size of the batch</param>
/// <param name="queueLimit">Maximum number of events in the queue</param>
Expand Down Expand Up @@ -168,6 +172,8 @@ public static class SplunkLoggingConfigurationExtensions
/// <param name="messageHandler">The handler used to send HTTP requests</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level to be changed at runtime.</param>
/// <param name="fields">Customfields that will be indexed in splunk with this event</param>
/// <param name="renderMessage">Include "RenderedMessage" parameter from output JSON message.</param>
/// <param name="subSecondPrecision">Timestamp sub-second precision</param>
/// <returns></returns>
public static LoggerConfiguration EventCollector(
this LoggerSinkConfiguration configuration,
Expand All @@ -182,11 +188,13 @@ public static class SplunkLoggingConfigurationExtensions
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
IFormatProvider formatProvider = null,
bool renderTemplate = true,
bool renderMessage = true,
int batchIntervalInSeconds = 2,
int batchSizeLimit = 100,
int? queueLimit = null,
HttpMessageHandler messageHandler = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));

Expand All @@ -209,7 +217,11 @@ public static class SplunkLoggingConfigurationExtensions
fields,
formatProvider,
renderTemplate,
messageHandler);
renderMessage,
messageHandler,
subSecondPrecision: subSecondPrecision
);


var batchingSink = new PeriodicBatchingSink(eventCollectorSink, batchingOptions);

Expand Down