├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github
├── dependabot.yml
├── release.yml
└── workflows
│ ├── ci-release.yml
│ ├── ci.yml
│ ├── code-coverage.yml
│ └── codeql.yml
├── .gitignore
├── Build.ps1
├── CHANGES.md
├── Create-DockerfileSolutionRestore.ps1
├── DockerfileSolutionRestore.txt
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── Serilog.sln.DotSettings
├── Setup.ps1
├── assets
└── Serilog.snk
├── docker-compose.dcproj
├── docker-compose.override.yml
├── docker-compose.yml
├── sample
├── Sample
│ ├── Dockerfile
│ ├── Program.cs
│ ├── Sample.csproj
│ └── appsettings.json
└── splunk
│ ├── Dockerfile
│ ├── default.yml
│ └── etc
│ ├── apps
│ └── splunk_httpinput
│ │ └── local
│ │ └── inputs.conf
│ └── system
│ └── local
│ ├── inputs.conf
│ ├── props.conf
│ ├── server.conf
│ └── web.conf
├── serilog-sink-nuget.png
├── serilog-sinks-splunk.sln
├── src
├── Serilog.Sinks.Splunk
│ ├── ConfigurationDefaults.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Serilog.Sinks.Splunk.csproj
│ ├── Sinks
│ │ └── Splunk
│ │ │ ├── CompactSplunkJsonFormatter.cs
│ │ │ ├── CustomField.cs
│ │ │ ├── CustomFields.cs
│ │ │ ├── Epoch.cs
│ │ │ ├── EventCollectorClient.cs
│ │ │ ├── EventCollectorRequest.cs
│ │ │ ├── EventCollectorSink.cs
│ │ │ ├── SplunkJsonFormatter.cs
│ │ │ └── SubSecondPrecision.cs
│ └── SplunkLoggingConfigurationExtensions.cs
├── Serilog.Sinks.TCP
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Serilog.Sinks.Splunk.TCP.csproj
│ ├── Sinks
│ │ └── Splunk
│ │ │ ├── SplunkTcpSinkConnectionInfo.cs
│ │ │ └── TcpSink.cs
│ ├── Splunk.Logging
│ │ ├── ExponentialBackoffTcpReconnectionPolicy.cs
│ │ ├── FixedSizeQueue.cs
│ │ ├── ITcpReconnectionPolicy.cs
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── TcpSocketWriter.cs
│ └── SplunkLoggingConfigurationExtensions.cs
├── Serilog.Sinks.UDP
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Serilog.Sinks.Splunk.UDP.csproj
│ ├── Sinks
│ │ └── Splunk
│ │ │ ├── SplunkUdpSinkConnectionInfo.cs
│ │ │ └── UdpSink.cs
│ └── SplunkLoggingConfigurationExtensions.cs
└── common.props
└── test
├── Serilog.Sinks.Splunk.TCP.Tests.Disabled
├── Properties
│ └── launchSettings.json
├── Serilog.Sinks.Splunk.TCP.Tests.csproj
└── TCPCollectorTests.cs
└── Serilog.Sinks.Splunk.Tests
├── CompactSplunkJsonFormatterTests.cs
├── EpochExtensionsTests.cs
├── Properties
└── launchSettings.json
├── Serilog.Sinks.Splunk.Tests.csproj
├── SplunkJsonFormatterTests.cs
└── Support
└── TextWriterSink.cs
/.dockerignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs
2 | ###############################
3 | # Core EditorConfig Options #
4 | ###############################
5 | # All files
6 | [*]
7 | indent_style = space
8 | trim_trailing_whitespace = true
9 |
10 | [Caddyfile]
11 | indent_size = tab
12 |
13 | # XML project files
14 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,dcproj}]
15 | indent_size = 2
16 |
17 | # XML config files
18 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
19 | indent_size = 2
20 |
21 | # JSON config files
22 | [*.{json,jsonc}]
23 | indent_size = 2
24 | insert_final_newline = true
25 |
26 | # YAML config files
27 | [*.{yml,yaml}]
28 | indent_size = 2
29 | insert_final_newline = true
30 |
31 | # Code files
32 | [*.{cs,csx,vb,vbx}]
33 | indent_size = 4
34 | insert_final_newline = true
35 | charset = utf-8-bom
36 |
37 | # Markdown
38 | [*.{md, mmd}]
39 | indent_size = 4
40 | insert_final_newline = true
41 | trim_trailing_whitespace = false
42 |
43 | ###############################
44 | # .NET Coding Conventions #
45 | ###############################
46 | [*.{cs,vb}]
47 | # Instance fields are camelCase and start with _
48 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
49 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
50 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
51 | dotnet_naming_symbols.instance_fields.applicable_kinds = field
52 | dotnet_naming_style.instance_field_style.capitalization = camel_case
53 | dotnet_naming_style.instance_field_style.required_prefix = _
54 | # Organize usings
55 | dotnet_sort_system_directives_first = true
56 | # this. preferences
57 | dotnet_style_qualification_for_field = false:silent
58 | dotnet_style_qualification_for_property = false:silent
59 | dotnet_style_qualification_for_method = false:silent
60 | dotnet_style_qualification_for_event = false:silent
61 | # Language keywords vs BCL types preferences
62 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent
63 | dotnet_style_predefined_type_for_member_access = true:silent
64 | # Parentheses preferences
65 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
66 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
67 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
68 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
69 | # Modifier preferences
70 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
71 | dotnet_style_readonly_field = true:suggestion
72 | # Expression-level preferences
73 | dotnet_style_object_initializer = true:suggestion
74 | dotnet_style_collection_initializer = true:suggestion
75 | dotnet_style_explicit_tuple_names = true:suggestion
76 | dotnet_style_null_propagation = true:suggestion
77 | dotnet_style_coalesce_expression = true:suggestion
78 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
79 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
80 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
81 | dotnet_style_prefer_auto_properties = true:silent
82 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
83 | dotnet_style_prefer_conditional_expression_over_return = true:silent
84 | ###############################
85 | # Naming Conventions #
86 | ###############################
87 | # Style Definitions
88 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
89 | # Use PascalCase for constant fields
90 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
91 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
92 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
93 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
94 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
95 | dotnet_naming_symbols.constant_fields.required_modifiers = const
96 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
97 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
98 | tab_width = 4
99 | end_of_line = crlf
100 | dotnet_style_prefer_compound_assignment = true:suggestion
101 | dotnet_style_prefer_simplified_interpolation = true:suggestion
102 | dotnet_style_namespace_match_folder = true:suggestion
103 | dotnet_style_prefer_collection_expression = true:suggestion
104 | ###############################
105 | # C# Coding Conventions #
106 | ###############################
107 | [*.cs]
108 | # var preferences
109 | csharp_style_var_for_built_in_types = true:silent
110 | csharp_style_var_when_type_is_apparent = true:silent
111 | csharp_style_var_elsewhere = true:silent
112 | # Expression-bodied members
113 | csharp_style_expression_bodied_methods = false:silent
114 | csharp_style_expression_bodied_constructors = false:silent
115 | csharp_style_expression_bodied_operators = false:silent
116 | csharp_style_expression_bodied_properties = true:silent
117 | csharp_style_expression_bodied_indexers = true:silent
118 | csharp_style_expression_bodied_accessors = true:silent
119 | # Pattern matching preferences
120 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
121 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
122 | # Null-checking preferences
123 | csharp_style_throw_expression = true:suggestion
124 | csharp_style_conditional_delegate_call = true:suggestion
125 | # Modifier preferences
126 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
127 | # Expression-level preferences
128 | csharp_prefer_braces = true:silent
129 | csharp_style_deconstructed_variable_declaration = true:suggestion
130 | csharp_prefer_simple_default_expression = true:suggestion
131 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
132 | csharp_style_inlined_variable_declaration = true:suggestion
133 | ###############################
134 | # C# Formatting Rules #
135 | ###############################
136 | # New line preferences
137 | csharp_new_line_before_open_brace = all
138 | csharp_new_line_before_else = true
139 | csharp_new_line_before_catch = true
140 | csharp_new_line_before_finally = true
141 | csharp_new_line_before_members_in_object_initializers = true
142 | csharp_new_line_before_members_in_anonymous_types = true
143 | csharp_new_line_between_query_expression_clauses = true
144 | # Indentation preferences
145 | csharp_indent_case_contents = true
146 | csharp_indent_switch_labels = true
147 | csharp_indent_labels = flush_left
148 | # Space preferences
149 | csharp_space_after_cast = false
150 | csharp_space_after_keywords_in_control_flow_statements = true
151 | csharp_space_between_method_call_parameter_list_parentheses = false
152 | csharp_space_between_method_declaration_parameter_list_parentheses = false
153 | csharp_space_between_parentheses = false
154 | csharp_space_before_colon_in_inheritance_clause = true
155 | csharp_space_after_colon_in_inheritance_clause = true
156 | csharp_space_around_binary_operators = before_and_after
157 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
158 | csharp_space_between_method_call_name_and_opening_parenthesis = false
159 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
160 | # Wrapping preferences
161 | csharp_preserve_single_line_statements = true
162 | csharp_preserve_single_line_blocks = true
163 | csharp_using_directive_placement = outside_namespace:suggestion
164 | csharp_prefer_simple_using_statement = true:suggestion
165 | csharp_style_namespace_declarations = file_scoped:suggestion
166 | csharp_style_expression_bodied_lambdas = true:silent
167 | csharp_style_expression_bodied_local_functions = true:silent
168 | csharp_style_prefer_null_check_over_type_check = true:suggestion
169 | csharp_style_prefer_method_group_conversion = true:silent
170 | csharp_style_prefer_top_level_statements = true:silent
171 | csharp_style_prefer_local_over_anonymous_function = true:suggestion
172 | csharp_style_prefer_index_operator = true:suggestion
173 | csharp_style_prefer_range_operator = true:suggestion
174 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
175 | csharp_style_prefer_primary_constructors = true:suggestion
176 | ###############################
177 | # VB Coding Conventions #
178 | ###############################
179 | [*.vb]
180 | # Modifier preferences
181 | 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
182 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 |
3 | * text=auto
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "weekly"
8 | - package-ecosystem: "nuget"
9 | directory: "/"
10 | schedule:
11 | interval: "daily"
12 | groups:
13 | coverlet:
14 | patterns:
15 | - "coverlet*"
16 | xunit:
17 | patterns:
18 | - "xunit*"
19 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | # .github/release.yml
2 |
3 | changelog:
4 | exclude:
5 | labels:
6 | - ignore-for-release
7 | authors:
8 | - octocat
9 | categories:
10 | - title: Breaking Changes 🛠
11 | labels:
12 | - Semver-Beta
13 | - title: Major Update 🛸
14 | labels:
15 | - Semver-Major
16 | - Feature
17 | - title: New Features 🎉
18 | labels:
19 | - Semver-Minor
20 | - enhancement
21 | - title: Dependencies 👒
22 | labels:
23 | - dependencies
24 | - title: Bugfixes 🐛
25 | labels:
26 | - Semver-Patch
27 | - bug
28 | - title: Other Changes
29 | labels:
30 | - "*"
31 |
--------------------------------------------------------------------------------
/.github/workflows/ci-release.yml:
--------------------------------------------------------------------------------
1 | # If this file is renamed, the incrementing run attempt number will be reset.
2 |
3 | name: Release
4 |
5 | on:
6 | push:
7 | tags:
8 | - '*'
9 |
10 | env:
11 | CI_BUILD_NUMBER: ${{ github.run_number }}
12 | CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
13 | CI_COMMIT_TAG: ${{ github.ref_name }}
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Setup
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: 9.0.x
26 | - name: Build and Publish
27 | env:
28 | DOTNET_CLI_TELEMETRY_OPTOUT: true
29 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
30 | shell: pwsh
31 | run: |
32 | ./Build.ps1
33 | - name: Create Release
34 | uses: ncipollo/release-action@v1
35 | with:
36 | artifacts: "artifacts/*.nupkg"
37 | generateReleaseNotes: true
38 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # If this file is renamed, the incrementing run attempt number will be reset.
2 |
3 | name: CI
4 |
5 | on:
6 | push:
7 | branches: [ "dev", "master" ]
8 | pull_request:
9 | branches: [ "dev", "master" ]
10 |
11 | env:
12 | CI_BUILD_NUMBER: ${{ github.run_number }}
13 | CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
14 | #CI_COMMIT_TAG: ""
15 |
16 | jobs:
17 | build:
18 |
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | - name: Setup
24 | uses: actions/setup-dotnet@v4
25 | with:
26 | dotnet-version: 9.0.x
27 | - name: Build and Publish
28 | env:
29 | DOTNET_CLI_TELEMETRY_OPTOUT: true
30 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
31 | shell: pwsh
32 | run: |
33 | ./Build.ps1
34 |
--------------------------------------------------------------------------------
/.github/workflows/code-coverage.yml:
--------------------------------------------------------------------------------
1 | name: Code Coverage Report
2 |
3 | on:
4 | push:
5 | branches:
6 | - '*'
7 | pull_request:
8 | branches:
9 | - '*'
10 |
11 | permissions:
12 | pull-requests: write
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 | name: CI Build
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 |
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: 9.0.x
26 |
27 | - name: Restore Dependencies
28 | run: dotnet restore serilog-sinks-splunk.sln
29 |
30 | - name: Build
31 | run: dotnet build serilog-sinks-splunk.sln --configuration Release --no-restore
32 |
33 | - name: Test
34 | run: dotnet test serilog-sinks-splunk.sln --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
35 |
36 | - name: Code Coverage Report
37 | uses: irongut/CodeCoverageSummary@v1.3.0
38 | with:
39 | filename: coverage/**/coverage.cobertura.xml
40 | badge: true
41 | fail_below_min: false
42 | format: markdown
43 | hide_branch_rate: false
44 | hide_complexity: true
45 | indicators: true
46 | output: both
47 | thresholds: '60 80'
48 |
49 | - name: Add Coverage PR Comment
50 | uses: marocchino/sticky-pull-request-comment@v2
51 | if: github.event_name == 'pull_request'
52 | with:
53 | recreate: true
54 | path: code-coverage-results.md
55 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [ "dev", "master", "main" ]
6 | pull_request:
7 | branches: [ "dev", "master", "main" ]
8 | schedule:
9 | - cron: '16 12 * * 6'
10 |
11 | jobs:
12 | analyze:
13 | name: Analyze (${{ matrix.language }})
14 | # Runner size impacts CodeQL analysis time. To learn more, please see:
15 | # - https://gh.io/recommended-hardware-resources-for-running-codeql
16 | # - https://gh.io/supported-runners-and-hardware-resources
17 | # - https://gh.io/using-larger-runners
18 | # Consider using larger runners for possible analysis time improvements.
19 | runs-on: 'ubuntu-latest'
20 | timeout-minutes: 360
21 | permissions:
22 | # required for all workflows
23 | security-events: write
24 | # only required for workflows in private repositories
25 | actions: read
26 | contents: read
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | include:
32 | - language: csharp
33 | # build-mode: autobuild
34 | build-mode: none # analyzes code only
35 | steps:
36 | - name: Checkout repository
37 | uses: actions/checkout@v4
38 |
39 | # Initializes the CodeQL tools for scanning.
40 | - name: Initialize CodeQL
41 | uses: github/codeql-action/init@v3
42 | with:
43 | languages: ${{ matrix.language }}
44 | build-mode: ${{ matrix.build-mode }}
45 |
46 | - name: Perform CodeQL Analysis
47 | uses: github/codeql-action/analyze@v3
48 | with:
49 | category: "/language:${{matrix.language}}"
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 |
24 | # Visual Studio 2015 cache/options directory
25 | .vs/
26 | # Uncomment if you have tasks that create the project's static files in wwwroot
27 | #wwwroot/
28 |
29 | # MSTest test Results
30 | [Tt]est[Rr]esult*/
31 | [Bb]uild[Ll]og.*
32 |
33 | # NUNIT
34 | *.VisualState.xml
35 | TestResult.xml
36 |
37 | # Build Results of an ATL Project
38 | [Dd]ebugPS/
39 | [Rr]eleasePS/
40 | dlldata.c
41 |
42 | # DNX
43 | project.lock.json
44 | artifacts/
45 |
46 | *_i.c
47 | *_p.c
48 | *_i.h
49 | *.ilk
50 | *.meta
51 | *.obj
52 | *.pch
53 | *.pdb
54 | *.pgc
55 | *.pgd
56 | *.rsp
57 | *.sbr
58 | *.tlb
59 | *.tli
60 | *.tlh
61 | *.tmp
62 | *.tmp_proj
63 | *.log
64 | *.vspscc
65 | *.vssscc
66 | .builds
67 | *.pidb
68 | *.svclog
69 | *.scc
70 |
71 | # Chutzpah Test files
72 | _Chutzpah*
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 | # NuGet v3's project.json files produces more ignoreable files
154 | *.nuget.props
155 | *.nuget.targets
156 |
157 | # Microsoft Azure Build Output
158 | csx/
159 | *.build.csdef
160 |
161 | # Microsoft Azure Emulator
162 | ecf/
163 | rcf/
164 |
165 | # Microsoft Azure ApplicationInsights config file
166 | ApplicationInsights.config
167 |
168 | # Windows Store app package directory
169 | AppPackages/
170 | BundleArtifacts/
171 |
172 | # Visual Studio cache files
173 | # files ending in .cache can be ignored
174 | *.[Cc]ache
175 | # but keep track of directories ending in .cache
176 | !*.[Cc]ache/
177 |
178 | # Others
179 | ClientBin/
180 | ~$*
181 | *~
182 | *.dbmdl
183 | *.dbproj.schemaview
184 | *.pfx
185 | *.publishsettings
186 | node_modules/
187 | orleans.codegen.cs
188 |
189 | # RIA/Silverlight projects
190 | Generated_Code/
191 |
192 | # Backup & report files from converting an old project file
193 | # to a newer Visual Studio version. Backup files are not needed,
194 | # because we have git ;-)
195 | _UpgradeReport_Files/
196 | Backup*/
197 | UpgradeLog*.XML
198 | UpgradeLog*.htm
199 |
200 | # SQL Server files
201 | *.mdf
202 | *.ldf
203 |
204 | # Business Intelligence projects
205 | *.rdl.data
206 | *.bim.layout
207 | *.bim_*.settings
208 |
209 | # Microsoft Fakes
210 | FakesAssemblies/
211 |
212 | # GhostDoc plugin setting file
213 | *.GhostDoc.xml
214 |
215 | # Node.js Tools for Visual Studio
216 | .ntvs_analysis.dat
217 |
218 | # Visual Studio 6 build log
219 | *.plg
220 |
221 | # Visual Studio 6 workspace options file
222 | *.opt
223 |
224 | # Visual Studio LightSwitch build output
225 | **/*.HTMLClient/GeneratedArtifacts
226 | **/*.DesktopClient/GeneratedArtifacts
227 | **/*.DesktopClient/ModelManifest.xml
228 | **/*.Server/GeneratedArtifacts
229 | **/*.Server/ModelManifest.xml
230 | _Pvt_Extensions
231 |
232 | # Paket dependency manager
233 | .paket/paket.exe
234 |
235 | # FAKE - F# Make
236 | .fake/
237 |
238 | .vscode/
239 |
240 | sample/Sample/out/
241 |
242 | .DS_Store
243 | .idea
244 |
--------------------------------------------------------------------------------
/Build.ps1:
--------------------------------------------------------------------------------
1 | Write-Output "build: Build started"
2 |
3 | Push-Location $PSScriptRoot
4 |
5 | Write-Output "build: Tool versions follow"
6 |
7 | dotnet --version
8 | dotnet --list-sdks
9 |
10 | if(Test-Path .\artifacts) {
11 | Write-Output "build: Cleaning ./artifacts"
12 | Remove-Item ./artifacts -Force -Recurse
13 | }
14 |
15 | & dotnet restore .\serilog-sinks-splunk.sln --no-cache
16 |
17 | $branch = $NULL -ne $env:CI_TARGET_BRANCH ? $env:CI_TARGET_BRANCH : (git symbolic-ref --short -q HEAD)
18 | $revision = $NULL -ne $env:CI_BUILD_NUMBER ? "{0:00000}" -f [Convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10) : "local"
19 |
20 | # add a suffix if this is not a tag build
21 | $suffix = $NULL -ne $env:CI_COMMIT_TAG ? "" : "$($branch.Substring(0, [Math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"
22 | $prefix = $env:CI_COMMIT_TAG
23 |
24 | Write-Output "build: Branch: $brach"
25 | Write-Output "build: Revision: $revision"
26 | Write-Output "build: VersionPrefix: $prefix"
27 | Write-Output "build: VersionSuffix: $suffix"
28 |
29 | foreach ($src in Get-ChildItem src/* -Directory) {
30 | Push-Location $src
31 |
32 | Write-Output "build: Packaging project in $src"
33 |
34 | if ($prefix) {
35 | # release build
36 | & dotnet pack -c Release -o ../../artifacts /p:VersionPrefix="$prefix"
37 | } elseif ($suffix) {
38 | # prerelease build
39 | & dotnet pack -c Release -o ../../artifacts /p:VersionSuffix="$suffix"
40 | } else {
41 | # local build
42 | & dotnet pack -c Release -o ../../artifacts
43 | }
44 | if($LASTEXITCODE -ne 0) { throw "Packaging failed" }
45 |
46 | Pop-Location
47 | }
48 |
49 | Write-Output "build: Checking complete solution builds"
50 | & dotnet build .\serilog-sinks-splunk.sln -c Release
51 | if($LASTEXITCODE -ne 0) { throw "Solution build failed" }
52 |
53 |
54 | foreach ($test in Get-ChildItem test/*.Tests) {
55 | Push-Location $test
56 |
57 | Write-Output "build: Testing project in $test"
58 |
59 | & dotnet test -c Release
60 | if($LASTEXITCODE -ne 0) { throw "Testing failed" }
61 |
62 | Pop-Location
63 | }
64 |
65 | Pop-Location
66 |
67 | if ($env:NUGET_API_KEY `
68 | -and (($env:GITHUB_REF_TYPE -eq "tag" -and $NULL -ne $prefix) `
69 | -or ($NULL -ne $suffix -and ($env:CI_TARGET_BRANCH -eq "dev" -or $env:CI_TARGET_BRANCH -eq "master")))) {
70 | # GitHub Actions will only supply this to branch builds and not PRs. We publish
71 | # builds from any branch this action targets (i.e. master and dev).
72 |
73 | Write-Output "build: Publishing NuGet packages"
74 |
75 | foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) {
76 | Write-Output "build: Publishing $nupkg"
77 | & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg" --no-symbols
78 | if($LASTEXITCODE -ne 0) { throw "Publishing failed" }
79 | }
80 | } else {
81 | if ($null -eq $env:NUGET_API_KEY) {
82 | Write-Output "build: Skipping Nuget publish, API key null"
83 | } else {
84 | Write-Output "build: Skipping Nuget publish reftype: $env:GITHUB_REF_TYPE, branch: $env:CI_TARGET_BRANCH"
85 | }
86 | }
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 3.6.0
2 | - [#138](https://github.com/serilog/serilog-sinks-splunk/pull/138)
3 |
4 | ## 3.5.0
5 | - [#134](https://github.com/serilog/serilog-sinks-splunk/pull/134)
6 |
7 | ## 3.4.0
8 | - [#126](https://github.com/serilog/serilog-sinks-splunk/pull/126)
9 | - [#122](https://github.com/serilog/serilog-sinks-splunk/pull/122)
10 | - [#121](https://github.com/serilog/serilog-sinks-splunk/pull/121)
11 |
12 | ## 3.3.0
13 | - Correct issues relating to #76 and signing.
14 | - Bump version to 3.3 for core Sink.
15 | - Bump version to 1.2 for UDP Sink.
16 | - Bump version to 1.2 for TCP Sink.
17 |
18 | ## 3.2.0
19 | - Remove TravisCI for Linux builds
20 | - Add AppVeyor for Linux Builds
21 | - [#76](https://github.com/serilog/serilog-sinks-splunk/issues/76)
22 |
23 | ## 3.1.0
24 | - [#105](https://github.com/serilog/serilog-sinks-splunk/pull/105)
25 |
26 | ## 3.0.0
27 | - [#76](https://github.com/serilog/serilog-sinks-splunk/issues/76) Add strong naming/signing to `Serilog.Sinks.Splunk`.
28 | - [#88](https://github.com/serilog/serilog-sinks-splunk/issues/88) Split Sinks into separate packages for maintainability.
29 | - *NOTE* Breaking changes. TCP & UDP Sinks moved to new packages
30 | - Serilog.Sinks.Splunk (3.0.x)
31 | - Serilog.Sinks.Splunk.TCP (1.0.x)
32 | - Serilog.Sinks.Splunk.UDP (1.0.x)
33 |
34 | ## 2.5.0
35 | - [#78](https://github.com/serilog/serilog-sinks-splunk/issues/78) Update `System.Net.Http` references to match other similar sinks.
36 | - [#79](https://github.com/serilog/serilog-sinks-splunk/issues/79) Addition of optional `LoggingLevelSwitch` param to EventCollector sink.
37 |
38 | ## 2.4.0
39 | - [#62](https://github.com/serilog/serilog-sinks-splunk/issues/62) Default fields added by Serilog to splunk
40 | - [#63](https://github.com/serilog/serilog-sinks-splunk/issues/63) Possible thread leak when ILogger instances are disposed
41 |
42 | ## 2.3.0
43 | - [#59](https://github.com/serilog/serilog-sinks-splunk/issues/59) Added ability to use custom fields with HEC. See http://dev.splunk.com/view/event-collector/SP-CAAAFB6.
44 |
45 | ## 2.2.1
46 | - [#47](https://github.com/serilog/serilog-sinks-splunk/issues/47) Tooling updates to VS2017
47 | - [#48](https://github.com/serilog/serilog-sinks-splunk/issues/48)
48 | - [#49](https://github.com/serilog/serilog-sinks-splunk/issues/49)
49 | - [#52](https://github.com/serilog/serilog-sinks-splunk/issues/52)
50 |
51 | ## 2.1.3
52 | - [#45](https://github.com/serilog/serilog-sinks-splunk/issues/45) - Deadlock fix on UI thread.
53 |
54 | ## 2.1.2
55 | - [#43](https://github.com/serilog/serilog-sinks-splunk/issues/43) - Extend sink & static configuration to allow for custom JSON formatter.
56 |
57 | ## 2.1.1
58 | - [#38](https://github.com/serilog/serilog-sinks-splunk/issues/38) - Fix for HttpEventlogCollector and sourceType
59 | - Clean up of sample app using examples of host, sourcetype, source override
60 |
61 | ## 2.1.0
62 |
63 | * Change to use a standalone formatter
64 | * Resolves - [#32](https://github.com/serilog/serilog-sinks-splunk/issues/32) & - [#26](https://github.com/serilog/serilog-sinks-splunk/issues/26) by exposing `HttpMessageHandler`
65 | * Resolves - [#30](https://github.com/serilog/serilog-sinks-splunk/issues/30) by ignoring OSX build and including tests in `build.sh` for TravisCI
66 |
67 | ## 2.0
68 | - Support for DotNet Core
69 | - Event Collector fluent interface changed to `.WriteTo.EventCollector`
70 | - Event Collector Sink targeting core
71 | - TCP/UDP Sinks targeting 4.5 *ONLY*
72 | - Updated Event Collector HTTP Client to add URI endpoint to host: "services/collector/event" if not included.
73 | - Event Collector changed to use epoch time [#15](https://github.com/serilog/serilog-sinks-splunk/pull/15)
74 |
75 | ## 1.8
76 | - Event Collector changed to use epoch time [#15](https://github.com/serilog/serilog-sinks-splunk/pull/15)
77 |
78 | ## 1.7
79 | - Better support for formatting including [#578](https://github.com/serilog/serilog/issues/578)
80 | - Cleanup on Event Collector
81 |
82 | ## 1.6.50
83 | - Streaming support for Event Collector
84 |
85 | ## 1.6.42
86 | - Added support for Splunk 6.3 Event Collector
87 | - Deprecated Splunk HTTP Sink using Management Port/API
88 |
89 | ## 1.5.30
90 | - Added switch for template rendering
91 |
92 | ## 1.5.0
93 | - Moved the sink from its [original location](https://github.com/serilog/serilog)
94 |
--------------------------------------------------------------------------------
/Create-DockerfileSolutionRestore.ps1:
--------------------------------------------------------------------------------
1 | param (
2 | [string]$solution = "serilog-sinks-splunk.sln"
3 | )
4 |
5 | $outfile = "DockerfileSolutionRestore.txt"
6 |
7 | # This script creates the $outfile file, with Dockerfile commands to restore all the packages for the solution,
8 | # so you can insert them (by hand) into Dockerfiles right before the "COPY . ." line
9 | # to increase build speed by optimizing the use of docker build images cache.
10 |
11 | # This script is only needed when adding or removing projects from the solution.
12 |
13 | Write-Output "" > $outfile
14 | Add-Content -Path $outfile "# Create this ""restore-solution"" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse"
15 | 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
16 | Add-Content -Path $outfile "COPY [""docker-compose.dcproj"", ""./""]"
17 | Add-Content -Path $outfile "COPY [""$solution"", ""./""]"
18 | Add-Content -Path $outfile "RUN dotnet restore ""$solution"""
19 | Add-Content -Path $outfile ""
20 |
21 |
22 | Add-Content -Path $outfile "# Docker Compose Paths"
23 |
24 | Get-ChildItem -Path "./" -Recurse -Filter "Dockerfile" |
25 | Resolve-Path -Relative |
26 | ForEach-Object { $_.Replace("\", "/") }
27 | Sort-Object |
28 | ForEach-Object {" ""$_"""} |
29 | Out-File -FilePath $outfile -Append
30 |
31 |
32 | Get-Content $outfile
33 |
--------------------------------------------------------------------------------
/DockerfileSolutionRestore.txt:
--------------------------------------------------------------------------------
1 |
2 | # Create this "restore-solution" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse
3 | COPY ["sample/Sample/Sample.csproj", "sample/Sample/"]
4 | COPY ["src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj", "src/Serilog.Sinks.Splunk/"]
5 | COPY ["src/Serilog.Sinks.TCP/Serilog.Sinks.Splunk.TCP.csproj", "src/Serilog.Sinks.TCP/"]
6 | COPY ["src/Serilog.Sinks.UDP/Serilog.Sinks.Splunk.UDP.csproj", "src/Serilog.Sinks.UDP/"]
7 | COPY ["test/Serilog.Sinks.Splunk.TCP.Tests/Serilog.Sinks.Splunk.TCP.Tests.csproj", "test/Serilog.Sinks.Splunk.TCP.Tests/"]
8 | COPY ["test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj", "test/Serilog.Sinks.Splunk.Tests/"]
9 | COPY ["docker-compose.dcproj", "./"]
10 | COPY ["serilog-sinks-splunk.sln", "./"]
11 | RUN dotnet restore "serilog-sinks-splunk.sln"
12 |
13 | # Docker Compose Paths
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | sh build.sh
3 |
4 | test: build
5 | echo "TODO"
6 |
7 | run-samples:
8 | docker-compose up --build
9 |
10 | kill-samples:
11 | docker-compose down
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Serilog.Sinks.Splunk
2 |
3 | 
4 | [](https://www.nuget.org/packages/Serilog.Sinks.Splunk)
5 | [](https://gitter.im/serilog/serilog)
6 |
7 | A Serilog sink that writes events to the [Splunk](https://splunk.com). Supports .NET 4.5+, .NET Core, and platforms compatible with the [.NET Platform Standard](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) `net45`, `netstandard1.1`, `netstandard2.0`.
8 |
9 | [](https://nuget.org/packages/serilog.sinks.splunk)
10 |
11 | **Package** - [Serilog.Sinks.Splunk](https://nuget.org/packages/serilog.sinks.splunk)
12 |
13 | ## Getting started
14 |
15 | To get started install the *Serilog.Sinks.Splunk* package:
16 |
17 | ```powershell
18 | PM> Install-Package Serilog.Sinks.Splunk
19 | ```
20 |
21 | OR
22 |
23 | ```bash
24 | $ dotnet add package Serilog.Sinks.Splunk
25 | ```
26 |
27 | If using the `TCP` or `UDP` sinks install the following packages
28 |
29 | * TCP: `Serilog.Sinks.Splunk.TCP`
30 | * UDP: `Serilog.Sinks.Splunk.UDP`
31 |
32 | To start using the Splunk Event Collector (Splunk 6.3 and above), logging can be setup as follows.
33 |
34 | ```csharp
35 | var log = new LoggerConfiguration()
36 | .WriteTo.EventCollector("https://mysplunk:8088/services/collector/event", "myeventcollectortoken")
37 | .CreateLogger();
38 | ```
39 |
40 | If using `appsettings.json` for configuration the following example illustrates using the Event Collector and Console sinks.
41 |
42 | ```javascript
43 | {
44 | "Serilog": {
45 | "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.Splunk"],
46 | "MinimumLevel": "Information",
47 | "WriteTo": [{
48 | "Name": "Console"
49 | },
50 | {
51 | "Name": "EventCollector",
52 | "Args": {
53 | "splunkHost": "http://splunk:8088",
54 | "uriPath": "services/collector/event",
55 | "eventCollectorToken": "00112233-4455-6677-8899-AABBCCDDEEFF"
56 | }
57 | }
58 | ],
59 | "Properties": {
60 | "Application": "Serilog Splunk Console Sample"
61 | }
62 | }
63 | }
64 | ```
65 |
66 | More information about Serilog is available on the [wiki](https://github.com/serilog/serilog-sinks-splunk/wiki).
67 |
68 | _Serilog is copyright © 2013-2024 Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html). Needle and thread logo a derivative of work by [Kenneth Appiah](http://www.kensets.com/)._
69 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you discover a security vulnerability in this project, please follow these steps to report it:
6 |
7 | 1. **Do not** create a public GitHub issue for the vulnerability.
8 | 2. Send an email to [security@eeparker.dev](mailto:security@eeparker.dev) with a detailed description of the vulnerability.
9 | 3. Include any relevant information, such as steps to reproduce the vulnerability or proof-of-concept code.
10 | 4. Provide your contact information so that we can follow up with you.
11 |
12 | ## Response Timeline
13 |
14 | We will do our best to respond to your report in a timely manner. Here is an outline of our response process:
15 |
16 | - We will acknowledge your report within 7 days.
17 | - Our team will investigate the reported vulnerability and determine its impact.
18 | - We will work on developing a fix for the vulnerability.
19 | - Once a fix is ready, we will release a security update.
20 | - We will publicly acknowledge your contribution if you choose to be credited.
21 |
22 | ## Supported Versions
23 |
24 | This project is actively maintained and security updates will be provided for the following versions:
25 |
26 | - Version 4.x.x (latest stable release)
27 |
28 | If you are using an older version, we recommend upgrading to the latest stable release to benefit from the latest security fixes.
29 |
30 | ## Security Measures
31 |
32 | We take security seriously and have implemented the following measures to protect our users:
33 |
34 | - Regular code reviews and security audits.
35 | - Secure coding practices and adherence to industry best practices.
36 | - Continuous monitoring and vulnerability scanning of our systems.
37 |
38 | ## Contact
39 |
40 | If you have any questions or concerns regarding the security of this project, please contact us at [security@eeparker.dev](mailto:security@eeparker.dev).
41 |
--------------------------------------------------------------------------------
/Setup.ps1:
--------------------------------------------------------------------------------
1 | $ErrorActionPreference = "Stop"
2 |
3 | $RequiredDotnetVersion = $(cat ./global.json | convertfrom-json).sdk.version
4 |
5 | New-Item -ItemType Directory -Force "./build/" | Out-Null
6 |
7 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./build/installcli.ps1"
8 | & ./build/installcli.ps1 -InstallDir "$pwd/.dotnetcli" -NoPath -Version $RequiredDotnetVersion
9 | if ($LASTEXITCODE) { throw ".NET install failed" }
10 |
--------------------------------------------------------------------------------
/assets/Serilog.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-splunk/7b5793d2d1fd1dc7888de7609443e23e38eae46c/assets/Serilog.snk
--------------------------------------------------------------------------------
/docker-compose.dcproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2.1
5 | Linux
6 | 1b9defa3-d600-45fa-93a5-79006076fb5c
7 | serilogsinkssplunk
8 | false
9 |
10 |
11 |
12 |
13 | docker-compose.yml
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docker-compose.override.yml:
--------------------------------------------------------------------------------
1 | version: '3.4'
2 | #https://learn.microsoft.com/en-us/visualstudio/containers/docker-compose-properties
3 |
4 | services:
5 | splunk:
6 | ports:
7 | - 8000:8000
8 | - 8088:8088
9 | - 8089:8089
10 |
11 | sampleconsoleapp:
12 | ports:
13 | - 8080:8080
14 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | splunk:
4 | container_name: splunk
5 | build:
6 | context: .
7 | dockerfile: sample/splunk/Dockerfile
8 | volumes:
9 | - "./sample/splunk/default.yml:/tmp/defaults/default.yml"
10 | environment:
11 | SPLUNK_START_ARGS: --accept-license --answer-yes --seed-passwd changeme
12 | SPLUNK_ENABLE_LISTEN: 9997
13 | SPLUNK_PASSWORD: changemeplease!
14 | SPLUNK_HEC_TOKEN: 00112233-4455-6677-8899-AABBCCDDEEFF
15 |
16 | sampleconsoleapp:
17 | container_name: sample
18 | depends_on:
19 | - "splunk"
20 | build:
21 | context: .
22 | dockerfile: sample/Sample/Dockerfile
23 |
--------------------------------------------------------------------------------
/sample/Sample/Dockerfile:
--------------------------------------------------------------------------------
1 | #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.
2 | FROM mcr.microsoft.com/dotnet/aspnet:8.0.4-alpine3.19 AS base
3 | WORKDIR /app
4 | EXPOSE 8080
5 |
6 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
7 | ARG BUILD_CONFIGURATION=Release
8 | WORKDIR /src
9 |
10 | # Create this "restore-solution" section by running ./Create-DockerfileSolutionRestore.ps1, to optimize build cache reuse
11 | COPY ["sample/Sample/Sample.csproj", "sample/Sample/"]
12 | COPY ["src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj", "src/Serilog.Sinks.Splunk/"]
13 | COPY ["src/Serilog.Sinks.TCP/Serilog.Sinks.Splunk.TCP.csproj", "src/Serilog.Sinks.TCP/"]
14 | COPY ["src/Serilog.Sinks.UDP/Serilog.Sinks.Splunk.UDP.csproj", "src/Serilog.Sinks.UDP/"]
15 | COPY ["test/Serilog.Sinks.Splunk.TCP.Tests/Serilog.Sinks.Splunk.TCP.Tests.csproj", "test/Serilog.Sinks.Splunk.TCP.Tests/"]
16 | COPY ["test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj", "test/Serilog.Sinks.Splunk.Tests/"]
17 | COPY ["docker-compose.dcproj", "./"]
18 | COPY ["serilog-sinks-splunk.sln", "./"]
19 | RUN dotnet restore "serilog-sinks-splunk.sln"
20 |
21 | COPY . .
22 | WORKDIR "/src/sample/Sample/"
23 | RUN dotnet build "./Sample.csproj" -c $BUILD_CONFIGURATION -o /app/build
24 |
25 | FROM build AS publish
26 | ARG BUILD_CONFIGURATION=Release
27 | RUN dotnet publish "./Sample.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
28 |
29 | FROM base AS final
30 | WORKDIR /app
31 | COPY --from=publish /app/publish .
32 | ENTRYPOINT ["dotnet", "Sample.dll"]
--------------------------------------------------------------------------------
/sample/Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Serilog;
5 | using Serilog.Events;
6 | using Serilog.Exceptions;
7 | using Serilog.Extensions;
8 | using Serilog.Sinks.Splunk;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Diagnostics;
12 | using System.IO;
13 | using System.Linq;
14 | using System.Threading.Tasks;
15 |
16 | namespace Sample
17 | {
18 | public class Program
19 | {
20 | const string SPLUNK_FULL_ENDPOINT = "http://splunk:8088/services/collector/event"; // Full splunk url
21 | const string SPLUNK_ENDPOINT = "http://splunk:8088"; // Your splunk url
22 | const string SPLUNK_HEC_TOKEN = "00112233-4455-6677-8899-AABBCCDDEEFF"; // Your HEC token. See http://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector
23 | public static string EventCollectorToken = SPLUNK_HEC_TOKEN;
24 |
25 | public static async Task Main(string[] args)
26 | {
27 | // Bootstrap a simple logger.
28 | var logger = new LoggerConfiguration()
29 | .WriteTo.Console()
30 | .CreateLogger();
31 |
32 | try
33 | {
34 | logger.Information("Sample app starting up...");
35 | logger.Information("Startup arguments \"{Arguments}\".", args);
36 |
37 | var eventsToCreate = 10;
38 | var runSSL = false;
39 | var secToWait = 30;
40 |
41 | if (args.Length > 0)
42 | eventsToCreate = int.Parse(args[0]);
43 |
44 | if (args.Length == 2)
45 | runSSL = bool.Parse(args[1]);
46 |
47 | if (args.Length == 3)
48 | secToWait = int.Parse(args[2]);
49 |
50 | Serilog.Debugging.SelfLog.Enable(msg =>
51 | {
52 | Console.WriteLine(msg);
53 | throw new Exception("Failed to write to Serilog.", new Exception(msg));
54 | });
55 |
56 |
57 | while (secToWait-- > 0)
58 | {
59 | logger.Information("Waiting {secToWait} seconds...", secToWait);
60 | await Task.Delay(1000);
61 | }
62 |
63 |
64 | logger.Information("Creating logger {MethodName}.", nameof(ReproduceGitHubIssue195));
65 | ReproduceGitHubIssue195();
66 |
67 | logger.Information("Creating logger {MethodName}.", nameof(ReproduceGitHubIssue183));
68 | ReproduceGitHubIssue183();
69 |
70 | logger.Information("Creating logger {MethodName}.", nameof(OverridingSubsecondPrecisionMicroseconds));
71 | OverridingSubsecondPrecisionMicroseconds(eventsToCreate);
72 |
73 | logger.Information("Creating logger {MethodName}.", nameof(OverridingSubsecondPrecisionNanoseconds));
74 | OverridingSubsecondPrecisionNanoseconds(eventsToCreate);
75 |
76 | logger.Information("Creating logger {MethodName}.", nameof(UsingAppSettingsJson));
77 | UsingAppSettingsJson(eventsToCreate);
78 |
79 | logger.Information("Creating logger {MethodName}.", nameof(UsingHostOnly));
80 | UsingHostOnly(eventsToCreate);
81 |
82 | logger.Information("Creating logger {MethodName}.", nameof(UsingFullUri));
83 | UsingFullUri(eventsToCreate);
84 |
85 | logger.Information("Creating logger {MethodName}.", nameof(OverridingSource));
86 | OverridingSource(eventsToCreate);
87 |
88 | logger.Information("Creating logger {MethodName}.", nameof(OverridingSourceType));
89 | OverridingSourceType(eventsToCreate);
90 |
91 | logger.Information("Creating logger {MethodName}.", nameof(OverridingHost));
92 | OverridingHost(eventsToCreate);
93 |
94 | logger.Information("Creating logger {MethodName}.", nameof(WithNoTemplate));
95 | WithNoTemplate(eventsToCreate);
96 |
97 | logger.Information("Creating logger {MethodName}.", nameof(WithCompactSplunkFormatter));
98 | WithCompactSplunkFormatter(eventsToCreate);
99 |
100 | if (runSSL)
101 | {
102 | logger.Information("Creating logger {MethodName}.", nameof(UsingSSL));
103 | UsingSSL(eventsToCreate);
104 | }
105 |
106 | logger.Information("Creating logger {MethodName}.", nameof(AddCustomFields));
107 | AddCustomFields(eventsToCreate);
108 | }
109 | finally
110 | {
111 | logger.Information("Done...");
112 | Log.CloseAndFlush();
113 | }
114 | }
115 |
116 | public static void UsingAppSettingsJson(int eventsToCreate)
117 | {
118 | var configuration = new ConfigurationBuilder()
119 | .SetBasePath(Directory.GetCurrentDirectory())
120 | .AddJsonFile("appsettings.json")
121 | .Build();
122 |
123 | Log.Logger = new LoggerConfiguration()
124 | .ReadFrom.Configuration(configuration)
125 | .CreateLogger();
126 |
127 | foreach (var i in Enumerable.Range(0, eventsToCreate))
128 | {
129 | Log.Information("Running via appsettings.json {Counter}", i);
130 | }
131 |
132 | Log.CloseAndFlush();
133 | }
134 |
135 | private static void WithCompactSplunkFormatter(int eventsToCreate)
136 | {
137 | // Vanilla Test with full uri specified
138 | Log.Logger = new LoggerConfiguration()
139 | .MinimumLevel.Debug()
140 | .WriteTo.Console()
141 | .WriteTo.EventCollector(
142 | SPLUNK_FULL_ENDPOINT,
143 | Program.EventCollectorToken, new CompactSplunkJsonFormatter())
144 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
145 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType",
146 | "Vanilla with CompactSplunkJsonFormatter specified")
147 | .CreateLogger();
148 |
149 |
150 | foreach (var i in Enumerable.Range(0, eventsToCreate))
151 | {
152 | Log.Information("{Counter}{Message}", i, " Running vanilla loop with CompactSplunkJsonFormatter");
153 | }
154 |
155 | Log.CloseAndFlush();
156 | }
157 |
158 | public static void OverridingSubsecondPrecisionMicroseconds(int eventsToCreate)
159 | {
160 | // Override Source
161 | Log.Logger = new LoggerConfiguration()
162 | .MinimumLevel.Debug()
163 | .WriteTo.Console()
164 | .WriteTo.EventCollector(
165 | SPLUNK_ENDPOINT,
166 | Program.EventCollectorToken,
167 | subSecondPrecision: SubSecondPrecision.Microseconds)
168 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
169 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Override")
170 | .CreateLogger();
171 |
172 | foreach (var i in Enumerable.Range(0, eventsToCreate))
173 | {
174 | Log.Information("Running source override loop {Counter} subseconds: {subsecondPrecision}", i, SubSecondPrecision.Microseconds.ToString());
175 | }
176 |
177 | Log.CloseAndFlush();
178 | }
179 |
180 | public static void OverridingSubsecondPrecisionNanoseconds(int eventsToCreate)
181 | {
182 | // Override Source
183 | Log.Logger = new LoggerConfiguration()
184 | .MinimumLevel.Debug()
185 | .WriteTo.Console()
186 | .WriteTo.EventCollector(
187 | SPLUNK_ENDPOINT,
188 | Program.EventCollectorToken,
189 | subSecondPrecision: SubSecondPrecision.Nanoseconds)
190 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
191 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Override")
192 | .CreateLogger();
193 |
194 | foreach (var i in Enumerable.Range(0, eventsToCreate))
195 | {
196 | Log.Information("Running source override loop {Counter} subseconds: {subsecondPrecision}", i, SubSecondPrecision.Nanoseconds.ToString());
197 | }
198 |
199 | Log.CloseAndFlush();
200 | }
201 |
202 | public static void OverridingSource(int eventsToCreate)
203 | {
204 | // Override Source
205 | Log.Logger = new LoggerConfiguration()
206 | .MinimumLevel.Debug()
207 | .WriteTo.Console()
208 | .WriteTo.EventCollector(
209 | SPLUNK_ENDPOINT,
210 | Program.EventCollectorToken,
211 | source: "Serilog.Sinks.Splunk.Sample.TestSource")
212 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
213 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Override")
214 | .CreateLogger();
215 |
216 | foreach (var i in Enumerable.Range(0, eventsToCreate))
217 | {
218 | Log.Information("Running source override loop {Counter}", i);
219 | }
220 |
221 | Log.CloseAndFlush();
222 | }
223 |
224 | public static void OverridingSourceType(int eventsToCreate)
225 | {
226 | // Override Source
227 | Log.Logger = new LoggerConfiguration()
228 | .MinimumLevel.Debug()
229 | .WriteTo.Console()
230 | .WriteTo.EventCollector(
231 | SPLUNK_ENDPOINT,
232 | Program.EventCollectorToken,
233 | sourceType: "_json")
234 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
235 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Source Type Override")
236 | .CreateLogger();
237 |
238 | foreach (var i in Enumerable.Range(0, eventsToCreate))
239 | {
240 | Log.Information("Running source type override loop {Counter}", i);
241 | }
242 |
243 | Log.CloseAndFlush();
244 | }
245 |
246 | public static void OverridingHost(int eventsToCreate)
247 | {
248 | // Override Host
249 | Log.Logger = new LoggerConfiguration()
250 | .MinimumLevel.Debug()
251 | .WriteTo.Console()
252 | .WriteTo.EventCollector(
253 | SPLUNK_ENDPOINT,
254 | Program.EventCollectorToken,
255 | host: "myamazingmachine")
256 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
257 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Host Override")
258 | .CreateLogger();
259 |
260 | foreach (var i in Enumerable.Range(0, eventsToCreate))
261 | {
262 | Log.Information("Running host override loop {Counter}", i);
263 | }
264 |
265 | Log.CloseAndFlush();
266 | }
267 |
268 | public static void UsingFullUri(int eventsToCreate)
269 | {
270 | // Vanilla Test with full uri specified
271 | Log.Logger = new LoggerConfiguration()
272 | .MinimumLevel.Debug()
273 | .WriteTo.Console()
274 | .WriteTo.EventCollector(
275 | SPLUNK_FULL_ENDPOINT,
276 | Program.EventCollectorToken)
277 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
278 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Vanilla with full uri specified")
279 | .CreateLogger();
280 |
281 |
282 | foreach (var i in Enumerable.Range(0, eventsToCreate))
283 | {
284 | Log.Information("Running vanilla loop with full uri {Counter}", i);
285 | }
286 |
287 | Log.CloseAndFlush();
288 | }
289 |
290 | public static void UsingHostOnly(int eventsToCreate)
291 | {
292 | // Vanilla Tests just host
293 | Log.Logger = new LoggerConfiguration()
294 | .MinimumLevel.Debug()
295 | .WriteTo.Console()
296 | .WriteTo.EventCollector(
297 | SPLUNK_ENDPOINT,
298 | Program.EventCollectorToken)
299 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
300 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "Vanilla No services/collector/event in uri")
301 | .CreateLogger();
302 |
303 | foreach (var i in Enumerable.Range(0, eventsToCreate))
304 | {
305 | Log.Information("Running vanilla without host name and port only {Counter}", i);
306 | }
307 |
308 | Log.CloseAndFlush();
309 | }
310 |
311 | public static void WithNoTemplate(int eventsToCreate)
312 | {
313 | // No Template
314 | Log.Logger = new LoggerConfiguration()
315 | .MinimumLevel.Debug()
316 | .WriteTo.Console()
317 | .WriteTo.EventCollector(
318 | SPLUNK_ENDPOINT,
319 | Program.EventCollectorToken,
320 | renderTemplate: false)
321 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
322 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "No Templates")
323 | .CreateLogger();
324 |
325 | foreach (var i in Enumerable.Range(0, eventsToCreate))
326 | {
327 | Log.Information("Running no template loop {Counter}", i);
328 | }
329 |
330 | Log.CloseAndFlush();
331 | }
332 |
333 | public static void UsingSSL(int eventsToCreate)
334 | {
335 | // SSL
336 | Log.Logger = new LoggerConfiguration()
337 | .MinimumLevel.Debug()
338 | .WriteTo.Console()
339 | .WriteTo.EventCollector(
340 | SPLUNK_ENDPOINT,
341 | Program.EventCollectorToken)
342 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
343 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "HTTPS")
344 | .CreateLogger();
345 |
346 | foreach (var i in Enumerable.Range(0, eventsToCreate))
347 | {
348 | Log.Information("HTTPS {Counter}", i);
349 | }
350 | Log.CloseAndFlush();
351 | }
352 |
353 | public static void AddCustomFields(int eventsToCreate)
354 | {
355 | var metaData = new CustomFields(new List
356 | {
357 | new CustomField("relChan", "Test"),
358 | new CustomField("version", "17.8.9.beta"),
359 | new CustomField("rel", "REL1706"),
360 | new CustomField("role", new List() { "service", "rest", "ESB" })
361 | });
362 | // Override Source
363 | Log.Logger = new LoggerConfiguration()
364 | .MinimumLevel.Debug()
365 | .WriteTo.Console()
366 | .WriteTo.EventCollector(
367 | splunkHost: SPLUNK_ENDPOINT
368 | , eventCollectorToken: SPLUNK_HEC_TOKEN
369 | , host: System.Environment.MachineName
370 | , source: "BackPackTestServerChannel"
371 | , sourceType: "_json"
372 | , fields: metaData)
373 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample", "ViaEventCollector")
374 | .Enrich.WithProperty("Serilog.Sinks.Splunk.Sample.TestType", "AddCustomFields")
375 | .CreateLogger();
376 |
377 | foreach (var i in Enumerable.Range(0, eventsToCreate))
378 | {
379 | Log.Information("AddCustomFields {Counter}", i);
380 | }
381 |
382 | Log.CloseAndFlush();
383 | }
384 |
385 | public static void ReproduceGitHubIssue183()
386 | {
387 | // Override Source
388 | var loggerConfig = new LoggerConfiguration()
389 | .MinimumLevel.Verbose()
390 | .WriteTo.EventCollector(
391 | SPLUNK_ENDPOINT,
392 | Program.EventCollectorToken,
393 | restrictedToMinimumLevel: LogEventLevel.Debug);
394 |
395 | using (var logger = loggerConfig.CreateLogger())
396 | {
397 | Log.Logger = logger;
398 |
399 | logger.Information("Information message {@param}", new { Property1 = 1, Property2 = 2 });
400 | logger.Warning("Warning message {@param}", "Hello this is a string");
401 | logger.Error(new Exception("Bang"), "Error message");
402 | }
403 |
404 | Log.CloseAndFlush();
405 | }
406 |
407 | public static void ReproduceGitHubIssue195()
408 | {
409 | var serviceCollection = new ServiceCollection()
410 | .AddLogging(builder => builder.AddSerilog(dispose: true));
411 |
412 | serviceCollection.AddSingleton();
413 |
414 | var services = serviceCollection.BuildServiceProvider();
415 |
416 | // Override Source
417 | var loggerConfig = new LoggerConfiguration()
418 | .Enrich.FromLogContext()
419 | .Enrich.WithExceptionDetails()
420 | .Enrich.WithCorrelationId(addValueIfHeaderAbsence: true)
421 | .MinimumLevel.Verbose()
422 | .WriteTo.EventCollector(
423 | SPLUNK_ENDPOINT,
424 | Program.EventCollectorToken,
425 | restrictedToMinimumLevel: LogEventLevel.Debug);
426 |
427 | using (var logger = loggerConfig.CreateLogger())
428 | {
429 | var http = services.GetRequiredService();
430 |
431 | http.HttpContext = new DefaultHttpContext();
432 |
433 |
434 | using var activity = new Activity("TraceIDTest");
435 | activity.Start();
436 | Activity.Current = activity;
437 | http.HttpContext.TraceIdentifier = activity.Id;
438 |
439 | Log.Logger = logger;
440 |
441 | logger.Information("TraceID Information message {@param}", new { Property1 = 1, Property2 = 2 });
442 | logger.Warning("TraceID Warning message {@param}", "Hello this is a string");
443 | logger.Error(new Exception("Bang"), "TraceID Error message");
444 | activity.Stop();
445 | }
446 |
447 | Log.CloseAndFlush();
448 | }
449 | }
450 |
451 |
452 | }
453 |
--------------------------------------------------------------------------------
/sample/Sample/Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/sample/Sample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Serilog": {
3 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Splunk" ],
4 | "MinimumLevel": "Information",
5 | "WriteTo": [
6 | {
7 | "Name": "Console"
8 | },
9 | {
10 | "Name": "EventCollector",
11 | "Args": {
12 | "splunkHost": "http://splunk:8088/",
13 | "uriPath": "services/collector/event",
14 | "eventCollectorToken": "00112233-4455-6677-8899-AABBCCDDEEFF"
15 | }
16 | }
17 | ],
18 | "Properties": {
19 | "Application": "Serilog Splunk Console Sample",
20 | "Serilog.Sinks.Splunk.Sample": "ViaEventCollector",
21 | "Serilog.Sinks.Splunk.Sample.TestType": "AppSettings.json"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/sample/splunk/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM splunk/splunk:latest
2 | ADD sample/splunk/etc ${SPLUNK_HOME}/etc
--------------------------------------------------------------------------------
/sample/splunk/default.yml:
--------------------------------------------------------------------------------
1 | splunk:
2 | hec:
3 | enable: True
4 | ssl: false
5 | port: 8088
6 | # hec.token is used only for ingestion (receiving Splunk events)
7 | token: 00112233-4455-6677-8899-AABBCCDDEEFF
8 |
--------------------------------------------------------------------------------
/sample/splunk/etc/apps/splunk_httpinput/local/inputs.conf:
--------------------------------------------------------------------------------
1 | [http]
2 | disabled = 0
3 | enableSSL = 0
4 |
5 | [http://splunk-sample]
6 | token = 00112233-4455-6677-8899-AABBCCDDEEFF
--------------------------------------------------------------------------------
/sample/splunk/etc/system/local/inputs.conf:
--------------------------------------------------------------------------------
1 | [http]
2 | disabled = 0
3 | useDeploymentServer = 0
--------------------------------------------------------------------------------
/sample/splunk/etc/system/local/props.conf:
--------------------------------------------------------------------------------
1 | TIME_PREFIX = \"time\"\:\s*\"
2 | TIME_FORMAT = %Y-%m-%d %H:%M:%S.%9N
3 | ADD_EXTRA_TIME_FIELDS = all
4 | MAX_TIMESTAMP_LOOKAHEAD = 30
--------------------------------------------------------------------------------
/sample/splunk/etc/system/local/server.conf:
--------------------------------------------------------------------------------
1 | [general]
2 | allowRemoteLogin = always
3 |
4 | [diskUsage]
5 | minFreeSpace = 0
--------------------------------------------------------------------------------
/sample/splunk/etc/system/local/web.conf:
--------------------------------------------------------------------------------
1 | [settings]
2 | enable_insecure_login = True
--------------------------------------------------------------------------------
/serilog-sink-nuget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-splunk/7b5793d2d1fd1dc7888de7609443e23e38eae46c/serilog-sink-nuget.png
--------------------------------------------------------------------------------
/serilog-sinks-splunk.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.9.34714.143
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{B9B13339-749C-4098-8845-780ED4FA488A}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | Build.ps1 = Build.ps1
12 | CHANGES.md = CHANGES.md
13 | src\common.props = src\common.props
14 | global.json = global.json
15 | README.md = README.md
16 | assets\Serilog.snk = assets\Serilog.snk
17 | Setup.ps1 = Setup.ps1
18 | EndProjectSection
19 | EndProject
20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{1C75E4A9-4CB1-497C-AD17-B438882051A1}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B9451AD8-09B9-4C09-A152-FBAE24806614}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk", "src\Serilog.Sinks.Splunk\Serilog.Sinks.Splunk.csproj", "{32CF915C-3ECD-496C-8FDB-1214581274A6}"
25 | EndProject
26 | 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}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "sample\Sample\Sample.csproj", "{4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk.TCP", "src\Serilog.Sinks.TCP\Serilog.Sinks.Splunk.TCP.csproj", "{F74FCFD0-536B-4311-AA66-0BD16112D895}"
31 | EndProject
32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Splunk.UDP", "src\Serilog.Sinks.UDP\Serilog.Sinks.Splunk.UDP.csproj", "{FE1504A6-5444-4B87-819C-E6F477662B7F}"
33 | EndProject
34 | Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1B9DEFA3-D600-45FA-93A5-79006076FB5C}"
35 | EndProject
36 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "splunk", "splunk", "{21EEF50A-C0FC-4406-97A1-8F5F499AE2FC}"
37 | ProjectSection(SolutionItems) = preProject
38 | sample\splunk\Dockerfile = sample\splunk\Dockerfile
39 | EndProjectSection
40 | EndProject
41 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A9F9CB1C-35C7-4878-97B7-673F5D4BC64A}"
42 | ProjectSection(SolutionItems) = preProject
43 | .github\workflows\ci.yml = .github\workflows\ci.yml
44 | EndProjectSection
45 | EndProject
46 | Global
47 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
48 | Debug|Any CPU = Debug|Any CPU
49 | Release|Any CPU = Release|Any CPU
50 | EndGlobalSection
51 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
52 | {32CF915C-3ECD-496C-8FDB-1214581274A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {32CF915C-3ECD-496C-8FDB-1214581274A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {32CF915C-3ECD-496C-8FDB-1214581274A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {32CF915C-3ECD-496C-8FDB-1214581274A6}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {F74FCFD0-536B-4311-AA66-0BD16112D895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {F74FCFD0-536B-4311-AA66-0BD16112D895}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {F74FCFD0-536B-4311-AA66-0BD16112D895}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {F74FCFD0-536B-4311-AA66-0BD16112D895}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {FE1504A6-5444-4B87-819C-E6F477662B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {FE1504A6-5444-4B87-819C-E6F477662B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {FE1504A6-5444-4B87-819C-E6F477662B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {FE1504A6-5444-4B87-819C-E6F477662B7F}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73 | {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
74 | {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {1B9DEFA3-D600-45FA-93A5-79006076FB5C}.Release|Any CPU.Build.0 = Release|Any CPU
76 | EndGlobalSection
77 | GlobalSection(SolutionProperties) = preSolution
78 | HideSolutionNode = FALSE
79 | EndGlobalSection
80 | GlobalSection(NestedProjects) = preSolution
81 | {32CF915C-3ECD-496C-8FDB-1214581274A6} = {7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}
82 | {3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {B9451AD8-09B9-4C09-A152-FBAE24806614}
83 | {4A4E361D-8BBE-4DDD-9E6C-53960C2B5F5B} = {1C75E4A9-4CB1-497C-AD17-B438882051A1}
84 | {F74FCFD0-536B-4311-AA66-0BD16112D895} = {7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}
85 | {FE1504A6-5444-4B87-819C-E6F477662B7F} = {7A774CBB-A6E9-4854-B4DB-4CF860B0C1C5}
86 | {21EEF50A-C0FC-4406-97A1-8F5F499AE2FC} = {1C75E4A9-4CB1-497C-AD17-B438882051A1}
87 | EndGlobalSection
88 | GlobalSection(ExtensibilityGlobals) = postSolution
89 | SolutionGuid = {D7BFF439-D18D-4124-A36F-15CFB8E84BCC}
90 | EndGlobalSection
91 | EndGlobal
92 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/ConfigurationDefaults.cs:
--------------------------------------------------------------------------------
1 | namespace Serilog
2 | {
3 | internal static class ConfigurationDefaults
4 | {
5 | internal const string DefaultSource = "";
6 | internal const string DefaultSourceType = "";
7 | internal const string DefaultHost = "";
8 | internal const string DefaultIndex = "";
9 |
10 | ///
11 | /// The default HTTP Event Collector path when not set via configuration.
12 | ///
13 | ///
14 | /// https://docs.splunk.com/Documentation/Splunk/9.1.0/Data/UsetheHTTPEventCollector#Send_data_to_HTTP_Event_Collector_on_Splunk_Enterprise
15 | ///
16 | internal const string DefaultEventCollectorPath = "services/collector/event";
17 | internal const string DefaultCollectorPath = "services/collector";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("Serilog.Sinks.Splunk.Tests, PublicKey=" +
5 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" +
6 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" +
7 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" +
8 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" +
9 | "b19485ec")]
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Serilog.Sinks.Splunk.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | The Splunk Sink for Serilog
7 | netstandard2.1;netstandard2.0;net8.0;net9.0
8 | true
9 | Serilog.Sinks.Splunk
10 | Serilog.Sinks.Splunk
11 | serilog;splunk;logging;event;collector;hec
12 | ../../assets/Serilog.snk
13 | true
14 | true
15 | Serilog
16 | true
17 | true
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs:
--------------------------------------------------------------------------------
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 Serilog.Events;
16 | using Serilog.Formatting;
17 | using Serilog.Formatting.Json;
18 | using Serilog.Parsing;
19 | using System;
20 | using System.IO;
21 | using System.Linq;
22 |
23 | namespace Serilog.Sinks.Splunk
24 | {
25 | ///
26 | /// Renders log events into a Compact JSON format for consumption by Splunk.
27 | ///
28 | public class CompactSplunkJsonFormatter : ITextFormatter
29 | {
30 | private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(typeTagName: "$type");
31 | private readonly string _suffix;
32 | private readonly bool _renderTemplate;
33 | private readonly SubSecondPrecision _subSecondPrecision;
34 |
35 | ///
36 | /// Construct a .
37 | ///
38 | /// The source of the event
39 | /// The source type of the event
40 | /// The host of the event
41 | /// The Splunk index to log to
42 | /// If true, the template used will be rendered and written to the output as a property named MessageTemplate
43 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
44 |
45 | public CompactSplunkJsonFormatter(
46 | bool renderTemplate = false,
47 | string source = null,
48 | string sourceType = null,
49 | string host = null,
50 | string index = null,
51 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
52 | {
53 | _renderTemplate = renderTemplate;
54 | _subSecondPrecision = subSecondPrecision;
55 |
56 | var suffixWriter = new StringWriter();
57 | suffixWriter.Write("}"); // Terminates "event"
58 |
59 | if (!string.IsNullOrWhiteSpace(source))
60 | {
61 | suffixWriter.Write(",\"source\":");
62 | JsonValueFormatter.WriteQuotedJsonString(source, suffixWriter);
63 | }
64 |
65 | if (!string.IsNullOrWhiteSpace(sourceType))
66 | {
67 | suffixWriter.Write(",\"sourcetype\":");
68 | JsonValueFormatter.WriteQuotedJsonString(sourceType, suffixWriter);
69 | }
70 |
71 | if (!string.IsNullOrWhiteSpace(host))
72 | {
73 | suffixWriter.Write(",\"host\":");
74 | JsonValueFormatter.WriteQuotedJsonString(host, suffixWriter);
75 | }
76 |
77 | if (!string.IsNullOrWhiteSpace(index))
78 | {
79 | suffixWriter.Write(",\"index\":");
80 | JsonValueFormatter.WriteQuotedJsonString(index, suffixWriter);
81 | }
82 | suffixWriter.Write('}'); // Terminates the payload
83 | _suffix = suffixWriter.ToString();
84 | }
85 |
86 | ///
87 | public void Format(LogEvent logEvent, TextWriter output)
88 | {
89 | if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
90 | if (output == null) throw new ArgumentNullException(nameof(output));
91 |
92 | output.Write("{\"time\":\"");
93 | output.Write(logEvent.Timestamp.ToEpoch(_subSecondPrecision));
94 | output.Write("\",\"event\":{\"@l\":\"");
95 | output.Write(logEvent.Level);
96 | output.Write('"');
97 |
98 | if (_renderTemplate)
99 | {
100 | output.Write(",\"@mt\":");
101 | JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
102 |
103 | var tokensWithFormat = logEvent.MessageTemplate.Tokens
104 | .OfType()
105 | .Where(pt => pt.Format != null);
106 |
107 | // Better not to allocate an array in the 99.9% of cases where this is false
108 | // ReSharper disable once PossibleMultipleEnumeration
109 | if (tokensWithFormat.Any())
110 | {
111 | output.Write(",\"@r\":[");
112 | var delim = "";
113 | foreach (var r in tokensWithFormat)
114 | {
115 | output.Write(delim);
116 | delim = ",";
117 | var space = new StringWriter();
118 | r.Render(logEvent.Properties, space);
119 | JsonValueFormatter.WriteQuotedJsonString(space.ToString(), output);
120 | }
121 | output.Write(']');
122 | }
123 | }
124 | if (logEvent.Exception != null)
125 | {
126 | output.Write(",\"@x\":");
127 | JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
128 | }
129 |
130 | foreach (var property in logEvent.Properties)
131 | {
132 | var name = property.Key;
133 | if (name.Length > 0 && name[0] == '@')
134 | {
135 | // Escape first '@' by doubling
136 | name = '@' + name;
137 | }
138 |
139 | output.Write(',');
140 | JsonValueFormatter.WriteQuotedJsonString(name, output);
141 | output.Write(':');
142 | ValueFormatter.Format(property.Value, output);
143 | }
144 | output.WriteLine(_suffix);
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/CustomField.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Serilog.Sinks.Splunk
4 | { ///
5 | /// A Class for storing CustomField. They are sort of key,value pair. In simpler form key as string and value as single string, but could also be key and list of strings.
6 | ///
7 | public class CustomField
8 | { ///
9 | /// the fieldsname eg: role, version,
10 | ///
11 | public string Name { get; set; }
12 | ///
13 | /// All values even simple string are stored as a list
14 | ///
15 | public List ValueList { get; set; }
16 | ///
17 | /// constructor for a simple fieldname and a value both are strings
18 | ///
19 | /// Name of filed to be indexed by Splunk. Eg Role,Version,Channel
20 | /// Value of keypair. Eg. Test,1.08, RestService
21 | public CustomField(string name, string value)
22 | {
23 | Name = name;
24 | ValueList = new List { value };
25 | }
26 | ///
27 | /// Constructor for Name and array of values
28 | ///
29 | /// Name of field eg TypeOfResource
30 | /// Array of values that should be connected with field.Eg Backend,Service,Rest
31 | public CustomField(string name, List value)
32 | {
33 | Name = name;
34 | ValueList = value;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/CustomFields.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Serilog.Sinks.Splunk
4 | {
5 | ///
6 | /// Class for storing CustomFields that splunk will index for the event but will not be displayed in the event.
7 | /// They are the same for all events. Could could contain type of server or releasecode see: http://dev.splunk.com/view/event-collector/SP-CAAAFB6
8 | ///
9 | public class CustomFields
10 | {///
11 | /// The List of all CustomFields of type CustomField
12 | ///
13 | public List CustomFieldList { get; set; }
14 | ///
15 | /// Constructor with no inital data
16 | ///
17 | public CustomFields()
18 | {
19 | CustomFieldList = new List();
20 | }
21 | ///
22 | /// Constructor with simple CustomField
23 | ///
24 | ///
25 | public CustomFields(CustomField customField)
26 | {
27 | CustomFieldList = new List{customField};
28 | }
29 | ///
30 | /// Constructor with a list of CustomFields
31 | ///
32 | public CustomFields(List customFields)
33 | {
34 | CustomFieldList = customFields ;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/Epoch.cs:
--------------------------------------------------------------------------------
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 | namespace Serilog.Sinks.Splunk
16 | {
17 | using System;
18 | using System.Globalization;
19 |
20 | ///
21 | /// Provides extension methods for DateTimeOffset to convert to epoch time.
22 | ///
23 | public static class EpochExtensions
24 | {
25 | private static DateTimeOffset Epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
26 | const string MillisecondFormat = "#.000";
27 | const string MicrosecondFormat = "#.000000";
28 | const string NanosecondFormat = "#.000000000";
29 |
30 | ///
31 | /// Converts a DateTimeOffset value to epoch time with specified sub-second precision.
32 | ///
33 | /// The DateTimeOffset value to convert.
34 | /// Timestamp sub-second precision.
35 | /// The epoch time representation of the DateTimeOffset value.
36 | public static string ToEpoch(this DateTimeOffset value, SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
37 | {
38 | // From Splunk HTTP Collector Protocol
39 | // The default time format is epoch time format, in the format ..
40 | // For example, 1433188255.500 indicates 1433188255 seconds and 500 milliseconds after epoch,
41 | // or Monday, June 1, 2015, at 7:50:55 PM GMT.
42 | // See: https://docs.splunk.com/Documentation/Splunk/9.2.0/SearchReference/Commontimeformatvariables
43 |
44 | var totalSeconds = ToSeconds(value.ToUniversalTime().Ticks - Epoch.Ticks);
45 | var format = subSecondPrecision switch {
46 | SubSecondPrecision.Nanoseconds => NanosecondFormat,
47 | SubSecondPrecision.Microseconds => MicrosecondFormat,
48 | _ => MillisecondFormat,
49 | };
50 |
51 | return Math.Round(totalSeconds, (int)subSecondPrecision, MidpointRounding.AwayFromZero).ToString(format, CultureInfo.InvariantCulture);
52 | }
53 |
54 | private static decimal ToSeconds(long ticks)
55 | {
56 | long wholeSecondPortion = (ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond;
57 | long subsecondPortion = ticks - wholeSecondPortion;
58 | decimal wholeSeconds = wholeSecondPortion / (decimal)TimeSpan.TicksPerSecond;
59 | decimal subseconds = subsecondPortion / (decimal)TimeSpan.TicksPerSecond;
60 | return wholeSeconds + subseconds;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorClient.cs:
--------------------------------------------------------------------------------
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 |
16 | using System;
17 | using System.Net.Http;
18 | using System.Net.Http.Headers;
19 |
20 | namespace Serilog.Sinks.Splunk
21 | {
22 | internal class EventCollectorClient : HttpClient, IDisposable
23 | {
24 | private const string AUTH_SCHEME = "Splunk";
25 | private const string SPLUNK_REQUEST_CHANNEL = "X-Splunk-Request-Channel";
26 |
27 | public EventCollectorClient(string eventCollectorToken) : base()
28 | {
29 | SetHeaders(eventCollectorToken);
30 | }
31 |
32 | public EventCollectorClient(string eventCollectorToken, HttpMessageHandler messageHandler) : base(messageHandler)
33 | {
34 | SetHeaders(eventCollectorToken);
35 | }
36 |
37 | private void SetHeaders(string eventCollectorToken)
38 | {
39 | // Reminder: If the event collector url is redirected, all authentication headers will be removed.
40 | // See: https://github.com/dotnet/runtime/blob/ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs#L53
41 |
42 | DefaultRequestHeaders.Authorization ??= new AuthenticationHeaderValue(AUTH_SCHEME, eventCollectorToken);
43 |
44 | if (!this.DefaultRequestHeaders.Contains(SPLUNK_REQUEST_CHANNEL))
45 | {
46 | this.DefaultRequestHeaders.Add(SPLUNK_REQUEST_CHANNEL, Guid.NewGuid().ToString());
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorRequest.cs:
--------------------------------------------------------------------------------
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 |
16 | using System;
17 | using System.Net.Http;
18 | using System.Text;
19 |
20 | namespace Serilog.Sinks.Splunk
21 | {
22 | internal class EventCollectorRequest : HttpRequestMessage
23 | {
24 | internal EventCollectorRequest(string splunkHost, string jsonPayLoad, string uri = ConfigurationDefaults.DefaultEventCollectorPath)
25 | {
26 | var hostUrl = splunkHost.Contains(ConfigurationDefaults.DefaultCollectorPath)
27 | ? splunkHost
28 | : $"{splunkHost.TrimEnd('/')}/{uri.TrimStart('/').TrimEnd('/')}";
29 |
30 | RequestUri = new Uri(hostUrl);
31 | Content = new StringContent(jsonPayLoad, Encoding.UTF8, "application/json");
32 | Method = HttpMethod.Post;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/EventCollectorSink.cs:
--------------------------------------------------------------------------------
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 Serilog.Core;
16 | using Serilog.Debugging;
17 | using Serilog.Events;
18 | using Serilog.Formatting;
19 | using System;
20 | using System.Collections.Generic;
21 | using System.IO;
22 | using System.Linq;
23 | using System.Net;
24 | using System.Net.Http;
25 | using System.Threading.Tasks;
26 |
27 | namespace Serilog.Sinks.Splunk
28 | {
29 | ///
30 | /// A sink to log to the Event Collector available in Splunk 6.3
31 | ///
32 | public class EventCollectorSink : IBatchedLogEventSink
33 | {
34 | internal const int DefaultQueueLimit = 100000;
35 |
36 | private readonly string _splunkHost;
37 | private readonly string _uriPath;
38 | private readonly ITextFormatter _jsonFormatter;
39 | private readonly EventCollectorClient _httpClient;
40 |
41 |
42 | ///
43 | /// Taken from Splunk.Logging.Common
44 | ///
45 | private static readonly HttpStatusCode[] HttpEventCollectorApplicationErrors =
46 | {
47 | HttpStatusCode.Forbidden,
48 | HttpStatusCode.MethodNotAllowed,
49 | HttpStatusCode.BadRequest
50 | };
51 |
52 | ///
53 | /// Creates a new instance of the sink
54 | ///
55 | /// The host of the Splunk instance with the Event collector configured
56 | /// The token to use when authenticating with the event collector
57 | /// The format provider used when rendering the message
58 | /// Whether to render the message template
59 | /// Include "RenderedMessage" parameter in output JSON message.
60 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
61 | public EventCollectorSink(
62 | string splunkHost,
63 | string eventCollectorToken,
64 | IFormatProvider formatProvider = null,
65 | bool renderTemplate = true,
66 | bool renderMessage = true,
67 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
68 | : this(
69 | splunkHost,
70 | eventCollectorToken,
71 | null, null, null, null, null,
72 | formatProvider,
73 | renderTemplate,
74 | renderMessage,
75 | subSecondPrecision: subSecondPrecision)
76 | {
77 | }
78 |
79 | ///
80 | /// Creates a new instance of the sink
81 | ///
82 | /// The host of the Splunk instance with the Event collector configured
83 | /// The token to use when authenticating with the event collector
84 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
85 | /// The format provider used when rendering the message
86 | /// Whether to render the message template
87 | /// The Splunk index to log to
88 | /// The source of the event
89 | /// The source type of the event
90 | /// The host of the event
91 | /// The handler used to send HTTP requests
92 | /// Include "RenderedMessage" parameter in output JSON message.
93 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
94 | public EventCollectorSink(
95 | string splunkHost,
96 | string eventCollectorToken,
97 | string uriPath,
98 | string source,
99 | string sourceType,
100 | string host,
101 | string index,
102 | IFormatProvider formatProvider = null,
103 | bool renderTemplate = true,
104 | bool renderMessage = true,
105 | HttpMessageHandler messageHandler = null,
106 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
107 | : this(
108 | splunkHost,
109 | eventCollectorToken,
110 | uriPath,
111 | new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, subSecondPrecision: subSecondPrecision),
112 | messageHandler)
113 | {
114 | }
115 |
116 | ///
117 | /// Creates a new instance of the sink with Customfields
118 | ///
119 | /// The host of the Splunk instance with the Event collector configured
120 | /// The token to use when authenticating with the event collector
121 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
122 | /// The format provider used when rendering the message
123 | /// Whether to render the message template
124 | /// The Splunk index to log to
125 | /// Add extra CustomExtraFields for Splunk to index
126 | /// The source of the event
127 | /// The source type of the event
128 | /// The host of the event
129 | /// The handler used to send HTTP requests
130 | /// Include "RenderedMessage" parameter in output JSON message.
131 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
132 | public EventCollectorSink(
133 | string splunkHost,
134 | string eventCollectorToken,
135 | string uriPath,
136 | string source,
137 | string sourceType,
138 | string host,
139 | string index,
140 | CustomFields fields,
141 | IFormatProvider formatProvider = null,
142 | bool renderTemplate = true,
143 | bool renderMessage = true,
144 | HttpMessageHandler messageHandler = null,
145 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
146 | // TODO here is the jsonformatter creation. We must make way to test output of jsonformatter.
147 | : this(
148 | splunkHost,
149 | eventCollectorToken,
150 | uriPath,
151 | new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, fields, subSecondPrecision: subSecondPrecision),
152 | messageHandler)
153 | {
154 | }
155 |
156 | ///
157 | /// Creates a new instance of the sink
158 | ///
159 | /// The host of the Splunk instance with the Event collector configured
160 | /// The token to use when authenticating with the event collector
161 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
162 | /// The text formatter used to render log events into a JSON format for consumption by Splunk
163 | /// The handler used to send HTTP requests
164 | public EventCollectorSink(
165 | string splunkHost,
166 | string eventCollectorToken,
167 | string uriPath,
168 | ITextFormatter jsonFormatter,
169 | HttpMessageHandler messageHandler = null)
170 | {
171 | _uriPath = uriPath;
172 | _splunkHost = splunkHost;
173 | _jsonFormatter = jsonFormatter;
174 |
175 | _httpClient = messageHandler != null
176 | ? new EventCollectorClient(eventCollectorToken, messageHandler)
177 | : new EventCollectorClient(eventCollectorToken);
178 | }
179 |
180 | ///
181 | public virtual Task OnEmptyBatchAsync() => Task.CompletedTask;
182 |
183 | ///
184 | public virtual async Task EmitBatchAsync(IReadOnlyCollection batch)
185 | {
186 | var allEvents = new StringWriter();
187 |
188 | foreach (var logEvent in batch)
189 | {
190 | _jsonFormatter.Format(logEvent, allEvents);
191 | }
192 |
193 | var request = new EventCollectorRequest(_splunkHost, allEvents.ToString(), _uriPath);
194 | var response = await _httpClient.SendAsync(request).ConfigureAwait(false);
195 |
196 | if (!response.IsSuccessStatusCode)
197 | {
198 | //Application Errors sent via HTTP Event Collector
199 | if (HttpEventCollectorApplicationErrors.Any(x => x == response.StatusCode))
200 | {
201 | // By not throwing an exception here the PeriodicBatchingSink will assume the batch succeeded and not send it again.
202 | SelfLog.WriteLine(
203 | "A status code of {0} was received when attempting to send to {1}. The event has been discarded and will not be placed back in the queue.",
204 | response.StatusCode.ToString(), _splunkHost);
205 | }
206 | else
207 | {
208 | // EnsureSuccessStatusCode will throw an exception and the PeriodicBatchingSink will catch/log the exception and retry the batch.
209 | response.EnsureSuccessStatusCode();
210 | }
211 | }
212 | }
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/SplunkJsonFormatter.cs:
--------------------------------------------------------------------------------
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 Serilog.Events;
16 | using Serilog.Formatting;
17 | using Serilog.Formatting.Json;
18 | using System;
19 | using System.Collections.Generic;
20 | using System.IO;
21 |
22 | namespace Serilog.Sinks.Splunk
23 | {
24 | ///
25 | ///
26 | /// Renders log events into a default JSON format for consumption by Splunk.
27 | ///
28 | public class SplunkJsonFormatter : ITextFormatter
29 | {
30 | static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter();
31 |
32 | private readonly bool _renderTemplate;
33 | private readonly IFormatProvider _formatProvider;
34 | private readonly SubSecondPrecision _subSecondPrecision;
35 | private readonly bool _renderMessage;
36 | private readonly string _suffix;
37 |
38 | ///
39 | ///
40 | /// Construct a .
41 | ///
42 | /// Supplies culture-specific formatting information, or null.
43 | /// If true, the template used will be rendered and written to the output as a property named MessageTemplate
44 | /// Removes the "RenderedMessage" parameter from output JSON message.
45 | public SplunkJsonFormatter(
46 | bool renderTemplate,
47 | bool renderMessage,
48 | IFormatProvider formatProvider)
49 | : this(renderTemplate, renderMessage, formatProvider, null, null, null, null)
50 | {
51 | }
52 |
53 | ///
54 | /// Construct a .
55 | ///
56 | /// Supplies culture-specific formatting information, or null.
57 | /// If true, the template used will be rendered and written to the output as a property named MessageTemplate
58 | /// The Splunk index to log to
59 | /// The source of the event
60 | /// The source type of the event
61 | /// The host of the event
62 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
63 | /// Removes the "RenderedMessage" parameter from output JSON message.
64 | public SplunkJsonFormatter(
65 | bool renderTemplate,
66 | bool renderMessage,
67 | IFormatProvider formatProvider,
68 | string source,
69 | string sourceType,
70 | string host,
71 | string index,
72 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
73 | : this(renderTemplate, renderMessage, formatProvider, source, sourceType, host, index, null, subSecondPrecision: subSecondPrecision)
74 | {
75 | }
76 |
77 | ///
78 | /// Construct a .
79 | ///
80 | /// Supplies culture-specific formatting information, or null.
81 | /// If true, the template used will be rendered and written to the output as a property named MessageTemplate
82 | /// The Splunk index to log to
83 | /// The source of the event
84 | /// The source type of the event
85 | /// The host of the event
86 | /// Object that describes extra splunk fields that should be indexed with event see: http://dev.splunk.com/view/event-collector/SP-CAAAFB6
87 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
88 | /// Include "RenderedMessage" parameter in output JSON message.
89 | public SplunkJsonFormatter(
90 | bool renderTemplate,
91 | bool renderMessage,
92 | IFormatProvider formatProvider,
93 | string source,
94 | string sourceType,
95 | string host,
96 | string index,
97 | CustomFields customFields,
98 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
99 | {
100 | _renderTemplate = renderTemplate;
101 | _formatProvider = formatProvider;
102 | _subSecondPrecision = subSecondPrecision;
103 | _renderMessage = renderMessage;
104 |
105 | using (var suffixWriter = new StringWriter())
106 | {
107 | suffixWriter.Write("}"); // Terminates "event"
108 |
109 | if (!string.IsNullOrWhiteSpace(source))
110 | {
111 | suffixWriter.Write(",\"source\":");
112 | JsonValueFormatter.WriteQuotedJsonString(source, suffixWriter);
113 | }
114 |
115 | if (!string.IsNullOrWhiteSpace(sourceType))
116 | {
117 | suffixWriter.Write(",\"sourcetype\":");
118 | JsonValueFormatter.WriteQuotedJsonString(sourceType, suffixWriter);
119 | }
120 |
121 | if (!string.IsNullOrWhiteSpace(host))
122 | {
123 | suffixWriter.Write(",\"host\":");
124 | JsonValueFormatter.WriteQuotedJsonString(host, suffixWriter);
125 | }
126 |
127 | if (!string.IsNullOrWhiteSpace(index))
128 | {
129 | suffixWriter.Write(",\"index\":");
130 | JsonValueFormatter.WriteQuotedJsonString(index, suffixWriter);
131 | }
132 | if (customFields != null)
133 | {
134 | // "fields": {"club":"glee", "wins",["regionals","nationals"]}
135 | suffixWriter.Write(",\"fields\": {");
136 | var lastFieldIndex = customFields.CustomFieldList.Count;
137 |
138 | foreach (var customField in customFields.CustomFieldList)
139 | {
140 | if (customField.ValueList.Count == 1)
141 | {
142 | //only one value e.g "club":"glee",
143 | suffixWriter.Write($"\"{customField.Name}\":");
144 | suffixWriter.Write($"\"{customField.ValueList[0]}\"");
145 | }
146 | else
147 | {
148 | //array of values e.g "wins",["regionals","nationals"]
149 | suffixWriter.Write($"\"{customField.Name}\":[");
150 | var lastArrIndex = customField.ValueList.Count;
151 | foreach (var cf in customField.ValueList)
152 | {
153 | suffixWriter.Write($"\"{cf}\"");
154 | //Different behaviour if it is the last one
155 | suffixWriter.Write(--lastArrIndex > 0 ? "," : "]");
156 | }
157 | }
158 | suffixWriter.Write(--lastFieldIndex > 0 ? "," : "}");
159 | }
160 |
161 | }
162 | suffixWriter.Write('}'); // Terminates the payload
163 | _suffix = suffixWriter.ToString();
164 | }
165 | }
166 |
167 | ///
168 | public void Format(LogEvent logEvent, TextWriter output)
169 | {
170 | if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
171 | if (output == null) throw new ArgumentNullException(nameof(output));
172 |
173 | output.Write("{\"time\":\"");
174 | output.Write(logEvent.Timestamp.ToEpoch(_subSecondPrecision));
175 | output.Write("\",\"event\":{\"Level\":\"");
176 | output.Write(logEvent.Level);
177 | output.Write('"');
178 |
179 | if (_renderTemplate)
180 | {
181 | output.Write(",\"MessageTemplate\":");
182 | JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
183 | }
184 |
185 | if (_renderMessage)
186 | {
187 | output.Write(",\"RenderedMessage\":");
188 | JsonValueFormatter.WriteQuotedJsonString(logEvent.RenderMessage(_formatProvider), output);
189 | }
190 |
191 | if (logEvent.Exception != null)
192 | {
193 | output.Write(",\"Exception\":");
194 | JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
195 | }
196 |
197 | if (logEvent.TraceId != null)
198 | {
199 | output.Write(",\"TraceId\":");
200 | JsonValueFormatter.WriteQuotedJsonString(logEvent.TraceId.ToString()!, output);
201 | }
202 |
203 | if (logEvent.SpanId != null)
204 | {
205 | output.Write(",\"SpanId\":");
206 | JsonValueFormatter.WriteQuotedJsonString(logEvent.SpanId.ToString()!, output);
207 | }
208 |
209 | if (logEvent.Properties.Count != 0)
210 | WriteProperties(logEvent.Properties, output);
211 |
212 | output.WriteLine(_suffix);
213 | }
214 |
215 | static void WriteProperties(IReadOnlyDictionary properties, TextWriter output)
216 | {
217 | output.Write(",\"Properties\":{");
218 |
219 | var precedingDelimiter = "";
220 | foreach (var property in properties)
221 | {
222 | output.Write(precedingDelimiter);
223 | precedingDelimiter = ",";
224 |
225 | JsonValueFormatter.WriteQuotedJsonString(property.Key, output);
226 | output.Write(':');
227 | ValueFormatter.Format(property.Value, output);
228 | }
229 |
230 | output.Write('}');
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/Sinks/Splunk/SubSecondPrecision.cs:
--------------------------------------------------------------------------------
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 | namespace Serilog.Sinks.Splunk
16 | {
17 | ///
18 | /// Enum representing the precision of sub-second time measurements.
19 | ///
20 | public enum SubSecondPrecision
21 | {
22 | ///
23 | /// Represents precision in milliseconds. Value corresponds to the number of decimal places.
24 | ///
25 | Milliseconds = 3,
26 |
27 | ///
28 | /// Represents precision in microseconds. Value corresponds to the number of decimal places.
29 | ///
30 | Microseconds = 6,
31 |
32 | ///
33 | /// Represents precision in nanoseconds. Value corresponds to the number of decimal places.
34 | ///
35 | Nanoseconds = 9
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.Splunk/SplunkLoggingConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 |
16 | using Serilog.Configuration;
17 | using Serilog.Core;
18 | using Serilog.Events;
19 | using Serilog.Formatting;
20 | using Serilog.Sinks.Splunk;
21 | using System;
22 | using System.Net.Http;
23 |
24 | namespace Serilog
25 | {
26 | ///
27 | /// Fluent configuration methods for Logger configuration
28 | ///
29 | public static class SplunkLoggingConfigurationExtensions
30 | {
31 | ///
32 | /// Adds a sink that writes log events as to a Splunk instance via the HTTP Event Collector.
33 | ///
34 | /// The logger config
35 | /// The Splunk host that is configured with an Event Collector
36 | /// The token provided to authenticate to the Splunk Event Collector
37 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
38 | /// The Splunk index to log to
39 | /// The source of the event
40 | /// The source type of the event
41 | /// The host of the event
42 | /// The minimum log event level required in order to write an event to the sink.
43 | /// Supplies culture-specific formatting information, or null.
44 | /// If true, the message template will be rendered
45 | /// Include "RenderedMessage" parameter in output JSON message.
46 | /// The interval in seconds that the queue should be instpected for batching
47 | /// The size of the batch
48 | /// Maximum number of events in the queue
49 | /// The handler used to send HTTP requests
50 | /// A switch allowing the pass-through minimum level to be changed at runtime.
51 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
52 | ///
53 | public static LoggerConfiguration EventCollector(
54 | this LoggerSinkConfiguration configuration,
55 | string splunkHost,
56 | string eventCollectorToken,
57 | string uriPath = ConfigurationDefaults.DefaultEventCollectorPath,
58 | string source = ConfigurationDefaults.DefaultSource,
59 | string sourceType = ConfigurationDefaults.DefaultSourceType,
60 | string host = ConfigurationDefaults.DefaultHost,
61 | string index = ConfigurationDefaults.DefaultIndex,
62 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
63 | IFormatProvider formatProvider = null,
64 | bool renderTemplate = true,
65 | bool renderMessage = true,
66 | int batchIntervalInSeconds = 2,
67 | int batchSizeLimit = 100,
68 | int? queueLimit = null,
69 | HttpMessageHandler messageHandler = null,
70 | LoggingLevelSwitch levelSwitch = null,
71 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
72 | {
73 | if (configuration == null) throw new ArgumentNullException(nameof(configuration));
74 |
75 | var batchingOptions = new BatchingOptions
76 | {
77 | BatchSizeLimit = batchSizeLimit,
78 | BufferingTimeLimit = TimeSpan.FromSeconds(batchIntervalInSeconds),
79 | EagerlyEmitFirstEvent = true,
80 | QueueLimit = queueLimit
81 | };
82 |
83 | var eventCollectorSink = new EventCollectorSink(
84 | splunkHost,
85 | eventCollectorToken,
86 | uriPath,
87 | source,
88 | sourceType,
89 | host,
90 | index,
91 | formatProvider,
92 | renderTemplate,
93 | renderMessage,
94 | messageHandler: messageHandler,
95 | subSecondPrecision: subSecondPrecision);
96 |
97 | return configuration.Sink(eventCollectorSink, batchingOptions, restrictedToMinimumLevel, levelSwitch);
98 | }
99 |
100 | ///
101 | /// Adds a sink that writes log events as to a Splunk instance via the HTTP Event Collector.
102 | ///
103 | /// The logger config
104 | /// The Splunk host that is configured with an Event Collector
105 | /// The token provided to authenticate to the Splunk Event Collector
106 | /// The text formatter used to render log events into a JSON format for consumption by Splunk
107 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
108 | /// The minimum log event level required in order to write an event to the sink.
109 | /// The interval in seconds that the queue should be instpected for batching
110 | /// The size of the batch
111 | /// Maximum number of events in the queue
112 | /// The handler used to send HTTP requests
113 | /// A switch allowing the pass-through minimum level to be changed at runtime.
114 | ///
115 | public static LoggerConfiguration EventCollector(
116 | this LoggerSinkConfiguration configuration,
117 | string splunkHost,
118 | string eventCollectorToken,
119 | ITextFormatter jsonFormatter,
120 | string uriPath = ConfigurationDefaults.DefaultEventCollectorPath,
121 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
122 | int batchIntervalInSeconds = 2,
123 | int batchSizeLimit = 100,
124 | int? queueLimit = null,
125 | HttpMessageHandler messageHandler = null,
126 | LoggingLevelSwitch levelSwitch = null)
127 | {
128 | if (configuration == null) throw new ArgumentNullException(nameof(configuration));
129 | if (jsonFormatter == null) throw new ArgumentNullException(nameof(jsonFormatter));
130 |
131 |
132 | var batchingOptions = new BatchingOptions
133 | {
134 | BatchSizeLimit = batchSizeLimit,
135 | BufferingTimeLimit = TimeSpan.FromSeconds(batchIntervalInSeconds),
136 | EagerlyEmitFirstEvent = true,
137 | QueueLimit = queueLimit
138 | };
139 |
140 | var eventCollectorSink = new EventCollectorSink(
141 | splunkHost,
142 | eventCollectorToken,
143 | uriPath,
144 | jsonFormatter,
145 | messageHandler);
146 |
147 | return configuration.Sink(eventCollectorSink, batchingOptions, restrictedToMinimumLevel, levelSwitch);
148 | }
149 |
150 |
151 | ///
152 | /// Adds a sink that writes log events as to a Splunk instance via the HTTP Event Collector.
153 | ///
154 | /// The logger config
155 | /// The Splunk host that is configured with an Event Collector
156 | /// The token provided to authenticate to the Splunk Event Collector
157 | /// Change the default endpoint of the Event Collector e.g. services/collector/event
158 | /// The Splunk index to log to
159 | /// The source of the event
160 | /// The source type of the event
161 | /// The host of the event
162 | /// The minimum log event level required in order to write an event to the sink.
163 | /// Supplies culture-specific formatting information, or null.
164 | /// If ture, the message template will be rendered
165 | /// The interval in seconds that the queue should be instpected for batching
166 | /// The size of the batch
167 | /// Maximum number of events in the queue
168 | /// The handler used to send HTTP requests
169 | /// A switch allowing the pass-through minimum level to be changed at runtime.
170 | /// Customfields that will be indexed in splunk with this event
171 | /// Include "RenderedMessage" parameter in output JSON message.
172 | /// Timestamp sub-second precision. Splunk props.conf setup is required.
173 | ///
174 | public static LoggerConfiguration EventCollector(
175 | this LoggerSinkConfiguration configuration,
176 | string splunkHost,
177 | string eventCollectorToken,
178 | CustomFields fields,
179 | string uriPath = ConfigurationDefaults.DefaultEventCollectorPath,
180 | string source = ConfigurationDefaults.DefaultSource,
181 | string sourceType = ConfigurationDefaults.DefaultSourceType,
182 | string host = ConfigurationDefaults.DefaultHost,
183 | string index = ConfigurationDefaults.DefaultIndex,
184 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
185 | IFormatProvider formatProvider = null,
186 | bool renderTemplate = true,
187 | bool renderMessage = true,
188 | int batchIntervalInSeconds = 2,
189 | int batchSizeLimit = 100,
190 | int? queueLimit = null,
191 | HttpMessageHandler messageHandler = null,
192 | LoggingLevelSwitch levelSwitch = null,
193 | SubSecondPrecision subSecondPrecision = SubSecondPrecision.Milliseconds)
194 | {
195 | if (configuration == null) throw new ArgumentNullException(nameof(configuration));
196 |
197 | var batchingOptions = new BatchingOptions
198 | {
199 | BatchSizeLimit = batchSizeLimit,
200 | BufferingTimeLimit = TimeSpan.FromSeconds(batchIntervalInSeconds),
201 | EagerlyEmitFirstEvent = true,
202 | QueueLimit = queueLimit
203 | };
204 |
205 | var eventCollectorSink = new EventCollectorSink(
206 | splunkHost,
207 | eventCollectorToken,
208 | uriPath,
209 | source,
210 | sourceType,
211 | host,
212 | index,
213 | fields,
214 | formatProvider,
215 | renderTemplate,
216 | renderMessage,
217 | messageHandler,
218 | subSecondPrecision: subSecondPrecision
219 | );
220 |
221 | return configuration.Sink(eventCollectorSink, batchingOptions, restrictedToMinimumLevel, levelSwitch);
222 | }
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("Serilog.Sinks.Splunk.Tests, PublicKey=" +
5 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" +
6 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" +
7 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" +
8 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" +
9 | "b19485ec")]
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Serilog.Sinks.Splunk.TCP.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | The Splunk TCP Sink for Serilog
7 | netstandard2.1;netstandard2.0;net8.0;net9.0
8 | Serilog.Sinks.Splunk.TCP
9 | Serilog.Sinks.Splunk.TCP
10 | serilog;splunk;logging;tcp
11 | ../../assets/Serilog.snk
12 | true
13 | true
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Sinks/Splunk/SplunkTcpSinkConnectionInfo.cs:
--------------------------------------------------------------------------------
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.Net;
16 |
17 | namespace Serilog.Sinks.Splunk
18 | {
19 | ///
20 | /// Defines connection info used to connect against Splunk
21 | /// using TCP.
22 | ///
23 | public class SplunkTcpSinkConnectionInfo
24 | {
25 | ///
26 | /// Default size of the socket writer queue.
27 | ///
28 | public const int DefaultMaxQueueSize = 10000;
29 |
30 | ///
31 | /// Splunk host.
32 | ///
33 | public IPAddress Host { get; }
34 |
35 | ///
36 | /// Splunk port.
37 | ///
38 | public int Port { get; }
39 |
40 | ///
41 | /// Max Queue size for the TCP socket writer.
42 | /// See for default value (10000).
43 | ///
44 | public int MaxQueueSize { get; set; } = DefaultMaxQueueSize;
45 |
46 | ///
47 | /// Creates an instance of used
48 | /// for defining connection info for connecting using TCP against Splunk.
49 | ///
50 | /// Splunk host.
51 | /// Splunk TCP port.
52 | public SplunkTcpSinkConnectionInfo(string host, int port) : this(IPAddress.Parse(host), port) { }
53 |
54 | ///
55 | /// Creates an instance of used
56 | /// for defining connection info for connecting using TCP against Splunk.
57 | ///
58 | /// Splunk host.
59 | /// Splunk TCP port.
60 | public SplunkTcpSinkConnectionInfo(IPAddress host, int port)
61 | {
62 | Host = host;
63 | Port = port;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Sinks/Splunk/TcpSink.cs:
--------------------------------------------------------------------------------
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 Serilog.Core;
16 | using Serilog.Events;
17 | using Serilog.Formatting;
18 | using Splunk.Logging;
19 | using System;
20 | using System.IO;
21 | using System.Text;
22 |
23 | namespace Serilog.Sinks.Splunk
24 | {
25 | ///
26 | /// A sink that logs to Splunk over TCP
27 | ///
28 | public class TcpSink : ILogEventSink, IDisposable
29 | {
30 | private readonly ITextFormatter _formatter;
31 | private readonly SplunkTcpSinkConnectionInfo _connectionInfo;
32 | private bool disposedValue = false;
33 |
34 | private TcpSocketWriter _writer;
35 |
36 | ///
37 | /// Creates an instance of the Splunk TCP Sink.
38 | ///
39 | /// Connection info used for connecting against Splunk.
40 | /// Optional format provider
41 | /// If true, the message template will be rendered
42 | /// Include "RenderedMessage" parameter in output JSON message.
43 | public TcpSink(SplunkTcpSinkConnectionInfo connectionInfo, IFormatProvider formatProvider = null, bool renderTemplate = true, bool renderMessage = true)
44 | : this(connectionInfo, CreateDefaultFormatter(formatProvider, renderTemplate, renderMessage))
45 | {
46 | }
47 |
48 | ///
49 | /// Creates an instance of the Splunk TCP Sink.
50 | ///
51 | /// Connection info used for connecting against Splunk.
52 | /// Custom formatter to use if you e.g. do not want to use the JsonFormatter.
53 | public TcpSink(SplunkTcpSinkConnectionInfo connectionInfo, ITextFormatter formatter)
54 | {
55 | _connectionInfo = connectionInfo;
56 | _formatter = formatter;
57 | _writer = CreateSocketWriter(connectionInfo);
58 | }
59 |
60 | ///
61 | protected virtual void Dispose(bool disposing)
62 | {
63 | if (!disposedValue)
64 | {
65 | if (disposing)
66 | {
67 | _writer?.Dispose();
68 | _writer = null;
69 | }
70 | disposedValue = true;
71 | }
72 | }
73 |
74 | ///
75 | public void Dispose()
76 | {
77 | Dispose(true);
78 | }
79 |
80 | ///
81 | public void Emit(LogEvent logEvent)
82 | {
83 | var sb = new StringBuilder();
84 |
85 | using (var sw = new StringWriter(sb))
86 | _formatter.Format(logEvent, sw);
87 |
88 | _writer.Enqueue(sb.ToString());
89 | }
90 |
91 | private static TcpSocketWriter CreateSocketWriter(SplunkTcpSinkConnectionInfo connectionInfo)
92 | {
93 | var reconnectionPolicy = new ExponentialBackoffTcpReconnectionPolicy();
94 |
95 | return new TcpSocketWriter(connectionInfo.Host, connectionInfo.Port, reconnectionPolicy, connectionInfo.MaxQueueSize);
96 | }
97 |
98 | private static SplunkJsonFormatter CreateDefaultFormatter(IFormatProvider formatProvider, bool renderTemplate, bool renderMessage = true)
99 | {
100 | return new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/ExponentialBackoffTcpReconnectionPolicy.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Splunk, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"): you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations
14 | * under the License.
15 | */
16 | using System;
17 | using System.Net;
18 | using System.Net.Sockets;
19 | using System.Threading;
20 | using System.Threading.Tasks;
21 |
22 | namespace Splunk.Logging
23 | {
24 | ///
25 | /// TcpConnectionPolicy implementation that tries to reconnect after
26 | /// increasingly long intervals.
27 | ///
28 | ///
29 | /// The intervals double every time, starting from 0s, 1s, 2s, 4s, ...
30 | /// until 10 minutes between connections, when it plateaus and does
31 | /// not increase the interval length any further.
32 | ///
33 | public class ExponentialBackoffTcpReconnectionPolicy : ITcpReconnectionPolicy
34 | {
35 | private int ceiling = 10 * 60; // 10 minutes in seconds
36 |
37 | public Socket Connect(Func connect, IPAddress host, int port, CancellationToken cancellationToken)
38 | {
39 | int delay = 1; // in seconds
40 | while (!cancellationToken.IsCancellationRequested)
41 | {
42 | try
43 | {
44 | return connect(host, port);
45 | }
46 | catch (SocketException) { }
47 |
48 | // If this is cancelled via the cancellationToken instead of
49 | // completing its delay, the next while-loop test will fail,
50 | // the loop will terminate, and the method will return null
51 | // with no additional connection attempts.
52 | Task.Delay(delay * 1000, cancellationToken).Wait();
53 | // The nth delay is min(10 minutes, 2^n - 1 seconds).
54 | delay = Math.Min((delay + 1) * 2 - 1, ceiling);
55 | }
56 |
57 | // cancellationToken has been cancelled.
58 | return null;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/FixedSizeQueue.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Splunk, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"): you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations
14 | * under the License.
15 | */
16 | using System;
17 | using System.Collections.Concurrent;
18 | using System.Threading;
19 |
20 | namespace Splunk.Logging
21 | {
22 | ///
23 | /// A queue with a maximum size. When the queue is at its maximum size
24 | /// and a new item is queued, the oldest item in the queue is dropped.
25 | ///
26 | ///
27 | internal class FixedSizeQueue
28 | {
29 | public int Size { get; private set; }
30 | public IProgress Progress = new Progress();
31 | public bool IsCompleted { get; private set; }
32 |
33 | private readonly BlockingCollection _collection = new BlockingCollection();
34 |
35 | public FixedSizeQueue(int size)
36 | : base()
37 | {
38 | Size = size;
39 | IsCompleted = false;
40 | }
41 |
42 | public void Enqueue(T obj)
43 | {
44 | lock (this)
45 | {
46 | if (IsCompleted)
47 | {
48 | throw new InvalidOperationException("Tried to add an item to a completed queue.");
49 | }
50 |
51 | _collection.Add(obj);
52 |
53 | while (_collection.Count > Size)
54 | {
55 | _collection.Take();
56 | }
57 | Progress.Report(true);
58 | }
59 | }
60 |
61 | public void CompleteAdding()
62 | {
63 | lock (this)
64 | {
65 | IsCompleted = true;
66 | }
67 | }
68 |
69 | public T Dequeue(CancellationToken cancellationToken)
70 | {
71 | return _collection.Take(cancellationToken);
72 | }
73 |
74 | public T Dequeue()
75 | {
76 | return _collection.Take();
77 | }
78 |
79 |
80 | public decimal Count { get { return _collection.Count; } }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/ITcpReconnectionPolicy.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Splunk, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"): you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations
14 | * under the License.
15 | */
16 | using System;
17 | using System.Net;
18 | using System.Net.Sockets;
19 | using System.Threading;
20 |
21 | namespace Splunk.Logging
22 | {
23 | ///
24 | /// TcpConnectionPolicy encapsulates a policy for what logging via TCP should
25 | /// do when there is a socket error.
26 | ///
27 | ///
28 | /// TCP loggers in this library (TcpTraceListener and TcpEventSink) take a
29 | /// TcpConnectionPolicy as an argument to their constructor. When the TCP
30 | /// session the logger uses has an error, the logger suspends logging and calls
31 | /// the Reconnect method of an implementation of TcpConnectionPolicy to get a
32 | /// new socket.
33 | ///
34 | public interface ITcpReconnectionPolicy
35 | {
36 | // A blocking method that should eventually return a Socket when it finally
37 | // manages to get a connection, or throw a TcpReconnectFailure if the policy
38 | // says to give up trying to connect.
39 | ///
40 | /// Try to reestablish a TCP connection.
41 | ///
42 | ///
43 | /// The method should block until it either
44 | ///
45 | /// 1. succeeds and returns a connected TCP socket, or
46 | /// 2. fails and throws a TcpReconnectFailure exception, or
47 | /// 3. the cancellationToken is cancelled, in which case the method should
48 | /// return null.
49 | ///
50 | /// The method takes a zero-parameter function that encapsulates trying to
51 | /// make a single connection and a cancellation token to stop the method
52 | /// if the logging system that invoked it is disposed.
53 | ///
54 | /// For example, the default ExponentialBackoffTcpConnectionPolicy invokes
55 | /// connect after increasingly long intervals until it makes a successful
56 | /// connnection, or is cancelled by the cancellationToken, at which point
57 | /// it returns null.
58 | ///
59 | /// A zero-parameter function that tries once to
60 | /// establish a connection.
61 | /// A token used to cancel the reconnect
62 | /// attempt when the invoking logger is disposed.
63 | /// A connected TCP socket.
64 | Socket Connect(Func connect, IPAddress host, int port, CancellationToken cancellationToken);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/LICENSE:
--------------------------------------------------------------------------------
1 | using static System.Collections.Specialized.BitVector32;
2 | using System.ComponentModel;
3 |
4 | Apache License
5 | Version 2.0, January 2004
6 | http://www.apache.org/licenses/
7 |
8 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
9 |
10 | 1. Definitions.
11 |
12 | "License" shall mean the terms and conditions for use, reproduction,
13 | and distribution as defined by Sections 1 through 9 of this document.
14 |
15 | "Licensor" shall mean the copyright owner or entity authorized by
16 | the copyright owner that is granting the License.
17 |
18 | "Legal Entity" shall mean the union of the acting entity and all
19 | other entities that control, are controlled by, or are under common
20 | control with that entity. For the purposes of this definition,
21 | "control" means (i) the power, direct or indirect, to cause the
22 | direction or management of such entity, whether by contract or
23 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
24 | outstanding shares, or (iii) beneficial ownership of such entity.
25 |
26 | "You" (or "Your") shall mean an individual or Legal Entity
27 | exercising permissions granted by this License.
28 |
29 | "Source" form shall mean the preferred form for making modifications,
30 | including but not limited to software source code, documentation
31 | source, and configuration files.
32 |
33 | "Object" form shall mean any form resulting from mechanical
34 | transformation or translation of a Source form, including but
35 | not limited to compiled object code, generated documentation,
36 | and conversions to other media types.
37 |
38 | "Work" shall mean the work of authorship, whether in Source or
39 | Object form, made available under the License, as indicated by a
40 | copyright notice that is included in or attached to the work
41 | (an example is provided in the Appendix below).
42 |
43 | "Derivative Works" shall mean any work, whether in Source or Object
44 | form, that is based on (or derived from) the Work and for which the
45 | editorial revisions, annotations, elaborations, or other modifications
46 | represent, as a whole, an original work of authorship. For the purposes
47 | of this License, Derivative Works shall not include works that remain
48 | separable from, or merely link (or bind by name) to the interfaces of,
49 | the Work and Derivative Works thereof.
50 |
51 | "Contribution" shall mean any work of authorship, including
52 | the original version of the Work and any modifications or additions
53 | to that Work or Derivative Works thereof, that is intentionally
54 | submitted to Licensor for inclusion in the Work by the copyright owner
55 | or by an individual or Legal Entity authorized to submit on behalf of
56 | the copyright owner. For the purposes of this definition, "submitted"
57 | means any form of electronic, verbal, or written communication sent
58 | to the Licensor or its representatives, including but not limited to
59 | communication on electronic mailing lists, source code control systems,
60 | and issue tracking systems that are managed by, or on behalf of, the
61 | Licensor for the purpose of discussing and improving the Work, but
62 | excluding communication that is conspicuously marked or otherwise
63 | designated in writing by the copyright owner as "Not a Contribution."
64 |
65 | "Contributor" shall mean Licensor and any individual or Legal Entity
66 | on behalf of whom a Contribution has been received by Licensor and
67 | subsequently incorporated within the Work.
68 |
69 | 2. Grant of Copyright License. Subject to the terms and conditions of
70 | this License, each Contributor hereby grants to You a perpetual,
71 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
72 | copyright license to reproduce, prepare Derivative Works of,
73 | publicly display, publicly perform, sublicense, and distribute the
74 | Work and such Derivative Works in Source or Object form.
75 |
76 | 3. Grant of Patent License. Subject to the terms and conditions of
77 | this License, each Contributor hereby grants to You a perpetual,
78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
79 | (except as stated in this section) patent license to make, have made,
80 | use, offer to sell, sell, import, and otherwise transfer the Work,
81 | where such license applies only to those patent claims licensable
82 | by such Contributor that are necessarily infringed by their
83 | Contribution(s) alone or by combination of their Contribution(s)
84 | with the Work to which such Contribution(s) was submitted. If You
85 | institute patent litigation against any entity (including a
86 | cross-claim or counterclaim in a lawsuit) alleging that the Work
87 | or a Contribution incorporated within the Work constitutes direct
88 | or contributory patent infringement, then any patent licenses
89 | granted to You under this License for that Work shall terminate
90 | as of the date such litigation is filed.
91 |
92 | 4. Redistribution. You may reproduce and distribute copies of the
93 | Work or Derivative Works thereof in any medium, with or without
94 | modifications, and in Source or Object form, provided that You
95 | meet the following conditions:
96 |
97 | (a)You must give any other recipients of the Work or
98 | Derivative Works a copy of this License; and
99 |
100 | (b) You must cause any modified files to carry prominent notices
101 | stating that You changed the files; and
102 |
103 | (c) You must retain, in the Source form of any Derivative Works
104 | that You distribute, all copyright, patent, trademark, and
105 | attribution notices from the Source form of the Work,
106 | excluding those notices that do not pertain to any part of
107 | the Derivative Works; and
108 |
109 | (d) If the Work includes a "NOTICE" text file as part of its
110 | distribution, then any Derivative Works that You distribute must
111 | include a readable copy of the attribution notices contained
112 | within such NOTICE file, excluding those notices that do not
113 | pertain to any part of the Derivative Works, in at least one
114 | of the following places: within a NOTICE text file distributed
115 | as part of the Derivative Works; within the Source form or
116 | documentation, if provided along with the Derivative Works; or,
117 | within a display generated by the Derivative Works, if and
118 | wherever such third-party notices normally appear. The contents
119 | of the NOTICE file are for informational purposes only and
120 | do not modify the License. You may add Your own attribution
121 | notices within Derivative Works that You distribute, alongside
122 | or as an addendum to the NOTICE text from the Work, provided
123 | that such additional attribution notices cannot be construed
124 | as modifying the License.
125 |
126 | You may add Your own copyright statement to Your modifications and
127 | may provide additional or different license terms and conditions
128 | for use, reproduction, or distribution of Your modifications, or
129 | for any such Derivative Works as a whole, provided Your use,
130 | reproduction, and distribution of the Work otherwise complies with
131 | the conditions stated in this License.
132 |
133 | 5. Submission of Contributions. Unless You explicitly state otherwise,
134 | any Contribution intentionally submitted for inclusion in the Work
135 | by You to the Licensor shall be under the terms and conditions of
136 | this License, without any additional terms or conditions.
137 | Notwithstanding the above, nothing herein shall supersede or modify
138 | the terms of any separate license agreement you may have executed
139 | with Licensor regarding such Contributions.
140 |
141 | 6. Trademarks. This License does not grant permission to use the trade
142 | names, trademarks, service marks, or product names of the Licensor,
143 | except as required for reasonable and customary use in describing the
144 | origin of the Work and reproducing the content of the NOTICE file.
145 |
146 | 7. Disclaimer of Warranty. Unless required by applicable law or
147 | agreed to in writing, Licensor provides the Work (and each
148 | Contributor provides its Contributions) on an "AS IS" BASIS,
149 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
150 | implied, including, without limitation, any warranties or conditions
151 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
152 | PARTICULAR PURPOSE. You are solely responsible for determining the
153 | appropriateness of using or redistributing the Work and assume any
154 | risks associated with Your exercise of permissions under this License.
155 |
156 | 8. Limitation of Liability. In no event and under no legal theory,
157 | whether in tort (including negligence), contract, or otherwise,
158 | unless required by applicable law (such as deliberate and grossly
159 | negligent acts) or agreed to in writing, shall any Contributor be
160 | liable to You for damages, including any direct, indirect, special,
161 | incidental, or consequential damages of any character arising as a
162 | result of this License or out of the use or inability to use the
163 | Work (including but not limited to damages for loss of goodwill,
164 | work stoppage, computer failure or malfunction, or any and all
165 | other commercial damages or losses), even if such Contributor
166 | has been advised of the possibility of such damages.
167 |
168 | 9. Accepting Warranty or Additional Liability. While redistributing
169 | the Work or Derivative Works thereof, You may choose to offer,
170 | and charge a fee for, acceptance of support, warranty, indemnity,
171 | or other liability obligations and/or rights consistent with this
172 | License. However, in accepting such obligations, You may act only
173 | on Your own behalf and on Your sole responsibility, not on behalf
174 | of any other Contributor, and only if You agree to indemnify,
175 | defend, and hold each Contributor harmless for any liability
176 | incurred by, or claims asserted against, such Contributor by reason
177 | of your accepting any such warranty or additional liability.
178 |
179 | END OF TERMS AND CONDITIONS
180 |
181 | APPENDIX: How to apply the Apache License to your work.
182 |
183 | To apply the Apache License to your work, attach the following
184 | boilerplate notice, with the fields enclosed by brackets "{}"
185 | replaced with your own identifying information. (Don't include
186 | the brackets!) The text should be enclosed in the appropriate
187 | comment syntax for the file format. We also recommend that a
188 | file or class name and description of purpose be included on the
189 | same "printed page" as the copyright notice for easier
190 | identification within third-party archives.
191 |
192 | Copyright {yyyy} { name of copyright owner}
193 |
194 | Licensed under the Apache License, Version 2.0 (the "License");
195 | you may not use this file except in compliance with the License.
196 | You may obtain a copy of the License at
197 |
198 | http://www.apache.org/licenses/LICENSE-2.0
199 |
200 | Unless required by applicable law or agreed to in writing, software
201 | distributed under the License is distributed on an "AS IS" BASIS,
202 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
203 | See the License for the specific language governing permissions and
204 | limitations under the License.
205 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/README.md:
--------------------------------------------------------------------------------
1 | # Splunk.Logging.Common
2 |
3 | Original Sources
4 | https://github.com/splunk/splunk-library-dotnetlogging
5 |
6 | These files are included here as the original published library does not contain a strong-name key.
7 |
8 | If this ever changes, suggest switching back to the published Nuget package for Splunk.Logging.Common.
9 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/Splunk.Logging/TcpSocketWriter.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Splunk, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"): you may
5 | * not use this file except in compliance with the License. You may obtain
6 | * a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 | * License for the specific language governing permissions and limitations
14 | * under the License.
15 | */
16 | using System;
17 | using System.Net;
18 | using System.Net.Sockets;
19 | using System.Text;
20 | using System.Threading;
21 | using System.Threading.Tasks;
22 |
23 | namespace Splunk.Logging
24 | {
25 | ///
26 | /// TcpSocketWriter encapsulates queueing strings to be written to a TCP socket
27 | /// and handling reconnections (according to a TcpConnectionPolicy object passed
28 | /// to it) when a TCP session drops.
29 | ///
30 | ///
31 | /// TcpSocketWriter maintains a fixed sized queue of strings to be sent via
32 | /// the TCP port and, while the socket is open, sends them as quickly as possible.
33 | ///
34 | /// If the TCP session drops, TcpSocketWriter will stop pulling strings off the
35 | /// queue until it can reestablish a connection. Any SocketErrors emitted during this
36 | /// process will be passed as arguments to invocations of LoggingFailureHandler.
37 | /// If the TcpConnectionPolicy.Connect method throws an exception (in particular,
38 | /// TcpReconnectFailure to indicate that the policy has reached a point where it
39 | /// will no longer try to establish a connection) then the LoggingFailureHandler
40 | /// event is invoked, and no further attempt to log anything will be made.
41 | ///
42 | public class TcpSocketWriter : IDisposable
43 | {
44 | private FixedSizeQueue eventQueue;
45 | private Thread queueListener;
46 | private ITcpReconnectionPolicy reconnectPolicy;
47 | private CancellationTokenSource tokenSource; // Must be private or Dispose will not function properly.
48 | private Func tryOpenSocket;
49 | private TaskCompletionSource disposed = new TaskCompletionSource();
50 |
51 | private Socket socket;
52 | private IPAddress host;
53 | private int port;
54 |
55 | ///
56 | /// Event that is invoked when reconnecting after a TCP session is dropped fails.
57 | ///
58 | public event Action LoggingFailureHandler = (ex) => { };
59 |
60 | ///
61 | /// Construct a TCP socket writer that writes to the given host and port.
62 | ///
63 | /// IPAddress of the host to open a TCP socket to.
64 | /// TCP port to use on the target host.
65 | /// A TcpConnectionPolicy object defining reconnect behavior.
66 | /// The maximum number of log entries to queue before starting to drop entries.
67 | /// An IProgress object that reports when the queue of entries to be written reaches empty or there is
68 | /// a reconnection failure. This is used for testing purposes only.
69 | public TcpSocketWriter(IPAddress host, int port, ITcpReconnectionPolicy policy,
70 | int maxQueueSize, Func connect = null)
71 | {
72 | this.host = host;
73 | this.port = port;
74 | this.reconnectPolicy = policy;
75 | this.eventQueue = new FixedSizeQueue(maxQueueSize);
76 | this.tokenSource = new CancellationTokenSource();
77 |
78 | if (connect == null)
79 | {
80 | this.tryOpenSocket = (h, p) =>
81 | {
82 | try
83 | {
84 | var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
85 | socket.Connect(host, port);
86 | return socket;
87 | }
88 | catch (SocketException e)
89 | {
90 | LoggingFailureHandler(e);
91 | throw;
92 | }
93 | };
94 | }
95 | else
96 | {
97 | this.tryOpenSocket = (h, p) =>
98 | {
99 | try
100 | {
101 | return connect(h, p);
102 | }
103 | catch (SocketException e)
104 | {
105 | LoggingFailureHandler(e);
106 | throw;
107 | }
108 | };
109 | }
110 |
111 | var threadReady = new TaskCompletionSource();
112 |
113 | queueListener = new Thread(() =>
114 | {
115 | try
116 | {
117 | this.socket = this.reconnectPolicy.Connect(tryOpenSocket, host, port, tokenSource.Token);
118 | threadReady.SetResult(true); // Signal the calling thread that we are ready.
119 |
120 | string entry = null;
121 | while (this.socket != null) // null indicates that the thread has been cancelled and cleaned up.
122 | {
123 | if (tokenSource.Token.IsCancellationRequested)
124 | {
125 | eventQueue.CompleteAdding();
126 | // Post-condition: no further items will be added to the queue, so there will be a finite number of items to handle.
127 | while (eventQueue.Count > 0)
128 | {
129 | entry = eventQueue.Dequeue();
130 | try
131 | {
132 | this.socket.Send(Encoding.UTF8.GetBytes(entry));
133 | }
134 | catch (SocketException ex)
135 | {
136 | LoggingFailureHandler(ex);
137 | }
138 | }
139 | break;
140 | }
141 | else if (entry == null)
142 | {
143 | entry = eventQueue.Dequeue(tokenSource.Token);
144 | }
145 | else if (entry != null)
146 | {
147 | try
148 | {
149 | if (this.socket.Send(Encoding.UTF8.GetBytes(entry)) != -1)
150 | {
151 | entry = null;
152 | }
153 | }
154 | catch (SocketException ex)
155 | {
156 | LoggingFailureHandler(ex);
157 | this.socket = this.reconnectPolicy.Connect(tryOpenSocket, host, port, tokenSource.Token);
158 | }
159 | }
160 | }
161 | }
162 | catch (Exception e)
163 | {
164 | LoggingFailureHandler(e);
165 | }
166 | finally
167 | {
168 | if (socket != null)
169 | {
170 | socket.Close();
171 | socket.Dispose();
172 | }
173 |
174 | disposed.SetResult(true);
175 | }
176 | });
177 | queueListener.IsBackground = true; // Prevent the thread from blocking the process from exiting.
178 | queueListener.Start();
179 | threadReady.Task.Wait();
180 | }
181 |
182 | public void Dispose()
183 | {
184 | // The following operations are idempotent. Issue a cancellation to tell the
185 | // writer thread to stop the queue from accepting entries and write what it has
186 | // before cleaning up, then wait until that cleanup is finished.
187 | this.tokenSource.Cancel();
188 | Task.Run(async () => await disposed.Task).Wait();
189 | }
190 |
191 | ///
192 | /// Push a string onto the queue to be written.
193 | ///
194 | /// The string to be written to the TCP socket.
195 | public void Enqueue(string entry)
196 | {
197 | this.eventQueue.Enqueue(entry);
198 | }
199 | }
200 | }
201 |
202 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.TCP/SplunkLoggingConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 Serilog.Configuration;
16 | using Serilog.Events;
17 | using Serilog.Formatting;
18 | using Serilog.Sinks.Splunk;
19 | using System;
20 |
21 | namespace Serilog
22 | {
23 | ///
24 | /// Adds the WriteTo.SplunkViaEventCollector() extension method to .
25 | ///
26 | public static class LoggerConfigurationSplunkExtensions
27 | {
28 | ///
29 | /// Adds a sink that writes log events as to a Splunk instance via TCP.
30 | ///
31 | /// The logger config
32 | ///
33 | /// The minimum log event level required in order to write an event to the sink.
34 | /// Supplies culture-specific formatting information, or null.
35 | /// If true, the message template is rendered
36 | /// Include "RenderedMessage" parameter in output JSON message.
37 | ///
38 | public static LoggerConfiguration SplunkViaTcp(this LoggerSinkConfiguration loggerConfiguration, SplunkTcpSinkConnectionInfo connectionInfo,
39 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, IFormatProvider formatProvider = null, bool renderTemplate = true, bool renderMessage = true)
40 | {
41 | var sink = new TcpSink(connectionInfo, formatProvider, renderTemplate, renderMessage);
42 |
43 | return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
44 | }
45 |
46 | ///
47 | /// Adds a sink that writes log events as to a Splunk instance via TCP.
48 | ///
49 | /// The logger config
50 | ///
51 | /// Custom formatter to use if you e.g. do not want to use the JsonFormatter.
52 | /// The minimum log event level required in order to write an event to the sink.
53 | ///
54 | public static LoggerConfiguration SplunkViaTcp(this LoggerSinkConfiguration loggerConfiguration, SplunkTcpSinkConnectionInfo connectionInfo,
55 | ITextFormatter formatter, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
56 | {
57 | var sink = new TcpSink(connectionInfo, formatter);
58 |
59 | return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.UDP/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo("Serilog.Sinks.Splunk.Tests, PublicKey=" +
5 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" +
6 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" +
7 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" +
8 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" +
9 | "b19485ec")]
--------------------------------------------------------------------------------
/src/Serilog.Sinks.UDP/Serilog.Sinks.Splunk.UDP.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | The Splunk UDP Sink for Serilog
7 | netstandard2.1;netstandard2.0;net8.0;net9.0
8 | true
9 | Serilog.Sinks.Splunk.UDP
10 | Serilog.Sinks.Splunk.UDP
11 | serilog;splunk;logging;udp
12 | true
13 | ../../assets/Serilog.snk
14 | true
15 | true
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.UDP/Sinks/Splunk/SplunkUdpSinkConnectionInfo.cs:
--------------------------------------------------------------------------------
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.Net;
17 |
18 | namespace Serilog.Sinks.Splunk
19 | {
20 | ///
21 | /// Defines connection info used to connect against Splunk
22 | /// using UDP.
23 | ///
24 | public class SplunkUdpSinkConnectionInfo
25 | {
26 | private int _queueSizeLimit;
27 |
28 | ///
29 | /// Splunk host.
30 | ///
31 | public IPAddress Host { get; }
32 |
33 | ///
34 | /// Splunk port.
35 | ///
36 | public int Port { get; }
37 |
38 | ///
39 | /// The maximum number of events to post in a single batch. Defaults to: 100.
40 | ///
41 | public int BatchPostingLimit { get; set; } = 100;
42 |
43 |
44 | ///
45 | /// The time to wait between checking for event batches. Defaults to 10 seconds.
46 | ///
47 | public TimeSpan Period { get; set; } = TimeSpan.FromSeconds(10);
48 |
49 |
50 | ///
51 | /// The maximum number of events that will be held in-memory while waiting to ship them to
52 | /// Splunk. Beyond this limit, events will be dropped. The default is 100,000. Has no effect on
53 | /// durable log shipping.
54 | ///
55 | public int QueueSizeLimit
56 | {
57 | get { return _queueSizeLimit; }
58 | set
59 | {
60 | if (value < 0)
61 | throw new ArgumentOutOfRangeException(nameof(QueueSizeLimit), "Queue size limit must be non-zero.");
62 | _queueSizeLimit = value;
63 | }
64 | }
65 | ///
66 | /// Creates an instance of used
67 | /// for defining connection info for connecting using UDP against Splunk.
68 | ///
69 | /// Splunk host.
70 | /// Splunk UDP port.
71 | public SplunkUdpSinkConnectionInfo(string host, int port) : this(IPAddress.Parse(host), port) { }
72 |
73 | ///
74 | /// Creates an instance of used
75 | /// for defining connection info for connecting using UDP against Splunk.
76 | ///
77 | /// Splunk host.
78 | /// Splunk UDP port.
79 | public SplunkUdpSinkConnectionInfo(IPAddress host, int port)
80 | {
81 | Host = host;
82 | Port = port;
83 | }
84 | }
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.UDP/Sinks/Splunk/UdpSink.cs:
--------------------------------------------------------------------------------
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 Serilog.Core;
16 | using Serilog.Events;
17 | using Serilog.Formatting;
18 | using System;
19 | using System.Collections.Generic;
20 | using System.IO;
21 | using System.Net.Sockets;
22 | using System.Text;
23 | using System.Threading.Tasks;
24 |
25 | namespace Serilog.Sinks.Splunk
26 | {
27 | ///
28 | /// A sink that logs to Splunk over UDP
29 | ///
30 | public class UdpSink : IBatchedLogEventSink
31 | {
32 | private readonly SplunkUdpSinkConnectionInfo _connectionInfo;
33 | private readonly ITextFormatter _formatter;
34 | private Socket _socket;
35 |
36 | ///
37 | /// Creates an instance of the Splunk UDP Sink.
38 | ///
39 | /// Connection info used for connecting against Splunk.
40 | /// Optional format provider
41 | /// If true, the message template will be rendered
42 | /// Include "RenderedMessage" parameter in output JSON message.
43 | public UdpSink(SplunkUdpSinkConnectionInfo connectionInfo, IFormatProvider formatProvider = null, bool renderTemplate = true, bool renderMessage = true)
44 | : this(connectionInfo, CreateDefaultFormatter(formatProvider, renderTemplate, renderMessage))
45 | {
46 | }
47 |
48 | ///
49 | /// Creates an instance of the Splunk UDP Sink.
50 | ///
51 | /// Connection info used for connecting against Splunk.
52 | /// Custom formatter to use if you e.g. do not want to use the JsonFormatter.
53 | public UdpSink(SplunkUdpSinkConnectionInfo connectionInfo, ITextFormatter formatter)
54 | {
55 | _connectionInfo = connectionInfo;
56 | _formatter = formatter;
57 | Connect();
58 | }
59 |
60 |
61 | private byte[] Convert(LogEvent logEvent)
62 | {
63 | var sb = new StringBuilder();
64 | using (var sw = new StringWriter(sb))
65 | _formatter.Format(logEvent, sw);
66 | return Encoding.UTF8.GetBytes(sb.ToString());
67 | }
68 |
69 | private void Connect()
70 | {
71 | _socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
72 | _socket.Connect(_connectionInfo.Host, _connectionInfo.Port);
73 | }
74 |
75 |
76 | protected void Dispose(bool disposing)
77 | {
78 | DisposeSocket();
79 | }
80 |
81 | private void DisposeSocket()
82 | {
83 | _socket?.Close();
84 | _socket?.Dispose();
85 | _socket = null;
86 | }
87 |
88 | ///
89 | /// Emit a batch of log events, running to completion synchronously.
90 | ///
91 | /// The events to emit.
92 | ///
93 | /// Override either
94 | /// or ,
95 | /// not both.
96 | ///
97 | public async Task EmitBatchAsync(IReadOnlyCollection batch)
98 | {
99 | foreach (var logEvent in batch)
100 | {
101 | byte[] data = Convert(logEvent);
102 |
103 | try
104 | {
105 | _socket.Send(data);
106 | }
107 | catch (SocketException)
108 | {
109 | // Try to reconnect and log
110 | DisposeSocket();
111 | Connect();
112 | _socket.Send(data);
113 | }
114 | }
115 | }
116 |
117 |
118 | private static SplunkJsonFormatter CreateDefaultFormatter(IFormatProvider formatProvider, bool renderTemplate, bool renderMessage)
119 | {
120 | return new SplunkJsonFormatter(renderTemplate, renderMessage, formatProvider);
121 | }
122 |
123 | ///
124 | public Task OnEmptyBatchAsync() => Task.CompletedTask;
125 | }
126 | }
127 |
128 |
--------------------------------------------------------------------------------
/src/Serilog.Sinks.UDP/SplunkLoggingConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 Serilog.Configuration;
16 | using Serilog.Events;
17 | using Serilog.Formatting;
18 | using Serilog.Sinks.Splunk;
19 | using System;
20 |
21 | namespace Serilog
22 | {
23 | ///
24 | /// Adds the WriteTo.SplunkViaEventCollector() extension method to .
25 | ///
26 | public static class LoggerConfigurationSplunkExtensions
27 | {
28 | ///
29 | /// Adds a sink that writes log events as to a Splunk instance via UDP.
30 | ///
31 | /// The logger config
32 | ///
33 | /// The minimum log event level required in order to write an event to the sink.
34 | /// Supplies culture-specific formatting information, or null.
35 | /// If true, the message template is rendered
36 | /// Include "RenderedMessage" parameter in output JSON message.
37 | ///
38 | public static LoggerConfiguration SplunkViaUdp(
39 | this LoggerSinkConfiguration loggerConfiguration,
40 | SplunkUdpSinkConnectionInfo connectionInfo,
41 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
42 | IFormatProvider formatProvider = null,
43 | bool renderTemplate = true,
44 | bool renderMessage = true)
45 | {
46 | var batchingOptions = new BatchingOptions
47 | {
48 | BatchSizeLimit = connectionInfo.BatchPostingLimit,
49 | BufferingTimeLimit = connectionInfo.Period,
50 | EagerlyEmitFirstEvent = true,
51 | QueueLimit = connectionInfo.QueueSizeLimit
52 | };
53 |
54 | var sink = new UdpSink(connectionInfo, formatProvider, renderTemplate, renderMessage);
55 |
56 | return loggerConfiguration.Sink(sink, batchingOptions, restrictedToMinimumLevel);
57 | }
58 |
59 | ///
60 | /// Adds a sink that writes log events as to a Splunk instance via UDP.
61 | ///
62 | /// The logger config
63 | ///
64 | /// Custom formatter to use if you e.g. do not want to use the JsonFormatter.
65 | /// The minimum log event level required in order to write an event to the sink.
66 | ///
67 | public static LoggerConfiguration SplunkViaUdp(
68 | this LoggerSinkConfiguration loggerConfiguration,
69 | SplunkUdpSinkConnectionInfo connectionInfo,
70 | ITextFormatter formatter,
71 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
72 | {
73 | var batchingOptions = new BatchingOptions
74 | {
75 | BatchSizeLimit = connectionInfo.BatchPostingLimit,
76 | BufferingTimeLimit = connectionInfo.Period,
77 | EagerlyEmitFirstEvent = true,
78 | QueueLimit = connectionInfo.QueueSizeLimit
79 | };
80 |
81 | var sink = new UdpSink(connectionInfo, formatter);
82 |
83 | return loggerConfiguration.Sink(sink, batchingOptions, restrictedToMinimumLevel);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/common.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 5.1.1
5 | debug
6 | Matthew Erbs, Serilog Contributors
7 | true
8 | https://github.com/serilog/serilog-sinks-splunk
9 | https://github.com/serilog/serilog-sinks-splunk
10 | git
11 | latest
12 | Apache-2.0
13 | README.md
14 | serilog-sink-nuget.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.TCP.Tests.Disabled/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "test": {
4 | "commandName": "test"
5 | },
6 | "test-dnxcore50": {
7 | "commandName": "test",
8 | "sdkVersion": "dnx-coreclr-win-x86.1.0.0-rc1-final"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.TCP.Tests.Disabled/Serilog.Sinks.Splunk.TCP.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net462;net48
5 | Serilog.Sinks.Splunk.Tests
6 | true
7 | false
8 | latest
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 | all
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 | all
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.TCP.Tests.Disabled/TCPCollectorTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using Serilog.Events;
5 | using Xunit;
6 |
7 | namespace Serilog.Sinks.Splunk.TCP.Tests;
8 |
9 | public class TCPCollectorTests
10 | {
11 | [Fact]
12 | public void LoggerExtensionTest()
13 | {
14 | using (var TestEnvironment = new TestEnvironment())
15 | {
16 | var log = new LoggerConfiguration()
17 | .WriteTo.SplunkViaTcp(
18 | new Serilog.Sinks.Splunk.SplunkTcpSinkConnectionInfo("127.0.0.1", 10001),
19 | restrictedToMinimumLevel: LevelAlias.Minimum,
20 | formatProvider: null,
21 | renderTemplate: true)
22 | .CreateLogger();
23 |
24 | log.Information("Hello World");
25 | }
26 | }
27 |
28 | }
29 |
30 | class TestEnvironment : IDisposable
31 | {
32 | TcpListener TcpServer;
33 |
34 | readonly public int TcpServerAddress;
35 |
36 | public TestEnvironment(int TcpServerAddress = 10001)
37 | {
38 | this.TcpServerAddress = TcpServerAddress;
39 |
40 | TcpServer = new TcpListener(IPAddress.Loopback, TcpServerAddress);
41 |
42 | TcpServer.Start();
43 | }
44 |
45 | public void Dispose()
46 | {
47 | TcpServer.Stop();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/CompactSplunkJsonFormatterTests.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using Serilog.Sinks.Splunk.Tests.Support;
3 | using System;
4 | using System.IO;
5 | using Xunit;
6 |
7 | namespace Serilog.Sinks.Splunk.Tests
8 | {
9 | public class CompactSplunkJsonFormatterTests
10 | {
11 | private void AssertValidJson(Action act,
12 | string source = "",
13 | string sourceType = "",
14 | string host = "",
15 | string index = "")
16 | {
17 | StringWriter outputRendered = new StringWriter(), output = new StringWriter();
18 | var log = new LoggerConfiguration()
19 | .WriteTo.Sink(new TextWriterSink(output, new CompactSplunkJsonFormatter(false, source, sourceType, host, index)))
20 | .WriteTo.Sink(new TextWriterSink(outputRendered, new CompactSplunkJsonFormatter(true, source, sourceType, host, index)))
21 | .CreateLogger();
22 |
23 | act(log);
24 |
25 | // Unfortunately this will not detect all JSON formatting issues; better than nothing however.
26 | JObject.Parse(output.ToString());
27 | JObject.Parse(outputRendered.ToString());
28 | }
29 |
30 | [Fact]
31 | public void AnEmptyEventIsValidJson()
32 | {
33 | AssertValidJson(log => log.Information("No properties"));
34 | }
35 |
36 | [Fact]
37 | public void AMinimalEventIsValidJson()
38 | {
39 | AssertValidJson(log => log.Information("One {Property}", 42));
40 | }
41 |
42 | [Fact]
43 | public void MultiplePropertiesAreDelimited()
44 | {
45 | AssertValidJson(log => log.Information("Property {First} and {Second}", "One", "Two"));
46 | }
47 |
48 | [Fact]
49 | public void ExceptionsAreFormattedToValidJson()
50 | {
51 | AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception"));
52 | }
53 |
54 | [Fact]
55 | public void ExceptionAndPropertiesAreValidJson()
56 | {
57 | AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception and {Property}", 42));
58 | }
59 |
60 | [Fact]
61 | public void AMinimalEventWithSourceIsValidJson()
62 | {
63 | AssertValidJson(log => log.Information("One {Property}", 42), source: "A Test Source");
64 | }
65 |
66 | [Fact]
67 | public void AMinimalEventWithSourceTypeIsValidJson()
68 | {
69 | AssertValidJson(log => log.Information("One {Property}", 42), sourceType: "A Test SourceType");
70 | }
71 |
72 | [Fact]
73 | public void AMinimalEventWithHostIsValidJson()
74 | {
75 | AssertValidJson(log => log.Information("One {Property}", 42), host: "A Test Host");
76 | }
77 |
78 | [Fact]
79 | public void AMinimalEventWithIndexIsValidJson()
80 | {
81 | AssertValidJson(log => log.Information("One {Property}", 42), host: "testindex");
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/EpochExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Xunit;
3 |
4 | namespace Serilog.Sinks.Splunk.Tests
5 | {
6 | public class EpochExtensionsTests
7 | {
8 | [Theory]
9 | [InlineData(1)]
10 | [InlineData(2)]
11 | [InlineData(3)]
12 | [InlineData(4)]
13 | [InlineData(5)]
14 | [InlineData(6)]
15 | [InlineData(7)]
16 | [InlineData(8)]
17 | [InlineData(9)]
18 | [InlineData(10)]
19 | [InlineData(11)]
20 | [InlineData(12)]
21 | [InlineData(13)]
22 | [InlineData(14)]
23 | [InlineData(-1)]
24 | [InlineData(-2)]
25 | [InlineData(-3)]
26 | [InlineData(-4)]
27 | [InlineData(-5)]
28 | [InlineData(-6)]
29 | [InlineData(-7)]
30 | [InlineData(-7.5)]
31 | [InlineData(-8)]
32 | [InlineData(-9)]
33 | [InlineData(-10)]
34 | [InlineData(-11)]
35 | public void ToEpochLocalTime_ShouldReturnCorrectEpochTime(float timeZoneOffset)
36 | {
37 | // Arrange
38 | var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.FromHours(timeZoneOffset)).AddHours(timeZoneOffset);
39 | var expectedEpochTime = "1640995200.000"; // Epoch time for 2022-01-01 00:00:00
40 |
41 | // Act
42 | var result = dateTimeOffset.ToEpoch();
43 |
44 | // Assert
45 | Assert.Equal(expectedEpochTime, result);
46 | }
47 |
48 |
49 | [Fact]
50 | public void ToEpoch_ShouldReturnCorrectEpochTime()
51 | {
52 | // Arrange
53 | var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, TimeSpan.Zero);
54 | var expectedEpochTime = "1640995200.000"; // Epoch time for 2022-01-01 00:00:00
55 |
56 | // Act
57 | var result = dateTimeOffset.ToEpoch();
58 |
59 | // Assert
60 | Assert.Equal(expectedEpochTime, result);
61 | }
62 |
63 | [Fact]
64 | public void ToEpoch_ShouldReturnCorrectEpochTime_Milliseconds()
65 | {
66 | // Arrange
67 | var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero);
68 | var expectedEpochTime = "1640995200.123"; // Epoch time for 2022-01-01 00:00:00.123
69 |
70 | // Act
71 | var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Milliseconds);
72 |
73 | // Assert
74 | Assert.Equal(expectedEpochTime, result);
75 | }
76 |
77 | [Fact]
78 | public void ToEpoch_ShouldReturnCorrectEpochTime_Microseconds()
79 | {
80 | // Arrange
81 | var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero) + TimeSpan.FromMicroseconds(456);
82 | var expectedEpochTime = "1640995200.123456"; // Epoch time for 2022-01-01 00:00:00.123
83 |
84 | // Act
85 | var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Microseconds);
86 |
87 | // Assert
88 | Assert.Equal(expectedEpochTime, result);
89 | }
90 |
91 | [Fact]
92 | public void ToEpoch_ShouldReturnCorrectEpochTime_Nanoseconds()
93 | {
94 | // Arrange
95 | // using from ticks here, NanoSeconds is not available in TimeSpan. Nanoseconds Per Tick = 100L.
96 | var dateTimeOffset = new DateTimeOffset(2022, 1, 1, 0, 0, 0, 123, TimeSpan.Zero) + TimeSpan.FromMicroseconds(456) + TimeSpan.FromTicks(7);
97 | var expectedEpochTime = "1640995200.123456700"; // Epoch time for 2022-01-01 00:00:00.123
98 |
99 | // Act
100 | var result = dateTimeOffset.ToEpoch(SubSecondPrecision.Nanoseconds);
101 |
102 | // Assert
103 | Assert.Equal(expectedEpochTime, result);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "test": {
4 | "commandName": "test"
5 | },
6 | "test-dnxcore50": {
7 | "commandName": "test",
8 | "sdkVersion": "dnx-coreclr-win-x86.1.0.0-rc1-final"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/Serilog.Sinks.Splunk.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net9.0
5 | Serilog.Sinks.Splunk.Tests
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 | all
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 | all
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/SplunkJsonFormatterTests.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using Serilog.Events;
4 | using Serilog.Parsing;
5 | using Serilog.Sinks.Splunk.Tests.Support;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Globalization;
9 | using System.IO;
10 | using Xunit;
11 | namespace Serilog.Sinks.Splunk.Tests
12 | {
13 |
14 | public class SplunkJsonFormatterTests
15 | {
16 | void AssertValidJson(Action act,
17 | string source = "",
18 | string sourceType = "",
19 | string host = "",
20 | string index = "",
21 | CustomFields fields = null)
22 | {
23 | StringWriter outputRendered = new StringWriter(), output = new StringWriter();
24 | var log = new LoggerConfiguration()
25 | .WriteTo.Sink(new TextWriterSink(output, new SplunkJsonFormatter(false, true, null, source, sourceType, host, index)))
26 | .WriteTo.Sink(new TextWriterSink(outputRendered, new SplunkJsonFormatter(true, true, null, source, sourceType, host, index)))
27 | .CreateLogger();
28 |
29 | act(log);
30 |
31 | // Unfortunately this will not detect all JSON formatting issues; better than nothing however.
32 | JObject.Parse(output.ToString());
33 | JObject.Parse(outputRendered.ToString());
34 | }
35 |
36 | [Fact]
37 | public void AnEmptyEventIsValidJson()
38 | {
39 | AssertValidJson(log => log.Information("No properties"));
40 | }
41 |
42 | [Fact]
43 | public void AMinimalEventIsValidJson()
44 | {
45 | AssertValidJson(log => log.Information("One {Property}", 42));
46 | }
47 |
48 | [Fact]
49 | public void MultiplePropertiesAreDelimited()
50 | {
51 | AssertValidJson(log => log.Information("Property {First} and {Second}", "One", "Two"));
52 | }
53 |
54 | [Fact]
55 | public void ExceptionsAreFormattedToValidJson()
56 | {
57 | AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception"));
58 | }
59 |
60 | [Fact]
61 | public void ExceptionAndPropertiesAreValidJson()
62 | {
63 | AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception and {Property}", 42));
64 | }
65 |
66 | [Fact]
67 | public void AMinimalEventWithSourceIsValidJson()
68 | {
69 | AssertValidJson(log => log.Information("One {Property}", 42), source: "A Test Source");
70 | }
71 |
72 | [Fact]
73 | public void AMinimalEventWithSourceTypeIsValidJson()
74 | {
75 | AssertValidJson(log => log.Information("One {Property}", 42), sourceType: "A Test SourceType");
76 | }
77 |
78 | [Fact]
79 | public void AMinimalEventWithHostIsValidJson()
80 | {
81 | AssertValidJson(log => log.Information("One {Property}", 42), host: "A Test Host");
82 | }
83 |
84 | [Fact]
85 | public void AMinimalEventWithIndexIsValidJson()
86 | {
87 | AssertValidJson(log => log.Information("One {Property}", 42), host: "testindex");
88 | }
89 | [Fact]
90 | public void EventWithCustomFields()
91 | {
92 | var metaData = new CustomFields(new List
93 | {
94 | new CustomField("relChan", "Test"),
95 | new CustomField("version", "17.8.9.beta"),
96 | new CustomField("rel", "REL1706"),
97 | new CustomField("role", new List() { "service", "rest", "ESB" })
98 | });
99 | AssertValidJson(log => log.Information("One {Property}", 42), fields: metaData);
100 | }
101 |
102 | [Fact]
103 | public void Test_CustomFields_Jsonformatter_for_Splunk_Sink()
104 | {
105 | //Arrange
106 | int a = 1;
107 | int b = 0;
108 | // Here we set up some made up CustomFields that we want splunk to index for every event so we could filter on them
109 | // Eg could be like in this example releasechannel eg Dev,Test,AccepteanceTest, prod ; version of the code, Release,
110 | // role is an example of when a field property has been set to a multi-value JSON array. See: http://dev.splunk.com/view/event-collector/SP-CAAAFB6
111 | // Could be used to describe a hierachy in dimension. Here it is a service of type rest for the Enterprise Service Bus
112 | // these field would of course be different for every Company. But should be conformed over the organisation. Just like Ralph Kimball conformed dimensions for BI.
113 | var metaData = new CustomFields(new List
114 | {
115 | new CustomField("relChan", "Test"),
116 | new CustomField("version", "17.8.9.beta"),
117 | new CustomField("rel", "REL1706"),
118 | new CustomField("role", new List() { "Backend", "Service", "Rest" })
119 | });
120 |
121 | TextWriter eventtTextWriter = new StringWriter();
122 | Exception testException = null;
123 | var timeStamp = DateTimeOffset.Now;
124 | // var timeStampUnix = (Math.Round(timeStamp.ToUnixTimeMilliseconds() / 1000.0, 3)).ToString("##.###", CultureInfo.InvariantCulture); //req dotnet 4.6.2
125 | var timeStampUnix = ((Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds).ToString("##.###", CultureInfo.InvariantCulture);
126 | var sut = new SplunkJsonFormatter(renderTemplate: true, renderMessage: true, formatProvider: null, source: "BackPackTestServerChannel", sourceType: "_json", host: "Wanda", index: "Main", customFields: metaData);
127 | try
128 | {
129 | var willnotwork = a / b;
130 | }
131 | catch (Exception e)
132 | {
133 | testException = e;
134 | }
135 | var logEvent = new LogEvent(timestamp: timeStamp, level: LogEventLevel.Debug, exception: testException
136 | , messageTemplate: new MessageTemplate("Should be an div by zero error", new List())
137 | , properties: new LogEventProperty[]
138 | {
139 | new LogEventProperty("Serilog.Sinks.Splunk.Sample", new ScalarValue("ViaEventCollector"))
140 | , new LogEventProperty("Serilog.Sinks.Splunk.Sample.TestType", new ScalarValue("AddCustomFields"))
141 | }
142 | );
143 | //Act
144 | sut.Format(logEvent, eventtTextWriter);
145 | var resultJson = eventtTextWriter.ToString();
146 | TestEventResultObject testEventResult = JsonConvert.DeserializeObject(resultJson);
147 | //Assert
148 | Assert.True(String.Equals(testEventResult.Host, "Wanda"), "Wanda should be my host see the movie, else the JsonFormater is wack in test"); // "Wanda should be my host see the movie, else the JsonFormater is wack in test"); // I do no seem to get the div part right. Something strange with round or how the json timestamp is calculated. I am practical and only check the whole part for now.
149 | var timestampPartFromTest = testEventResult.Time.Split('.')[0];
150 | var timeShouldBe = timeStampUnix.Split('.')[0];
151 | Assert.True(String.Equals(timestampPartFromTest, timeShouldBe), "Json Time stamp is off ");
152 | Assert.True(String.Equals(testEventResult.Source, "BackPackTestServerChannel"), "Jsonformater do not se the Splunk field source to the right value");
153 | Assert.True(String.Equals(testEventResult.Sourcetype, "_json"), "Jsonformater do not se the Splunk field sourcetype to the right value _json");
154 | Assert.True(testEventResult.@Event.Exception.StartsWith("System.DivideByZeroException:"), "Exception Does not seem to be right after JsonFormater no DivideByZeroText ");
155 | // StringAssert.IsMatch("AddCustomFields", testEventResult.@event.Exception, "Exception Does not seem to be right after JsonFormater no AddCustomField ");
156 | Assert.True(String.Equals(testEventResult.@Event.Level, LogEventLevel.Debug.ToString()), "Siri LogEvent should be Debug");
157 | //Now finally we start to check the CustomField
158 | Assert.True(String.Equals(testEventResult.Fields.RelChan, "Test"), "CustomField RelChan is not correct after format for Splunkjsonformatter");
159 | Assert.True(String.Equals(testEventResult.Fields.Version, "17.8.9.beta"), "CustomField Version is not correct after format for Splunkjsonformatter");
160 | Assert.True(String.Equals(testEventResult.Fields.Rel, "REL1706"), "CustomField rel is not correct after format for Splunkjsonformatter");
161 | Assert.True(String.Equals(testEventResult.Fields.Role[0], "Backend"), "CustomField Role array 0 is not correct after format for Splunkjsonformatter");
162 | Assert.True(String.Equals(testEventResult.Fields.Role[1], "Service"), "CustomField Role array 1 correct after format for Splunkjsonformatter");
163 | Assert.True(String.Equals(testEventResult.Fields.Role[2], "Rest"), "CustomField Role array 2 correct after format for Splunkjsonformatter");
164 | }
165 | #region Test_CustomFields_Jsonformatter_for_Splunk_Sink_Help_Classes
166 | // http://json2csharp.com/#
167 | // https://github.com/JamesNK/Newtonsoft.Json
168 | public class Event
169 | {
170 | public string Level { get; set; }
171 | public string MessageTemplate { get; set; }
172 | public string RenderedMessage { get; set; }
173 | public string Exception { get; set; }
174 | }
175 | public class TestCustomFields
176 | {
177 | public string RelChan { get; set; }
178 | public string Version { get; set; }
179 | public string Rel { get; set; }
180 | public List Role { get; set; }
181 | }
182 | public class TestEventResultObject
183 | {
184 | public string Time { get; set; }
185 | public Event @Event { get; set; }
186 | public string Source { get; set; }
187 | public string Sourcetype { get; set; }
188 | public string Host { get; set; }
189 | public string Index { get; set; }
190 | public TestCustomFields Fields { get; set; }
191 |
192 | }
193 | #endregion
194 | }
195 | }
--------------------------------------------------------------------------------
/test/Serilog.Sinks.Splunk.Tests/Support/TextWriterSink.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Serilog.Core;
3 | using Serilog.Events;
4 | using Serilog.Formatting;
5 |
6 | namespace Serilog.Sinks.Splunk.Tests.Support
7 | {
8 | public class TextWriterSink : ILogEventSink
9 | {
10 | readonly StringWriter _output;
11 | readonly ITextFormatter _formatter;
12 |
13 | public TextWriterSink(StringWriter output, ITextFormatter formatter)
14 | {
15 | _output = output;
16 | _formatter = formatter;
17 | }
18 |
19 | public void Emit(LogEvent logEvent)
20 | {
21 | _formatter.Format(logEvent, _output);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------