├── .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 | ![Build Status](https://github.com/serilog-contrib/serilog-sinks-splunk/actions/workflows/ci.yml/badge.svg?branch=dev) 4 | [![NuGet Version](https://buildstats.info/nuget/Serilog.Sinks.Splunk)](https://www.nuget.org/packages/Serilog.Sinks.Splunk) 5 | [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](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 | [![Package Logo](https://serilog.net/images/serilog-sink-nuget.png)](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 | } --------------------------------------------------------------------------------