diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e10c2dd
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,181 @@
+# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
+###############################
+# Core EditorConfig Options #
+###############################
+# All files
+[*]
+indent_style = space
+trim_trailing_whitespace = true
+
+[Caddyfile]
+indent_size = tab
+
+# XML project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,dcproj}]
+indent_size = 2
+
+# XML config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+# JSON config files
+[*.{json,jsonc}]
+indent_size = 2
+insert_final_newline = true
+
+# YAML config files
+[*.{yml,yaml}]
+indent_size = 2
+insert_final_newline = true
+
+# Code files
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+insert_final_newline = true
+charset = utf-8-bom
+
+# Markdown
+[*.{md, mmd}]
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = false
+
+###############################
+# .NET Coding Conventions #
+###############################
+[*.{cs,vb}]
+# Instance fields are camelCase and start with _
+dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
+dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
+dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
+dotnet_naming_symbols.instance_fields.applicable_kinds = field
+dotnet_naming_style.instance_field_style.capitalization = camel_case
+dotnet_naming_style.instance_field_style.required_prefix = _
+# Organize usings
+dotnet_sort_system_directives_first = true
+# this. preferences
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_readonly_field = true:suggestion
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+###############################
+# Naming Conventions #
+###############################
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+# Use PascalCase for constant fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+end_of_line = crlf
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_prefer_collection_expression = true:suggestion
+###############################
+# C# Coding Conventions #
+###############################
+[*.cs]
+# var preferences
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = true:silent
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+# Expression-level preferences
+csharp_prefer_braces = true:silent
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+###############################
+# C# Formatting Rules #
+###############################
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+# Wrapping preferences
+csharp_preserve_single_line_statements = true
+csharp_preserve_single_line_blocks = true
+csharp_using_directive_placement = outside_namespace:suggestion
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = true:silent
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_top_level_statements = true:silent
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_primary_constructors = true:suggestion
+###############################
+# VB Coding Conventions #
+###############################
+[*.vb]
+# Modifier preferences
+visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
diff --git a/CHANGES.md b/CHANGES.md
index b76714a..f606b53 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -69,7 +69,7 @@
- Event Collector fluent interface changed to `.WriteTo.EventCollector`
- Event Collector Sink targeting core
- TCP/UDP Sinks targeting 4.5 *ONLY*
- - Updated Event Collector HTTP Client to add URI endpoint to host: "services/collector" if not included.
+ - Updated Event Collector HTTP Client to add URI endpoint to host: "services/collector/event" if not included.
- Event Collector changed to use epoch time [#15](https://github.com/serilog/serilog-sinks-splunk/pull/15)
## 1.8
diff --git a/Create-DockerfileSolutionRestore.ps1 b/Create-DockerfileSolutionRestore.ps1
new file mode 100644
index 0000000..bd52768
--- /dev/null
+++ b/Create-DockerfileSolutionRestore.ps1
@@ -0,0 +1,32 @@
+param (
+ [string]$solution = "serilog-sinks-splunk.sln"
+)
+
+$outfile = "DockerfileSolutionRestore.txt"
+
+# This script creates the $outfile file, with Dockerfile commands to restore all the packages for the solution,
+# so you can insert them (by hand) into Dockerfiles right before the "COPY . ." line
+# to increase build speed by optimizing the use of docker build images cache.
+
+# This script is only needed when adding or removing projects from the solution.
+
+Write-Output "" > $outfile
+Add-Content -Path $outfile "# Create this ""restore-solution"" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse"
+Select-String -Path $solution -Pattern ', "(.*?\.csproj)"' | ForEach-Object { $_.Matches.Groups[1].Value.Replace("\", "/") } | Sort-Object | ForEach-Object {"COPY [""$_"", """ + $_.Substring(0, $_.LastIndexOf("/") + 1) + """]"} | Out-File -FilePath $outfile -Append
+Add-Content -Path $outfile "COPY [""docker-compose.dcproj"", ""./""]"
+Add-Content -Path $outfile "COPY [""$solution"", ""./""]"
+Add-Content -Path $outfile "RUN dotnet restore ""$solution"""
+Add-Content -Path $outfile ""
+
+
+Add-Content -Path $outfile "# Docker Compose Paths"
+
+Get-ChildItem -Path "./" -Recurse -Filter "Dockerfile" |
+ Resolve-Path -Relative |
+ ForEach-Object { $_.Replace("\", "/") }
+ Sort-Object |
+ ForEach-Object {" ""$_"""} |
+Out-File -FilePath $outfile -Append
+
+
+Get-Content $outfile
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index ac43bb8..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-FROM microsoft/dotnet:8.0-sdk AS build
-ADD . /
-WORKDIR /sample/Sample
-RUN dotnet restore
-RUN dotnet publish -c Release -o out -f net8.0
-
-FROM microsoft/dotnet:8.0-runtime AS runtime
-WORKDIR /sample/Sample
-COPY --from=build /sample/Sample/out ./
-ENTRYPOINT ["dotnet", "Sample.dll"]
\ No newline at end of file
diff --git a/DockerfileSolutionRestore.txt b/DockerfileSolutionRestore.txt
new file mode 100644
index 0000000..a783374
--- /dev/null
+++ b/DockerfileSolutionRestore.txt
@@ -0,0 +1,13 @@
+
+# Create this "restore-solution" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse
+COPY ["sample/Sample/Sample.csproj", "sample/Sample/"]
+COPY ["src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj", "src/Serilog.Sinks.Splunk/"]
+COPY ["src/Serilog.Sinks.TCP/Serilog.Sinks.Splunk.TCP.csproj", "src/Serilog.Sinks.TCP/"]
+COPY ["src/Serilog.Sinks.UDP/Serilog.Sinks.Splunk.UDP.csproj", "src/Serilog.Sinks.UDP/"]
+COPY ["test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj", "test/Serilog.Sinks.Splunk.Tests/"]
+COPY ["docker-compose.dcproj", "./"]
+COPY ["nuget.config", "./"]
+COPY ["serilog-sinks-splunk.sln", "./"]
+RUN dotnet restore "serilog-sinks-splunk.sln"
+
+# Docker Compose Paths
diff --git a/docker-compose.dcproj b/docker-compose.dcproj
new file mode 100644
index 0000000..a21fe1d
--- /dev/null
+++ b/docker-compose.dcproj
@@ -0,0 +1,17 @@
+
+
+
+ 2.1
+ Linux
+ 1b9defa3-d600-45fa-93a5-79006076fb5c
+ serilogsinkssplunk
+
+
+
+
+ docker-compose.yml
+
+
+
+
+
\ No newline at end of file
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
new file mode 100644
index 0000000..6c2a928
--- /dev/null
+++ b/docker-compose.override.yml
@@ -0,0 +1,13 @@
+version: '3.4'
+#https://learn.microsoft.com/en-us/visualstudio/containers/docker-compose-properties
+
+services:
+ splunk:
+ ports:
+ - 8000:8000
+ - 8088:8088
+ - 8089:8089
+
+ sampleconsoleapp:
+ ports:
+ - 8080:8080
diff --git a/docker-compose.yml b/docker-compose.yml
index db99c14..7639e45 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,25 +1,22 @@
version: '3'
services:
splunk:
- build: ./sample/splunk
- image: serilog-splunk
+ container_name: splunk
+ build:
+ context: .
+ dockerfile: sample/splunk/Dockerfile
+ volumes:
+ - "./sample/splunk/default.yml:/tmp/defaults/default.yml"
environment:
- SPLUNK_START_ARGS: --accept-license --answer-yes --seed-passwd changeme
- SPLUNK_ENABLE_LISTEN: 9997
- SPLUNK_PASSWORD: changemeplease!
- ports:
- - 8000:8000
- - 8088:8088
- - 8089:8089
- networks:
- splunkbase_docker:
+ SPLUNK_START_ARGS: --accept-license --answer-yes --seed-passwd changeme
+ SPLUNK_ENABLE_LISTEN: 9997
+ SPLUNK_PASSWORD: changemeplease!
+ SPLUNK_HEC_TOKEN: 00112233-4455-6677-8899-AABBCCDDEEFF
+
sampleconsoleapp:
+ container_name: sample
depends_on:
- "splunk"
- build: .
- image: serilog-console-sample
- networks:
- splunkbase_docker:
-networks:
- splunkbase_docker:
-
+ build:
+ context: .
+ dockerfile: sample/Sample/Dockerfile
diff --git a/sample/Sample/Dockerfile b/sample/Sample/Dockerfile
new file mode 100644
index 0000000..a23ba1c
--- /dev/null
+++ b/sample/Sample/Dockerfile
@@ -0,0 +1,31 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
+WORKDIR /app
+EXPOSE 8080
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+
+# Create this "restore-solution" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse
+COPY ["sample/Sample/Sample.csproj", "sample/Sample/"]
+COPY ["src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj", "src/Serilog.Sinks.Splunk/"]
+COPY ["src/Serilog.Sinks.TCP/Serilog.Sinks.Splunk.TCP.csproj", "src/Serilog.Sinks.TCP/"]
+COPY ["src/Serilog.Sinks.UDP/Serilog.Sinks.Splunk.UDP.csproj", "src/Serilog.Sinks.UDP/"]
+COPY ["test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj", "test/Serilog.Sinks.Splunk.Tests/"]
+COPY ["docker-compose.dcproj", "./"]
+COPY ["serilog-sinks-splunk.sln", "./"]
+RUN dotnet restore "serilog-sinks-splunk.sln"
+
+COPY . .
+WORKDIR "/src/sample/Sample/"
+RUN dotnet build "./Sample.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./Sample.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "Sample.dll"]
\ No newline at end of file
diff --git a/sample/Sample/Program.cs b/sample/Sample/Program.cs
index 1f1dfd9..459c3a7 100644
--- a/sample/Sample/Program.cs
+++ b/sample/Sample/Program.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
namespace Sample
{
@@ -15,7 +16,7 @@ public class Program
const string SPLUNK_HEC_TOKEN = "00112233-4455-6677-8899-AABBCCDDEEFF"; // Your HEC token. See http://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector
public static string EventCollectorToken = SPLUNK_HEC_TOKEN;
- public static void Main(string[] args)
+ public static async Task Main(string[] args)
{
// Bootstrap a simple logger.
var logger = new LoggerConfiguration()
@@ -27,9 +28,9 @@ public static void Main(string[] args)
logger.Information("Sample app starting up...");
logger.Information("Startup arguments \"{Arguments}\".", args);
- var eventsToCreate = 100;
+ var eventsToCreate = 10;
var runSSL = false;
- var millisecsToWait = 60000;
+ var secToWait = 30;
if (args.Length > 0)
eventsToCreate = int.Parse(args[0]);
@@ -38,7 +39,7 @@ public static void Main(string[] args)
runSSL = bool.Parse(args[1]);
if (args.Length == 3)
- millisecsToWait = int.Parse(args[2]);
+ secToWait = int.Parse(args[2]);
Serilog.Debugging.SelfLog.Enable(msg =>
{
@@ -46,8 +47,18 @@ public static void Main(string[] args)
throw new Exception("Failed to write to Serilog.", new Exception(msg));
});
- logger.Information("Waiting {MillisecondsToWait} millisecs...", millisecsToWait);
- System.Threading.Thread.Sleep(millisecsToWait);
+
+ while (secToWait-- > 0)
+ {
+ logger.Information("Waiting {secToWait} seconds...", secToWait);
+ await Task.Delay(1000);
+ }
+
+ logger.Information("Creating logger {MethodName}.", nameof(OverridingSubsecondPrecisionMicroseconds));
+ OverridingSubsecondPrecisionMicroseconds(eventsToCreate);
+
+ logger.Information("Creating logger {MethodName}.", nameof(OverridingSubsecondPrecisionNanoseconds));
+ OverridingSubsecondPrecisionNanoseconds(eventsToCreate);
logger.Information("Creating logger {MethodName}.", nameof(UsingAppSettingsJson));
UsingAppSettingsJson(eventsToCreate);
@@ -131,6 +142,50 @@ private static void WithCompactSplunkFormatter(int eventsToCreate)
Log.CloseAndFlush();
}
+ public static void OverridingSubsecondPrecisionMicroseconds(int eventsToCreate)
+ {
+ // Override Source
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .WriteTo.EventCollector(
+ SPLUNK_ENDPOINT,
+ Program.EventCollectorToken,
+ subSecondPrecision: SubSecondPrecision.Microseconds)
+ .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
+ .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Override")
+ .CreateLogger();
+
+ foreach (var i in Enumerable.Range(0, eventsToCreate))
+ {
+ Log.Information("Running source override loop {Counter} subseconds: {subsecondPrecision}", i, SubSecondPrecision.Microseconds.ToString());
+ }
+
+ Log.CloseAndFlush();
+ }
+
+ public static void OverridingSubsecondPrecisionNanoseconds(int eventsToCreate)
+ {
+ // Override Source
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .WriteTo.EventCollector(
+ SPLUNK_ENDPOINT,
+ Program.EventCollectorToken,
+ subSecondPrecision: SubSecondPrecision.Nanoseconds)
+ .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
+ .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Override")
+ .CreateLogger();
+
+ foreach (var i in Enumerable.Range(0, eventsToCreate))
+ {
+ Log.Information("Running source override loop {Counter} subseconds: {subsecondPrecision}", i, SubSecondPrecision.Nanoseconds.ToString());
+ }
+
+ Log.CloseAndFlush();
+ }
+
public static void OverridingSource(int eventsToCreate)
{
// Override Source
@@ -229,7 +284,7 @@ public static void UsingHostOnly(int eventsToCreate)
SPLUNK_ENDPOINT,
Program.EventCollectorToken)
.Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
- .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Vanilla No services/collector in uri")
+ .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Vanilla No services/collector/event in uri")
.CreateLogger();
foreach (var i in Enumerable.Range(0, eventsToCreate))
diff --git a/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj
index 0521562..6fd1eb4 100644
--- a/sample/Sample/Sample.csproj
+++ b/sample/Sample/Sample.csproj
@@ -16,7 +16,7 @@
-
+
PreserveNewest
diff --git a/sample/Sample/appsettings.json b/sample/Sample/appsettings.json
index 70ed38e..7fab711 100644
--- a/sample/Sample/appsettings.json
+++ b/sample/Sample/appsettings.json
@@ -21,4 +21,4 @@
"Serilog.Sinks.Splunk.Sample.TestType": "AppSettings.json"
}
}
-}
\ No newline at end of file
+}
diff --git a/sample/splunk/Dockerfile b/sample/splunk/Dockerfile
index 9f46425..f006f84 100644
--- a/sample/splunk/Dockerfile
+++ b/sample/splunk/Dockerfile
@@ -1,2 +1,2 @@
FROM splunk/splunk:latest
-ADD etc ${SPLUNK_HOME}/etc
\ No newline at end of file
+ADD sample/splunk/etc ${SPLUNK_HOME}/etc
\ No newline at end of file
diff --git a/sample/splunk/default.yml b/sample/splunk/default.yml
new file mode 100644
index 0000000..c344bef
--- /dev/null
+++ b/sample/splunk/default.yml
@@ -0,0 +1,7 @@
+splunk:
+ hec:
+ enable: True
+ ssl: false
+ port: 8088
+ # hec.token is used only for ingestion (receiving Splunk events)
+ token: 00112233-4455-6677-8899-AABBCCDDEEFF
diff --git a/sample/splunk/etc/apps/splunk_httpinput/local/inputs.conf b/sample/splunk/etc/apps/splunk_httpinput/local/inputs.conf
index ff47956..a65d8a1 100644
--- a/sample/splunk/etc/apps/splunk_httpinput/local/inputs.conf
+++ b/sample/splunk/etc/apps/splunk_httpinput/local/inputs.conf
@@ -2,5 +2,5 @@
disabled = 0
enableSSL = 0
-[http://serilog_sample]
+[http://splunk-sample]
token = 00112233-4455-6677-8899-AABBCCDDEEFF
\ No newline at end of file
diff --git a/sample/splunk/etc/system/local/props.conf b/sample/splunk/etc/system/local/props.conf
new file mode 100644
index 0000000..665e07b
--- /dev/null
+++ b/sample/splunk/etc/system/local/props.conf
@@ -0,0 +1,4 @@
+TIME_PREFIX = \"time\"\:\s*\"
+TIME_FORMAT = %Y-%m-%d %H:%M:%S.%9N
+ADD_EXTRA_TIME_FIELDS = all
+MAX_TIMESTAMP_LOOKAHEAD = 30
\ No newline at end of file
diff --git a/serilog-sinks-splunk.sln b/serilog-sinks-splunk.sln
index ba502cd..c1f15b4 100644
--- a/serilog-sinks-splunk.sln
+++ b/serilog-sinks-splunk.sln
@@ -1,12 +1,13 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26430.12
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34714.143
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{B9B13339-749C-4098-8845-780ED4FA488A}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
.travis.yml = .travis.yml
appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
@@ -20,15 +21,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{1C75E4
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B9451AD8-09B9-4C09-A152-FBAE24806614}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Splunk", "src\Serilog.Sinks.Splunk\Serilog.Sinks.Splunk.csproj", "{32CF915C-3ECD-496C-8FDB-1214581274A6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk", "src\Serilog.Sinks.Splunk\Serilog.Sinks.Splunk.csproj", "{32CF915C-3ECD-496C-8FDB-1214581274A6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Splunk.Tests", "test\Serilog.Sinks.Splunk.Tests\Serilog.Sinks.Splunk.Tests.csproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk.Tests", "test\Serilog.Sinks.Splunk.Tests\Serilog.Sinks.Splunk.Tests.csproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "sample\Sample\Sample.csproj", "{4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "sample\Sample\Sample.csproj", "{4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Splunk.TCP", "src\Serilog.Sinks.TCP\Serilog.Sinks.Splunk.TCP.csproj", "{F74FCFD0-536B-4311-AA66-0BD16112D895}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk.TCP", "src\Serilog.Sinks.TCP\Serilog.Sinks.Splunk.TCP.csproj", "{F74FCFD0-536B-4311-AA66-0BD16112D895}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Sinks.Splunk.UDP", "src\Serilog.Sinks.UDP\Serilog.Sinks.Splunk.UDP.csproj", "{FE1504A6-5444-4B87-819C-E6F477662B7F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk.UDP", "src\Serilog.Sinks.UDP\Serilog.Sinks.Splunk.UDP.csproj", "{FE1504A6-5444-4B87-819C-E6F477662B7F}"
+EndProject
+Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1B9DEFA3-D600-45FA-93A5-79006076FB5C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "splunk", "splunk", "{21EEF50A-C0FC-4406-97A1-8F5F499AE2FC}"
+ ProjectSection(SolutionItems) = preProject
+ sample\splunk\Dockerfile = sample\splunk\Dockerfile
+ EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -56,6 +64,10 @@ Global
{FE1504A6-5444-4B87-819C-E6F477662B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FE1504A6-5444-4B87-819C-E6F477662B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE1504A6-5444-4B87-819C-E6F477662B7F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -66,5 +78,9 @@ Global
{4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B} = {1C75E4A9-4CB1-497C-AD17-B438882051A1}
{F74FCFD0-536B-4311-AA66-0BD16112D895} = {7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}
{FE1504A6-5444-4B87-819C-E6F477662B7F} = {7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}
+ {21EEF50A-C0FC-4406-97A1-8F5F499AE2FC} = {1C75E4A9-4CB1-497C-AD17-B438882051A1}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D7BFF439-D18D-4124-A36F-15CFB8E84BCC}
EndGlobalSection
EndGlobal
diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
index 79132fa..7b56f49 100644
--- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
+++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
@@ -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;
///
/// Construct a .
@@ -40,9 +41,19 @@ public class CompactSplunkJsonFormatter : ITextFormatter
/// The host of the event
/// The Splunk index to log to
/// If true, the template used will be rendered and written to the output as a property named MessageTemplate
- public CompactSplunkJsonFormatter(bool renderTemplate = false, string source = null, string sourceType = null, string host = null, string index = null)
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
+
+ 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"
@@ -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));
output.Write("\",\"event\":{\"@l\":\"");
output.Write(logEvent.Level);
output.Write('"');
@@ -134,4 +145,4 @@ public void Format(LogEvent logEvent, TextWriter output)
output.WriteLine(_suffix);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs
index 0f4c39b..1d85a2d 100644
--- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs
+++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs
@@ -1,4 +1,4 @@
-// Copyright 2016 Serilog Contributors
+// 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.
@@ -15,20 +15,49 @@
namespace Serilog.Sinks.Splunk
{
using System;
+ using System.Globalization;
- internal static class EpochExtensions
+ ///
+ /// Provides extension methods for DateTimeOffset to convert to epoch time.
+ ///
+ 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);
+ const string MillisecondFormat = "#.000";
+ const string MicrosecondFormat = "#.000000";
+ const string NanosecondFormat = "#.000000000";
- public static double ToEpoch(this DateTimeOffset value)
+ ///
+ /// Converts a DateTimeOffset value to epoch time with specified sub-second precision.
+ ///
+ /// The DateTimeOffset value to convert.
+ /// Timestamp sub-second precision.
+ /// The epoch time representation of the DateTimeOffset value.
+ public static string ToEpoch(this DateTimeOffset value, SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
{
// From Splunk HTTP Collector Protocol
// The default time format is epoch time format, in the format ..
// 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);
+ var totalSeconds = ToSeconds(value.Ticks - Epoch.Ticks);
+ var format = subSecondPrecision switch {
+ SubSecondPrecision.Nanoseconds => NanosecondFormat,
+ SubSecondPrecision.Microseconds => MicrosecondFormat,
+ _ => MillisecondFormat,
+ };
+
+ return Math.Round(totalSeconds, (int)subSecondPrecision, MidpointRounding.AwayFromZero).ToString(format, CultureInfo.InvariantCulture);
+ }
+
+ private static decimal ToSeconds(long ticks)
+ {
+ long wholeSecondPortion = (ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond;
+ long subsecondPortion = ticks - wholeSecondPortion;
+ decimal wholeSeconds = wholeSecondPortion / (decimal)TimeSpan.TicksPerSecond;
+ decimal subseconds = subsecondPortion / (decimal)TimeSpan.TicksPerSecond;
+ return wholeSeconds + subseconds;
}
}
}
diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs
index 1754dd2..79c5e2f 100644
--- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs
+++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs
@@ -1,4 +1,4 @@
-// Copyright 2016 Serilog Contributors
+// 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.
@@ -12,6 +12,10 @@
// 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;
@@ -19,10 +23,6 @@
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
{
@@ -56,17 +56,23 @@ public class EventCollectorSink : IBatchedLogEventSink
/// The token to use when authenticating with the event collector
/// The format provider used when rendering the message
/// Whether to render the message template
+ /// Include "RenderedMessage" parameter from output JSON message.
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
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)
{
}
@@ -83,6 +89,8 @@ public class EventCollectorSink : IBatchedLogEventSink
/// The source type of the event
/// The host of the event
/// The handler used to send HTTP requests
+ /// Include "RenderedMessage" parameter from output JSON message.
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
public EventCollectorSink(
string splunkHost,
string eventCollectorToken,
@@ -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)
{
}
@@ -118,6 +127,8 @@ public class EventCollectorSink : IBatchedLogEventSink
/// The source type of the event
/// The host of the event
/// The handler used to send HTTP requests
+ /// Include "RenderedMessage" parameter from output JSON message.
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
public EventCollectorSink(
string splunkHost,
string eventCollectorToken,
@@ -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)
{
}
diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs
index 17ee054..ac9eaa5 100644
--- a/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs
+++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs
@@ -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;
///
@@ -40,10 +42,12 @@ public class SplunkJsonFormatter : ITextFormatter
///
/// Supplies culture-specific formatting information, or null.
/// If true, the template used will be rendered and written to the output as a property named MessageTemplate
+ /// Removes the "RenderedMessage" parameter from output JSON message.
public SplunkJsonFormatter(
bool renderTemplate,
+ bool renderMessage,
IFormatProvider formatProvider)
- : this(renderTemplate, formatProvider, null, null, null, null)
+ : this(renderTemplate, renderMessage, formatProvider, null, null, null, null)
{
}
@@ -56,14 +60,18 @@ public class SplunkJsonFormatter : ITextFormatter
/// The source of the event
/// The source type of the event
/// The host of the event
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
+ /// Removes the "RenderedMessage" parameter from output JSON message.
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)
{
}
@@ -77,17 +85,23 @@ public class SplunkJsonFormatter : ITextFormatter
/// The source type of the event
/// The host of the event
/// Object that describes extra splunk fields that should be indexed with event see: http://dev.splunk.com/view/event-collector/SP-CAAAFB6
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
+ /// Include "RenderedMessage" parameter from output JSON message.
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())
{
@@ -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));
output.Write("\",\"event\":{\"Level\":\"");
output.Write(logEvent.Level);
output.Write('"');
@@ -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)
+ {
+ output.Write(",\"RenderedMessage\":");
+ JsonValueFormatter.WriteQuotedJsonString(logEvent.RenderMessage(_formatProvider), output);
+ }
if (logEvent.Exception != null)
{
diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/SubSecondPrecision.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/SubSecondPrecision.cs
new file mode 100644
index 0000000..7788daa
--- /dev/null
+++ b/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
+{
+ ///
+ /// Enum representing the precision of sub-second time measurements.
+ ///
+ public enum SubSecondPrecision
+ {
+ ///
+ /// Represents precision in milliseconds. Value corresponds to the number of decimal places.
+ ///
+ Milliseconds = 3,
+
+ ///
+ /// Represents precision in microseconds. Value corresponds to the number of decimal places.
+ ///
+ Microseconds = 6,
+
+ ///
+ /// Represents precision in nanoseconds. Value corresponds to the number of decimal places.
+ ///
+ Nanoseconds = 9
+ }
+}
diff --git a/src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs b/src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs
index 05165f6..333dd81 100644
--- a/src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs
+++ b/src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs
@@ -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;
@@ -44,11 +43,13 @@ public static class SplunkLoggingConfigurationExtensions
/// The minimum log event level required in order to write an event to the sink.
/// Supplies culture-specific formatting information, or null.
/// If true, the message template will be rendered
+ /// Include "RenderedMessage" parameter from output JSON message.
/// The interval in seconds that the queue should be instpected for batching
/// The size of the batch
/// Maximum number of events in the queue
/// The handler used to send HTTP requests
/// A switch allowing the pass-through minimum level to be changed at runtime.
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
///
public static LoggerConfiguration EventCollector(
this LoggerSinkConfiguration configuration,
@@ -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));
@@ -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);
@@ -103,7 +108,6 @@ public static class SplunkLoggingConfigurationExtensions
/// The text formatter used to render log events into a JSON format for consumption by Splunk
/// Change the default endpoint of the Event Collector e.g. services/collector/event
/// The minimum log event level required in order to write an event to the sink.
-
/// The interval in seconds that the queue should be instpected for batching
/// The size of the batch
/// Maximum number of events in the queue
@@ -168,6 +172,8 @@ public static class SplunkLoggingConfigurationExtensions
/// The handler used to send HTTP requests
/// A switch allowing the pass-through minimum level to be changed at runtime.
/// Customfields that will be indexed in splunk with this event
+ /// Include "RenderedMessage" parameter from output JSON message.
+ /// Timestamp sub-second precision. Splunk props.conf setup is required.
///
public static LoggerConfiguration EventCollector(
this LoggerSinkConfiguration configuration,
@@ -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));
@@ -209,7 +217,11 @@ public static class SplunkLoggingConfigurationExtensions
fields,
formatProvider,
renderTemplate,
- messageHandler);
+ renderMessage,
+ messageHandler,
+ subSecondPrecision: subSecondPrecision
+ );
+
var batchingSink = new PeriodicBatchingSink(eventCollectorSink, batchingOptions);
diff --git a/test/Serilog.Sinks.Splunk.Tests/EpochExtensionsTests.cs b/test/Serilog.Sinks.Splunk.Tests/EpochExtensionsTests.cs
new file mode 100644
index 0000000..949905d
--- /dev/null
+++ b/test/Serilog.Sinks.Splunk.Tests/EpochExtensionsTests.cs
@@ -0,0 +1,67 @@
+using Serilog.Sinks.Splunk;
+using System;
+using System.Diagnostics;
+using Xunit;
+
+namespace Serilog.Sinks.Splunk.Tests
+{
+ public class EpochExtensionsTests
+ {
+ [Fact]
+ public void ToEpoch_ShouldReturnCorrectEpochTime()
+ {
+ // Arrange
+ var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero);
+ var expectedEpochTime = "1640995200.000"; // Epoch time for 2022-01-01 00:00:00
+
+ // Act
+ var result = dateTimeOffset.ToEpoch();
+
+ // Assert
+ Assert.Equal(expectedEpochTime, result);
+ }
+
+ [Fact]
+ public void ToEpoch_ShouldReturnCorrectEpochTime_Milliseconds()
+ {
+ // Arrange
+ var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero);
+ var expectedEpochTime = "1640995200.123"; // Epoch time for 2022-01-01 00:00:00.123
+
+ // Act
+ var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Milliseconds);
+
+ // Assert
+ Assert.Equal(expectedEpochTime, result);
+ }
+
+ [Fact]
+ public void ToEpoch_ShouldReturnCorrectEpochTime_Microseconds()
+ {
+ // Arrange
+ var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero) + TimeSpan.FromMicroseconds(456);
+ var expectedEpochTime = "1640995200.123456"; // Epoch time for 2022-01-01 00:00:00.123
+
+ // Act
+ var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Microseconds);
+
+ // Assert
+ Assert.Equal(expectedEpochTime, result);
+ }
+
+ [Fact]
+ public void ToEpoch_ShouldReturnCorrectEpochTime_Nanoseconds()
+ {
+ // Arrange
+ // using from ticks here, NanoSeconds is not available in TimeSpan. Nanoseconds Per Tick = 100L.
+ var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero) + TimeSpan.FromMicroseconds(456) + TimeSpan.FromTicks(7);
+ var expectedEpochTime = "1640995200.123456700"; // Epoch time for 2022-01-01 00:00:00.123
+
+ // Act
+ var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Nanoseconds);
+
+ // Assert
+ Assert.Equal(expectedEpochTime, result);
+ }
+ }
+}
diff --git a/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj b/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj
index 7f73a20..3ab383c 100644
--- a/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj
+++ b/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj
@@ -3,25 +3,24 @@
net8.0
Serilog.Sinks.Splunk.Tests
- Serilog.Sinks.Splunk.Tests
- true
- $(PackageTargetFallback);dnxcore50;portable-net45+win8
- 1.0.4
- ../../assets/Serilog.snk
- true
- true
- true
true
-
-
+
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
diff --git a/test/Serilog.Sinks.Splunk.Tests/SplunkJsonFormatterTests.cs b/test/Serilog.Sinks.Splunk.Tests/SplunkJsonFormatterTests.cs
index 2310062..af34962 100644
--- a/test/Serilog.Sinks.Splunk.Tests/SplunkJsonFormatterTests.cs
+++ b/test/Serilog.Sinks.Splunk.Tests/SplunkJsonFormatterTests.cs
@@ -1,28 +1,29 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Xunit;
-using Serilog.Sinks.Splunk.Tests.Support;
using Serilog.Events;
using Serilog.Parsing;
+using Serilog.Sinks.Splunk.Tests.Support;
+using System;
+using System.Collections.Generic;
using System.Globalization;
-using Newtonsoft.Json;
+using System.IO;
+using Xunit;
namespace Serilog.Sinks.Splunk.Tests
{
+
public class SplunkJsonFormatterTests
{
- void AssertValidJson(Action act,
+ void AssertValidJson(Action act,
string source = "",
- string sourceType= "",
- string host= "",
- string index= "",
- CustomFields fields=null)
+ string sourceType = "",
+ string host = "",
+ string index = "",
+ CustomFields fields = null)
{
StringWriter outputRendered = new StringWriter(), output = new StringWriter();
var log = new LoggerConfiguration()
- .WriteTo.Sink(new TextWriterSink(output, new SplunkJsonFormatter(false, null, source, sourceType, host, index)))
- .WriteTo.Sink(new TextWriterSink(outputRendered, new SplunkJsonFormatter(true, null, source, sourceType, host, index)))
+ .WriteTo.Sink(new TextWriterSink(output, new SplunkJsonFormatter(false, true, null, source, sourceType, host, index)))
+ .WriteTo.Sink(new TextWriterSink(outputRendered, new SplunkJsonFormatter(true, true, null, source, sourceType, host, index)))
.CreateLogger();
act(log);
@@ -67,7 +68,7 @@ public void AMinimalEventWithSourceIsValidJson()
{
AssertValidJson(log => log.Information("One {Property}", 42), source: "A Test Source");
}
-
+
[Fact]
public void AMinimalEventWithSourceTypeIsValidJson()
{
@@ -122,7 +123,7 @@ public void Test_CustomFields_Jsonformatter_for_Splunk_Sink()
var timeStamp = DateTimeOffset.Now;
// var timeStampUnix = (Math.Round(timeStamp.ToUnixTimeMilliseconds() / 1000.0, 3)).ToString("##.###", CultureInfo.InvariantCulture); //req dotnet 4.6.2
var timeStampUnix = ((Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds).ToString("##.###", CultureInfo.InvariantCulture);
- var sut = new SplunkJsonFormatter(renderTemplate: true, formatProvider: null, source: "BackPackTestServerChannel", sourceType: "_json", host: "Wanda", index: "Main", customFields: metaData);
+ var sut = new SplunkJsonFormatter(renderTemplate: true, renderMessage: true, formatProvider: null, source: "BackPackTestServerChannel", sourceType: "_json", host: "Wanda", index: "Main", customFields: metaData);
try
{
var willnotwork = a / b;
@@ -189,6 +190,6 @@ public class TestEventResultObject
public TestCustomFields Fields { get; set; }
}
- #endregion
+ #endregion
}
}
\ No newline at end of file