├── .editorconfig ├── .gitignore ├── LICENSE ├── NuGet.Config ├── README.md ├── appveyor.yml ├── artifacts └── Serilog.Sinks.Graylog.2.1.0.snupkg ├── codecov.yml ├── deploy.yml └── src ├── Serilog.Sinks.Graylog.Batching ├── BatchingGraylogSinkOptions.cs ├── LoggerConfigurationGrayLogExtensions.cs ├── PeriodicBatchingGraylogSink.cs ├── Serilog.Sinks.Graylog.Batching.csproj ├── Serilog.Sinks.Graylog.Batching.csproj.user └── sign.snk ├── Serilog.Sinks.Graylog.Core.Tests ├── Extensions │ └── StringExtensionsFixture.cs ├── GelfConverterFixture.cs ├── Helpers │ └── MessageIdGeneratorFixture.cs ├── MessageBuilders │ ├── ExceptionMessageBuilderFixture.cs │ └── MessageBuilderFixture.cs ├── Serilog.Sinks.Graylog.Core.Tests.csproj └── Transport │ ├── DnsResolver.cs │ ├── Http │ ├── HttpTransportClientFixture.cs │ └── HttpTransportFixture.cs │ └── Udp │ ├── DataToChunkConverterFixture.cs │ ├── UdpTransportClientFixture.cs │ └── UdpTransportFixture.cs ├── Serilog.Sinks.Graylog.Core ├── Extensions │ ├── DateTimeExtensions.cs │ ├── StringExtensions.cs │ └── TypeExtensions.cs ├── GelfConverter.cs ├── GraylogSinkOptions.cs ├── Helpers │ ├── HttpBasicAuthenticationGenerator.cs │ ├── LogLevelMapper.cs │ └── MessageIdGenerator.cs ├── MessageBuilders │ ├── ExceptionMessageBuilder.cs │ ├── GelfMessageBuilder.cs │ └── IMessageBuilder.cs ├── Serilog.Sinks.Graylog.Core.csproj ├── Transport │ ├── DnsResolver.cs │ ├── Http │ │ ├── HttpTransport.cs │ │ └── HttpTransportClient.cs │ ├── ITransport.cs │ ├── ITransportClient'.cs │ ├── Tcp │ │ ├── TcpTransport.cs │ │ └── TcpTransportClient.cs │ ├── TransportType.cs │ └── Udp │ │ ├── ChunkSettings.cs │ │ ├── DataToChunkConverter.cs │ │ ├── UdpTransport.cs │ │ └── UdpTransportClient.cs ├── TransportFactory.cs └── key.snk ├── Serilog.Sinks.Graylog.Tests ├── Configurations │ └── AppSettingsWithGraylogSinkContainingHostProperty.json ├── GraylogSinkFixture.cs ├── IntegrateSinkTestWithHttp.cs ├── IntegrateSinkTestWithTcp.cs ├── IntegrateSinkTestWithUdp.cs ├── LogEventSource.cs ├── LoggerConfigurationGrayLogExtensionsFixture.cs ├── Serilog.Sinks.Graylog.Tests.csproj ├── SerilogExceptionsFixture.cs ├── TestProfile │ └── Profile.cs └── altcover.xml ├── Serilog.Sinks.Graylog ├── GraylogSink.cs ├── GraylogSinkOptions.cs ├── LoggerConfigurationGrayLogExtensions.cs ├── Serilog.Sinks.Graylog.csproj ├── Serilog.Sinks.GraylogSingleTarget.csproj ├── Serilog.Sinks.GraylogSingleTarget.csproj.user └── sign.snk ├── TestApplication ├── CustomException.cs ├── Program.cs ├── TestApplication.csproj └── appsettings.json ├── serilog-sink-nuget.png └── serilog-sinks-graylog.sln /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | charset = utf-8 9 | end_of_line = lf 10 | 11 | [*.{csproj,json,config,yml,props}] 12 | indent_size = 2 13 | 14 | [*.sh] 15 | end_of_line = lf 16 | 17 | [*.{cmd, bat}] 18 | end_of_line = crlf 19 | 20 | # C# formatting settings - Namespace options 21 | csharp_style_namespace_declarations = file_scoped:suggestion 22 | 23 | csharp_style_prefer_switch_expression = true:suggestion 24 | 25 | # C# formatting settings - Spacing options 26 | csharp_space_after_cast = false 27 | csharp_space_after_keywords_in_control_flow_statements = true 28 | csharp_space_between_parentheses = false 29 | csharp_space_before_colon_in_inheritance_clause = true 30 | csharp_space_after_colon_in_inheritance_clause = true 31 | csharp_space_around_binary_operators = before_and_after 32 | csharp_space_between_method_declaration_parameter_list_parentheses = false 33 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 34 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 35 | csharp_space_between_method_call_parameter_list_parentheses = false 36 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 37 | csharp_space_between_method_call_name_and_opening_parenthesis = false 38 | csharp_space_after_comma = true 39 | csharp_space_before_comma = false 40 | csharp_space_after_dot = false 41 | csharp_space_before_dot = false 42 | csharp_space_after_semicolon_in_for_statement = true 43 | csharp_space_before_semicolon_in_for_statement = false 44 | csharp_space_around_declaration_statements = false 45 | csharp_space_before_open_square_brackets = false 46 | csharp_space_between_empty_square_brackets = false 47 | csharp_space_between_square_brackets = false 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.pdb 3 | *.suo 4 | *.ncrunchproject 5 | **/packages/** 6 | **/bin/** 7 | **/obj/** 8 | *.cache 9 | *.coverage 10 | *.dg 11 | */_ReSharper.Caches 12 | *.nupkg 13 | *ncrunch* 14 | *.ide 15 | src/.vs/* 16 | *.ide-wal 17 | src/.vs/serilog-sinks-graylogvs2017/v15/sqlite3/db.lock 18 | *.ide-shm 19 | src/.vs/serilog-sinks-graylogvs2017/v15/Server/sqlite3/db.lock 20 | src/.vs/serilog-sinks-graylogvs2017/DesignTimeBuild/.dtbcache 21 | /.vs/slnx.sqlite 22 | .ionide/symbolCache.db 23 | src/.idea/ 24 | *.DotSettings -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Anton Volkov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Status 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/yqi7f32mxrwtnrvv/branch/master?svg=true)](https://ci.appveyor.com/project/whir1/serilog-sinks-graylog-j9flc/branch/master) 4 | 5 | ### serilog-sinks-graylog 6 | 7 | [![NuGet](https://img.shields.io/nuget/v/serilog.sinks.graylog.svg)](https://www.nuget.org/packages/serilog.sinks.graylog/) 8 | [![Downloads](https://img.shields.io/nuget/dt/serilog.sinks.graylog.svg)](https://www.nuget.org/packages/serilog.sinks.graylog/) 9 | 10 | ### serilog-sinks-graylog-Batching 11 | 12 | [![NuGet](https://img.shields.io/nuget/v/serilog.sinks.graylog.batching.svg)](https://www.nuget.org/packages/Serilog.Sinks.Graylog.Batching/) 13 | [![Downloads](https://img.shields.io/nuget/dt/serilog.sinks.graylog.batching.svg)](https://www.nuget.org/packages/Serilog.Sinks.Graylog.Batching/) 14 | 15 | ## What is this sink ? 16 | The Serilog Graylog sink project is a sink (basically a writer) for the Serilog logging framework. Structured log events are written to sinks and each sink is responsible for writing it to its own backend, database, store etc. This sink delivers the data to Graylog2, a NoSQL search engine. 17 | 18 | ## Quick start 19 | 20 | ```powershell 21 | Install-Package serilog.sinks.graylog 22 | ``` 23 | Register the sink in code. 24 | ```csharp 25 | var loggerConfig = new LoggerConfiguration() 26 | .WriteTo.Graylog(new GraylogSinkOptions 27 | { 28 | HostnameOrAddress = "localhost", 29 | Port = 12201 30 | }); 31 | ``` 32 | ...or alternatively configure the sink in appsettings.json configuration like so: 33 | 34 | ```json 35 | { 36 | "Serilog": { 37 | "Using": ["Serilog.Sinks.Graylog"], 38 | "MinimumLevel": "Debug", 39 | "WriteTo": [ 40 | { 41 | "Name": "Graylog", 42 | "Args": { 43 | "hostnameOrAddress": "localhost", 44 | "port": "12201", 45 | "transportType": "Udp" 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | Note that because of the limitations of the Serilog.Settings.Configuration package, you cannot configure IGelfConverter using json. 54 | 55 | by default udp protocol is using, if you want to use http define sink options like 56 | 57 | ```csharp 58 | new GraylogSinkOptions 59 | { 60 | HostnameOrAddress = "http://localhost", 61 | Port = 12201, 62 | TransportType = TransportType.Http, 63 | } 64 | ``` 65 | 66 | All options you can see at https://github.com/whir1/serilog-sinks-graylog/blob/master/src/Serilog.Sinks.Graylog.Core/GraylogSinkOptions.cs 67 | 68 | You can create your own implementation of transports or converter and set it to options. But maybe i'll delete this feature in the future 69 | 70 | PS this is my first package XD. 71 | 72 | PPS I am sorry for my language, but my second language is C# 73 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | - 2 | branches: 3 | except: 4 | - master 5 | init: 6 | - cmd: "set branch=%APPVEYOR_REPO_BRANCH%" 7 | - cmd: "echo branch:%branch%" 8 | - cmd: "set newVersion=1.0.%APPVEYOR_BUILD_NUMBER%-%branch%" 9 | - cmd: "echo newVersion:%newVersion%" 10 | - cmd: "set versonSuffix=%APPVEYOR_BUILD_NUMBER%-%branch%" 11 | - cmd: "echo versonSuffix:%versonSuffix%" 12 | - cmd: appveyor UpdateBuild -Version "%newVersion%" 13 | 14 | skip_tags: true 15 | 16 | before_build: 17 | - nuget install OpenCover -ExcludeVersion -OutputDirectory "packages" 18 | - choco install opencover.portable 19 | - choco install codecov 20 | - cmd: dotnet tool install --global altcover.global 21 | 22 | configuration: Debug 23 | 24 | image: Visual Studio 2022 25 | 26 | build_script: 27 | - cmd: dotnet restore -v n ./src/serilog-sinks-graylog.sln 28 | - cmd: dotnet build -v n -c Debug ./src/serilog-sinks-graylog.sln 29 | - cmd: dotnet pack -v n --include-symbols --include-source -o "./artifacts" --version-suffix Beta -p:SymbolPackageFormat=snupkg ./src/Serilog.Sinks.Graylog/Serilog.Sinks.Graylog.csproj 30 | - cmd: dotnet pack -v n --include-symbols --include-source -o "./artifacts" --version-suffix Beta -p:SymbolPackageFormat=snupkg ./src/Serilog.Sinks.Graylog.Batching/Serilog.Sinks.Graylog.Batching.csproj 31 | 32 | test: 33 | categories: 34 | except: 35 | - Integration 36 | 37 | test_script: 38 | - dotnet test ./src/Serilog.Sinks.Graylog.Core.Tests --filter Category!=Integration /p:AltCover=true 39 | - dotnet test ./src/Serilog.Sinks.Graylog.Tests --filter Category!=Integration /p:AltCover=true 40 | 41 | after_test: 42 | - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" 43 | - pip install codecov 44 | - codecov -f "./src/Serilog.Sinks.Graylog.Core.Tests/coverage.xml" -t cd3f1ab1-60c6-4848-824b-466b93321d96 45 | - codecov -f "./src/Serilog.Sinks.Graylog.Tests/coverage.xml" -t cd3f1ab1-60c6-4848-824b-466b93321d96 46 | 47 | artifacts: 48 | - path: "./artifacts/*.*" 49 | - 50 | branches: 51 | only: 52 | - master 53 | 54 | configuration: Release 55 | 56 | image: Visual Studio 2022 57 | 58 | build: 59 | publish_nuget_symbols: true 60 | use_snupkg_format: true 61 | 62 | build_script: 63 | - cmd: dotnet restore -v n ./src/serilog-sinks-graylog.sln 64 | - cmd: dotnet build -v n -c Release ./src/serilog-sinks-graylog.sln 65 | - cmd: dotnet pack -v n --include-symbols --include-source -o "./artifacts" -p:SymbolPackageFormat=snupkg ./src/Serilog.Sinks.Graylog/Serilog.Sinks.Graylog.csproj 66 | - cmd: dotnet pack -v n --include-symbols --include-source -o "./artifacts" -p:SymbolPackageFormat=snupkg ./src/Serilog.Sinks.Graylog.Batching/Serilog.Sinks.Graylog.Batching.csproj 67 | test: 68 | categories: 69 | except: 70 | - Integration 71 | 72 | artifacts: 73 | - path: "./artifacts/*.*" 74 | 75 | deploy: 76 | provider: NuGet 77 | api_key: 78 | secure: J4E+ROQN+2v19RntNXpjllr1nAy/Q0tEW69Cav2ru+sYhMsvNpjOjPPd6ZoMmtf8 79 | skip_symbols: false 80 | artifact: /.*.* 81 | -------------------------------------------------------------------------------- /artifacts/Serilog.Sinks.Graylog.2.1.0.snupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/artifacts/Serilog.Sinks.Graylog.2.1.0.snupkg -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: 50...100 8 | 9 | ignore: 10 | - "**/*Tests/**" 11 | -------------------------------------------------------------------------------- /deploy.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/deploy.yml -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/BatchingGraylogSinkOptions.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core; 2 | using Serilog.Sinks.PeriodicBatching; 3 | using System; 4 | 5 | namespace Serilog.Sinks.Graylog.Batching 6 | { 7 | public class BatchingGraylogSinkOptions : GraylogSinkOptionsBase 8 | { 9 | public BatchingGraylogSinkOptions() 10 | { 11 | PeriodicOptions = new PeriodicBatchingSinkOptions() 12 | { 13 | BatchSizeLimit = 10, 14 | Period = TimeSpan.FromSeconds(1), 15 | QueueLimit = 10, 16 | }; 17 | } 18 | 19 | public PeriodicBatchingSinkOptions PeriodicOptions { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/LoggerConfigurationGrayLogExtensions.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | using Serilog.Events; 3 | using Serilog.Sinks.Graylog.Core; 4 | using Serilog.Sinks.Graylog.Core.Helpers; 5 | using Serilog.Sinks.Graylog.Core.Transport; 6 | using Serilog.Sinks.PeriodicBatching; 7 | using System; 8 | 9 | namespace Serilog.Sinks.Graylog.Batching 10 | { 11 | public static class LoggerConfigurationGrayLogExtensions 12 | { 13 | /// The logger sink configuration. 14 | /// The options. 15 | public static LoggerConfiguration Graylog(this LoggerSinkConfiguration loggerSinkConfiguration, 16 | BatchingGraylogSinkOptions options) 17 | { 18 | var sink = new PeriodicBatchingGraylogSink(options); 19 | 20 | var batchingSink = new PeriodicBatchingSink(sink, options.PeriodicOptions); 21 | 22 | return loggerSinkConfiguration.Sink(batchingSink, options.MinimumLogEventLevel); 23 | } 24 | 25 | /// The logger sink configuration. 26 | /// The hostname or address. 27 | /// The port. 28 | /// Type of the transport. 29 | /// The minimum log event level. 30 | /// Type of the message identifier generator. 31 | /// Short length of the message maximum. 32 | /// The stack trace depth. 33 | /// The facility. 34 | /// The batch size limit 35 | /// The period limit default is one second 36 | /// queue limit 37 | /// the maxMessageSizeInUdp 38 | /// if set to true if include message template to graylog. 39 | /// Name of the message template field. 40 | public static LoggerConfiguration Graylog(this LoggerSinkConfiguration loggerSinkConfiguration, 41 | string hostnameOrAddress, 42 | int port, 43 | TransportType transportType, 44 | LogEventLevel minimumLogEventLevel = LevelAlias.Minimum, 45 | MessageIdGeneratorType messageIdGeneratorType = GraylogSinkOptionsBase.DefaultMessageGeneratorType, 46 | int shortMessageMaxLength = GraylogSinkOptionsBase.DefaultShortMessageMaxLength, 47 | int stackTraceDepth = GraylogSinkOptionsBase.DefaultStackTraceDepth, 48 | string facility = GraylogSinkOptionsBase.DefaultFacility, 49 | int maxMessageSizeInUdp = GraylogSinkOptionsBase.DefaultMaxMessageSizeInUdp, 50 | int batchSizeLimit = 10, 51 | TimeSpan period = default, 52 | int queueLimit = 1000, 53 | bool includeMessageTemplate = false, 54 | string messageTemplateFieldName = GraylogSinkOptionsBase.DefaultMessageTemplateFieldName 55 | ) 56 | { 57 | if (period == default) 58 | { 59 | period = TimeSpan.FromSeconds(1); 60 | } 61 | 62 | // ReSharper disable once UseObjectOrCollectionInitializer 63 | var options = new BatchingGraylogSinkOptions 64 | { 65 | HostnameOrAddress = hostnameOrAddress, 66 | Port = port, 67 | TransportType = transportType, 68 | MinimumLogEventLevel = minimumLogEventLevel, 69 | MessageGeneratorType = messageIdGeneratorType, 70 | ShortMessageMaxLength = shortMessageMaxLength, 71 | StackTraceDepth = stackTraceDepth, 72 | Facility = facility, 73 | PeriodicOptions = new PeriodicBatchingSinkOptions() 74 | { 75 | BatchSizeLimit = batchSizeLimit, 76 | Period = period, 77 | QueueLimit = queueLimit, 78 | }, 79 | MaxMessageSizeInUdp = maxMessageSizeInUdp, 80 | IncludeMessageTemplate = includeMessageTemplate, 81 | MessageTemplateFieldName = messageTemplateFieldName 82 | }; 83 | return loggerSinkConfiguration.Graylog(options); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/PeriodicBatchingGraylogSink.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Debugging; 2 | using Serilog.Events; 3 | using Serilog.Sinks.Graylog.Core; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using Serilog.Sinks.PeriodicBatching; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text.Json.Nodes; 10 | using System.Threading.Tasks; 11 | 12 | namespace Serilog.Sinks.Graylog.Batching 13 | { 14 | public class PeriodicBatchingGraylogSink : IBatchedLogEventSink 15 | { 16 | private readonly Lazy _transport; 17 | private readonly Lazy _converter; 18 | 19 | public PeriodicBatchingGraylogSink(BatchingGraylogSinkOptions options) 20 | { 21 | ISinkComponentsBuilder sinkComponentsBuilder = new SinkComponentsBuilder(options); 22 | _transport = new Lazy(sinkComponentsBuilder.MakeTransport); 23 | _converter = new Lazy(sinkComponentsBuilder.MakeGelfConverter); 24 | } 25 | 26 | public Task OnEmptyBatchAsync() 27 | { 28 | return Task.CompletedTask; 29 | } 30 | 31 | Task IBatchedLogEventSink.EmitBatchAsync(IEnumerable batch) 32 | { 33 | try 34 | { 35 | IEnumerable sendTasks = batch.Select(async logEvent => 36 | { 37 | JsonObject json = _converter.Value.GetGelfJson(logEvent); 38 | await _transport.Value.Send(json.ToString()).ConfigureAwait(false); 39 | }); 40 | 41 | return Task.WhenAll(sendTasks); 42 | } catch (Exception exc) 43 | { 44 | SelfLog.WriteLine("Oops something going wrong {0}", exc); 45 | return Task.CompletedTask; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/Serilog.Sinks.Graylog.Batching.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11.0 5 | 6 | 7 | 8 | net7.0;net6.0;netstandard2.0 9 | 10 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage 11 | 12 | true 13 | full 14 | enable 15 | true 16 | 17 | Serilog.Sinks.Graylog.Batching 18 | Serilog.Sinks.Graylog.Batching 19 | Anton Volkov 20 | Batching version of Serilog.Sinks.Graylog Sink. The Serilog Graylog Sink project is a sink (basically a writer) for the Serilog logging framework. Structured log events are written to sinks and each sink is responsible for writing it to its own backend, database, store etc. This sink delivers the data to Graylog2, a NoSQL search engine. 21 | 22 | https://github.com/whir1/serilog-sinks-graylog 23 | https://github.com/whir1/serilog-sinks-graylog 24 | serilog-sink-nuget.png 25 | 26 | Anton Volkov Copyright © 2023 27 | 28 | http://serilog.net/images/serilog-sink-nuget.png 29 | Serilog Sink Graylog Batching 30 | en 31 | git 32 | true 33 | 3.0.3 34 | sign.snk 35 | 3.0.0.0 36 | 3.0.0.0 37 | MIT 38 | Update package dependencies 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/Serilog.Sinks.Graylog.Batching.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Batching/sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/src/Serilog.Sinks.Graylog.Batching/sign.snk -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Extensions/StringExtensionsFixture.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Serilog.Sinks.Graylog.Core.Extensions; 3 | using Xunit; 4 | 5 | namespace Serilog.Sinks.Graylog.Core.Tests.Extensions 6 | { 7 | public class StringExtensionsFixture 8 | { 9 | [Fact] 10 | public void WhenCompressMessage_ThenResultShoouldBeExpected() 11 | { 12 | var giwen = "Some string"; 13 | var expected = new byte[] 14 | { 15 | 31,139,8,0,0,0,0,0,0,10,11,206,207,77,85,40,46,41,202,204,75,7,0,142,183,209,127,11,0,0,0 16 | }; 17 | 18 | byte[] actual = giwen.ToGzip(); 19 | actual.Should().BeEquivalentTo(expected); 20 | } 21 | 22 | [Theory] 23 | [InlineData("SomeTestString", "Some", 4)] 24 | [InlineData("SomeTestString", "SomeTest", 8)] 25 | [InlineData("SomeTestString", "SomeTestString", 200)] 26 | public void WhenShortMessage_ThenResultShouldBeExpected(string given, string expected, int length) 27 | { 28 | var actual = given.Truncate(length); 29 | 30 | actual.Should().BeEquivalentTo(expected); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/GelfConverterFixture.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Serilog.Sinks.Graylog.Core.MessageBuilders; 3 | using Serilog.Sinks.Graylog.Tests; 4 | using System; 5 | using System.Collections.Generic; 6 | using Xunit; 7 | 8 | namespace Serilog.Sinks.Graylog.Core.Tests 9 | { 10 | public class GelfConverterFixture 11 | { 12 | [Fact] 13 | public void WhenLogEvent_ThenMessageBuilderShouldBeCalled() 14 | { 15 | var errorBuilder = new Mock(); 16 | var messageBuilder = new Mock(); 17 | 18 | var messageBuilders = new Dictionary> 19 | { 20 | [BuilderType.Exception] = new Lazy(() => errorBuilder.Object), 21 | [BuilderType.Message] = new Lazy(() => messageBuilder.Object) 22 | }; 23 | 24 | GelfConverter target = new(messageBuilders); 25 | 26 | var simpleEvent = LogEventSource.GetSimpleLogEvent(DateTimeOffset.Now); 27 | 28 | target.GetGelfJson(simpleEvent); 29 | 30 | errorBuilder.Verify(c => c.Build(simpleEvent), Times.Never); 31 | messageBuilder.Verify(c => c.Build(simpleEvent), Times.Once); 32 | } 33 | 34 | [Fact] 35 | public void WhenLogErrorEvent_ThenErrorMessageBuilderShouldBeCalled() 36 | { 37 | var errorBuilder = new Mock(); 38 | var messageBuilder = new Mock(); 39 | 40 | var messageBuilders = new Dictionary> 41 | { 42 | [BuilderType.Exception] = new Lazy(() => errorBuilder.Object), 43 | [BuilderType.Message] = new Lazy(() => messageBuilder.Object) 44 | }; 45 | 46 | GelfConverter target = new(messageBuilders); 47 | 48 | var simpleEvent = LogEventSource.GetErrorEvent(DateTimeOffset.Now); 49 | 50 | target.GetGelfJson(simpleEvent); 51 | 52 | errorBuilder.Verify(c => c.Build(simpleEvent), Times.Once); 53 | messageBuilder.Verify(c => c.Build(simpleEvent), Times.Never); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Helpers/MessageIdGeneratorFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using FluentAssertions; 3 | using Serilog.Sinks.Graylog.Core.Helpers; 4 | using System; 5 | using System.Linq; 6 | using System.Security.Cryptography; 7 | using Xunit; 8 | 9 | namespace Serilog.Sinks.Graylog.Core.Tests.Helpers 10 | { 11 | public class MessageIdGeneratorFixture 12 | { 13 | private readonly Fixture _fixture; 14 | 15 | public MessageIdGeneratorFixture() 16 | { 17 | _fixture = new Fixture(); 18 | } 19 | 20 | [Fact] 21 | public void WhenGenerateFromTimeStamp_ThenReturnsExpectedResult() 22 | { 23 | DateTime time = DateTime.UtcNow; 24 | byte[] given = _fixture.CreateMany(10).ToArray(); 25 | var target = new TimestampMessageIdGenerator(); 26 | 27 | byte[] actual = target.GenerateMessageId(given); 28 | 29 | var actticks = BitConverter.ToInt64(actual, 0); 30 | var actdate = DateTime.FromBinary(actticks); 31 | 32 | actdate.Should().BeCloseTo(time, TimeSpan.FromMilliseconds(200)); 33 | } 34 | 35 | [Fact] 36 | public void WhenGenerateTimestampFromMd5_ThenReturnsExpected() 37 | { 38 | byte[] given = _fixture.CreateMany(10).ToArray(); 39 | 40 | var target = new Md5MessageIdGenerator(); 41 | 42 | MD5 md5 = MD5.Create(); 43 | var expected = md5.ComputeHash(given).Take(8).ToArray(); 44 | 45 | var actual = target.GenerateMessageId(given); 46 | 47 | actual.Should().BeEquivalentTo(expected); 48 | } 49 | 50 | [Fact] 51 | public void WhenResolveMd5Generator_ThenResult_ShouldBeAsExpectedType() 52 | { 53 | var resolver = new MessageIdGeneratorResolver(); 54 | 55 | IMessageIdGenerator actual = resolver.Resolve(MessageIdGeneratorType.Md5); 56 | 57 | Assert.IsType(actual); 58 | } 59 | 60 | [Fact] 61 | public void WhenResolveTimeStampGenerator_ThenResult_ShouldBeAsExpectedType() 62 | { 63 | var resolver = new MessageIdGeneratorResolver(); 64 | 65 | IMessageIdGenerator actual = resolver.Resolve(MessageIdGeneratorType.Timestamp); 66 | 67 | Assert.IsType(actual); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/MessageBuilders/ExceptionMessageBuilderFixture.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Sinks.Graylog.Core.MessageBuilders; 3 | using Serilog.Sinks.Graylog.Tests; 4 | using System; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.Graylog.Core.Tests.MessageBuilders 8 | { 9 | public class ExceptionMessageBuilderFixture 10 | { 11 | [Fact] 12 | public void WhenCreateException_ThenBuildShoulNotThrow() 13 | { 14 | var options = new GraylogSinkOptions(); 15 | 16 | ExceptionMessageBuilder exceptionBuilder = new("localhost", options); 17 | 18 | Exception testExc = null; 19 | 20 | try 21 | { 22 | try 23 | { 24 | throw new InvalidOperationException("Level One exception"); 25 | } catch (Exception exc) 26 | { 27 | throw new NotImplementedException("Nested Exception", exc); 28 | } 29 | } catch (Exception exc) 30 | { 31 | testExc = exc; 32 | } 33 | 34 | 35 | DateTimeOffset date = DateTimeOffset.Now; 36 | LogEvent logEvent = LogEventSource.GetExceptionLogEvent(date, testExc); 37 | 38 | //JObject obj = exceptionBuilder.Build(logEvent); 39 | 40 | //obj.Should().NotBeNull(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/MessageBuilders/MessageBuilderFixture.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Parsing; 3 | using Serilog.Sinks.Graylog.Core.MessageBuilders; 4 | using Serilog.Sinks.Graylog.Tests; 5 | using System; 6 | using System.Collections.Generic; 7 | using Xunit; 8 | 9 | namespace Serilog.Sinks.Graylog.Core.Tests.MessageBuilders 10 | { 11 | public class GelfMessageBuilderFixture 12 | { 13 | [Fact] 14 | [Trait("Category", "Debug")] 15 | public void TryComplexEvent() 16 | { 17 | var options = new GraylogSinkOptions(); 18 | var target = new GelfMessageBuilder("localhost", options); 19 | 20 | DateTimeOffset date = DateTimeOffset.Now; 21 | 22 | LogEvent logEvent = LogEventSource.GetComplexEvent(date); 23 | 24 | //string actual = target.Build(logEvent).ToString(Newtonsoft.Json.Formatting.None); 25 | } 26 | 27 | [Fact] 28 | public void GetSimpleLogEvent_GraylogSinkOptionsContainsHost_ReturnsOptionsHost() 29 | { 30 | //arrange 31 | GraylogSinkOptions options = new() 32 | { 33 | HostnameOverride = "my_host" 34 | }; 35 | GelfMessageBuilder messageBuilder = new("localhost", options); 36 | DateTime date = DateTime.UtcNow; 37 | string expectedHost = "my_host"; 38 | 39 | //act 40 | LogEvent logEvent = LogEventSource.GetSimpleLogEvent(date); 41 | var actual = messageBuilder.Build(logEvent); 42 | string actualHost = actual["host"].AsValue().ToString(); 43 | 44 | //assert 45 | Assert.Equal(expectedHost, actualHost); 46 | } 47 | 48 | 49 | [Fact] 50 | public static void WhenTryCreateLogEventWithNullKeyOrValue_ThenThrow() 51 | { 52 | //If in future this test fail then should add check for null in GelfMessageBuilder 53 | Assert.Throws(() => 54 | { 55 | var logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, 56 | new MessageTemplate("abcdef{TestProp}", new List 57 | { 58 | new TextToken("abcdef", 0), 59 | new PropertyToken("TestProp", "zxc", alignment: new Alignment(AlignmentDirection.Left, 3)) 60 | 61 | }), new List 62 | { 63 | new("TestProp", new ScalarValue("zxc")), 64 | new("id", new ScalarValue("asd")), 65 | new("Oo", null), 66 | new(null, null), 67 | new("StructuredProperty", 68 | new StructureValue(new List 69 | { 70 | new("id", new ScalarValue(1)), 71 | new("_TestProp", new ScalarValue(3)), 72 | }, "TypeTag")) 73 | }); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Serilog.Sinks.Graylog.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0;net6.0 5 | full 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/DnsResolver.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core.Transport; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport 7 | { 8 | public class DnsWrapperFixture 9 | { 10 | [Fact] 11 | public async Task Test() 12 | { 13 | var traget = new DnsWrapper(); 14 | 15 | IPAddress[] actual = await traget.GetHostAddresses("github.com"); 16 | 17 | Assert.NotEmpty(actual); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/Http/HttpTransportClientFixture.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Serilog.Debugging; 3 | using Serilog.Sinks.Graylog.Core.Transport.Http; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport.Http 8 | { 9 | public class HttpTransportClientFixture 10 | { 11 | [Fact(Skip = "This test not work anymore because logs.aeroclub.int not exists")] 12 | [Trait("Category", "Integration")] 13 | public async Task WhenSendJson_ThenResultShouldNotThrows() 14 | { 15 | var target = new HttpTransportClient(new GraylogSinkOptions 16 | { 17 | HostnameOrAddress = "http://logs.aeroclub.int:12201/gelf" 18 | }); 19 | 20 | await target.Send("{\"facility\":\"VolkovTestFacility\",\"full_message\":\"SomeComplexTestEntry TestClass { Id: 1, TestPropertyOne: \\\"1\\\", Bar: Bar { Id: 2, Prop: \\\"123\\\" }, TestPropertyTwo: \\\"2\\\", TestPropertyThree: \\\"3\\\" }\",\"host\":\"N68-MSK\",\"level\":6,\"short_message\":\"SomeComplexTestEntry TestClass { Id: 1, TestProper\",\"timestamp\":\"2017-03-24T11:18:54.1850651\",\"version\":\"1.1\",\"_stringLevel\":\"Information\",\"_test.Id\":1,\"_test.TestPropertyOne\":\"1\",\"_test.Bar.Id\":2,\"_test.Bar.Prop\":\"123\",\"_test.TestPropertyTwo\":\"2\",\"_test.TestPropertyThree\":\"3\"}"); 21 | } 22 | 23 | [Fact(Skip = "This test not work anymore because logs.aeroclub.int not exists")] 24 | [Trait("Category", "Integration")] 25 | public async Task WhenSendJson_ThenResultShouldThrowException() 26 | { 27 | var target = new HttpTransportClient(new GraylogSinkOptions 28 | { 29 | HostnameOrAddress = "http://logs.aeroclub.int:12201" 30 | }); 31 | 32 | LoggingFailedException exception = await Assert.ThrowsAsync(() => target.Send("{\"facility\":\"VolkovTestFacility\",\"full_message\":\"SomeComplexTestEntry TestClass { Id: 1, TestPropertyOne: \\\"1\\\", Bar: Bar { Id: 2, Prop: \\\"123\\\" }, TestPropertyTwo: \\\"2\\\", TestPropertyThree: \\\"3\\\" }\",\"host\":\"N68-MSK\",\"level\":6,\"short_message\":\"SomeComplexTestEntry TestClass { Id: 1, TestProper\",\"timestamp\":\"2017-03-24T11:18:54.1850651\",\"version\":\"1.1\",\"_stringLevel\":\"Information\",\"_test.Id\":1,\"_test.TestPropertyOne\":\"1\",\"_test.Bar.Id\":2,\"_test.Bar.Prop\":\"123\",\"_test.TestPropertyTwo\":\"2\",\"_test.TestPropertyThree\":\"3\"}")); 33 | 34 | exception.Message.Should().Be("Unable send log message to graylog via HTTP transport"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/Http/HttpTransportFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Moq; 3 | using Serilog.Sinks.Graylog.Core.Transport; 4 | using Serilog.Sinks.Graylog.Core.Transport.Http; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport.Http 9 | { 10 | public class HttpTransportFixture 11 | { 12 | private readonly Fixture _fixture; 13 | 14 | public HttpTransportFixture() 15 | { 16 | _fixture = new Fixture(); 17 | } 18 | 19 | [Fact] 20 | public async Task WhenCallSend_ThenCallSendWithoutAnyChanges() 21 | { 22 | var transportClient = new Mock>(); 23 | 24 | var target = new HttpTransport(transportClient.Object); 25 | 26 | var payload = _fixture.Create(); 27 | 28 | await target.Send(payload); 29 | 30 | transportClient.Verify(c => c.Send(payload), Times.Once); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/Udp/DataToChunkConverterFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using FluentAssertions; 3 | using Moq; 4 | using Serilog.Sinks.Graylog.Core.Helpers; 5 | using Serilog.Sinks.Graylog.Core.Transport.Udp; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using Xunit; 10 | 11 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport.Udp 12 | { 13 | public class DataToChunkConverterFixture 14 | { 15 | private readonly ChunkSettings _settings; 16 | private readonly Fixture _fixture; 17 | private readonly Mock _resolver; 18 | 19 | public DataToChunkConverterFixture() 20 | { 21 | _settings = new ChunkSettings(MessageIdGeneratorType.Md5, 8192); 22 | _fixture = new Fixture(); 23 | _resolver = new Mock(); 24 | } 25 | 26 | [Fact] 27 | public void WhenConvertToChunkWithSmallData_ThenReturnsOneChunk() 28 | { 29 | var target = new DataToChunkConverter(_settings, _resolver.Object); 30 | 31 | byte[] giwenData = _fixture.CreateMany(1000).ToArray(); 32 | IList actual = target.ConvertToChunks(giwenData); 33 | 34 | var expected = new List() 35 | { 36 | giwenData 37 | }; 38 | 39 | actual.Should().BeEquivalentTo(expected); 40 | } 41 | 42 | [Fact] 43 | public void WhenChunksWasTooMany_ThenThrowsException() 44 | { 45 | byte[] giwenData = new byte[10000000]; 46 | 47 | var target = new DataToChunkConverter(_settings, _resolver.Object); 48 | 49 | Assert.Throws(() => target.ConvertToChunks(giwenData)); 50 | } 51 | 52 | [Fact] 53 | public void WhenMessageIsLong_ThenSplitItToChunks() 54 | { 55 | byte[] giwenData = new byte[100000]; 56 | 57 | var idGenerator = new Mock(); 58 | 59 | var messageId = _fixture.CreateMany(8).ToArray(); 60 | 61 | idGenerator.Setup(c => c.GenerateMessageId(giwenData)).Returns(messageId); 62 | 63 | _resolver.Setup(c => c.Resolve(_settings.MessageIdGeneratorType)) 64 | .Returns(idGenerator.Object); 65 | 66 | var target = new DataToChunkConverter(_settings, _resolver.Object); 67 | 68 | var actual = target.ConvertToChunks(giwenData); 69 | 70 | 71 | Assert.True(actual.Count == 13); 72 | 73 | for (int i = 0; i < actual.Count; i++) 74 | { 75 | actual[i].Take(2).ToArray().Should().BeEquivalentTo(new[] { 0x1e, 0x0f }); 76 | actual[i].Skip(2).Take(8).ToArray().Should().BeEquivalentTo(messageId); 77 | actual[i].Skip(10).Take(1).First().Should().Be((byte)i); 78 | actual[i].Skip(11).Take(1).First().Should().Be(13); 79 | Assert.True(actual[i].Skip(12).All(c => c == 0)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/Udp/UdpTransportClientFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Serilog.Sinks.Graylog.Core.Transport.Udp; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport.Udp 8 | { 9 | using Core.Transport; 10 | 11 | public class UdpTransportClientFixture 12 | { 13 | [Fact] 14 | public async Task TrySendSomeData() 15 | { 16 | var fixture = new Fixture(); 17 | var bytes = fixture.CreateMany(128); 18 | 19 | var client = new UdpTransportClient(new GraylogSinkOptions 20 | { 21 | HostnameOrAddress = "127.0.0.1", 22 | Port = 3128 23 | }, new DnsWrapper()); 24 | 25 | await client.Send(bytes.ToArray()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core.Tests/Transport/Udp/UdpTransportFixture.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Moq; 3 | using Serilog.Sinks.Graylog.Core.Extensions; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using Serilog.Sinks.Graylog.Core.Transport.Udp; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using Xunit; 9 | 10 | namespace Serilog.Sinks.Graylog.Core.Tests.Transport.Udp 11 | { 12 | public class UdpTransportFixture 13 | { 14 | [Fact] 15 | public void WhenSend_ThenCallMethods() 16 | { 17 | var transportClient = new Mock>(); 18 | var dataToChunkConverter = new Mock(); 19 | var options = new GraylogSinkOptions(); 20 | 21 | var fixture = new Fixture(); 22 | 23 | var stringData = fixture.Create(); 24 | 25 | byte[] data = stringData.ToGzip(); 26 | 27 | List chunks = fixture.CreateMany(3).ToList(); 28 | 29 | dataToChunkConverter.Setup(c => c.ConvertToChunks(data)).Returns(chunks); 30 | 31 | UdpTransport target = new(transportClient.Object, dataToChunkConverter.Object, options); 32 | 33 | target.Send(stringData); 34 | 35 | dataToChunkConverter.Verify(c => c.ConvertToChunks(data), Times.Once); 36 | 37 | foreach (byte[] chunk in chunks) 38 | { 39 | transportClient.Verify(c => c.Send(chunk), Times.Once); 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Sinks.Graylog.Core.Extensions 4 | { 5 | public static class DateTimeExtensions 6 | { 7 | private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 8 | 9 | /// 10 | /// Converts to nix date time. 11 | /// 12 | /// 13 | /// 14 | public static double ConvertToNix(this DateTimeOffset dateTimeOffset) 15 | { 16 | var duration = dateTimeOffset.ToUniversalTime() - Epoch; 17 | 18 | return Math.Round(duration.TotalSeconds, 3, MidpointRounding.AwayFromZero); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Text; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.Extensions 7 | { 8 | public static class StringExtensions 9 | { 10 | public static byte[] ToGzip(this string source) 11 | { 12 | var resultStream = new MemoryStream(); 13 | 14 | using (var gzipStream = new GZipStream(resultStream, CompressionMode.Compress)) 15 | { 16 | byte[] messageBytes = ToByteArray(source); 17 | 18 | gzipStream.Write(messageBytes, 0, messageBytes.Length); 19 | } 20 | 21 | return resultStream.ToArray(); 22 | } 23 | 24 | public static byte[] ToByteArray(this string source) => Encoding.UTF8.GetBytes(source); 25 | 26 | /// 27 | /// Truncates the specified maximum length. 28 | /// 29 | /// The source. 30 | /// The maximum length. 31 | /// 32 | public static string Truncate(this string source, int maxLength) 33 | { 34 | return source.Length > maxLength ? source.Substring(0, maxLength) : source; 35 | } 36 | 37 | public static string Expand(this string source) 38 | { 39 | return Environment.ExpandEnvironmentVariables(source); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Serilog.Sinks.Graylog.Core.Extensions 4 | { 5 | /// 6 | /// Some type extensions 7 | /// 8 | public static class TypeExtensions 9 | { 10 | /// 11 | /// Determines whether [is numeric type]. 12 | /// 13 | /// The type. 14 | /// 15 | /// true if [is numeric type] [the specified type]; otherwise, false. 16 | /// 17 | public static bool IsNumericType(this Type type) 18 | { 19 | return Type.GetTypeCode(type) switch 20 | { 21 | TypeCode.Byte or 22 | TypeCode.SByte or 23 | TypeCode.UInt16 or 24 | TypeCode.UInt32 or 25 | TypeCode.UInt64 or 26 | TypeCode.Int16 or 27 | TypeCode.Int32 or 28 | TypeCode.Int64 or 29 | TypeCode.Decimal or 30 | TypeCode.Double or 31 | TypeCode.Single => true, 32 | _ => false, 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/GelfConverter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Sinks.Graylog.Core.MessageBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text.Json.Nodes; 6 | 7 | namespace Serilog.Sinks.Graylog.Core 8 | { 9 | public interface IGelfConverter 10 | { 11 | JsonObject GetGelfJson(LogEvent logEvent); 12 | } 13 | 14 | public class GelfConverter : IGelfConverter 15 | { 16 | private readonly IDictionary> _messageBuilders; 17 | 18 | public GelfConverter(IDictionary> messageBuilders) 19 | { 20 | _messageBuilders = messageBuilders; 21 | } 22 | 23 | public JsonObject GetGelfJson(LogEvent logEvent) 24 | { 25 | IMessageBuilder builder = logEvent.Exception != null 26 | ? _messageBuilders[BuilderType.Exception].Value 27 | : _messageBuilders[BuilderType.Message].Value; 28 | 29 | return builder.Build(logEvent); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/GraylogSinkOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Events; 3 | using Serilog.Sinks.Graylog.Core.Helpers; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using System.Text.Json; 6 | // ReSharper disable InconsistentNaming 7 | // ReSharper disable MemberCanBePrivate.Global 8 | // ReSharper disable PublicConstructorInAbstractClass 9 | // ReSharper disable UnusedAutoPropertyAccessor.Global 10 | // ReSharper disable PropertyCanBeMadeInitOnly.Global 11 | namespace Serilog.Sinks.Graylog.Core 12 | { 13 | 14 | /// 15 | /// Sync options for graylog 16 | /// 17 | public abstract class GraylogSinkOptionsBase 18 | { 19 | public const string DefaultFacility = null!; 20 | public const int DefaultShortMessageMaxLength = 500; 21 | public const LogEventLevel DefaultMinimumLogEventLevel = LevelAlias.Minimum; 22 | public const int DefaultStackTraceDepth = 10; 23 | public const MessageIdGeneratorType DefaultMessageGeneratorType = MessageIdGeneratorType.Timestamp; 24 | 25 | public const int DefaultMaxMessageSizeInUdp = 8192; 26 | /// 27 | /// The default option value (null) for GELF's "host" property. DNS hostname will be used instead. 28 | /// 29 | public const string DefaultHost = null!; 30 | 31 | public const int DefaultPort = 12201; 32 | 33 | public const string DefaultMessageTemplateFieldName = "message_template"; 34 | 35 | // ReSharper disable once MemberCanBePrivate.Global 36 | public static readonly JsonSerializerOptions DefaultSerializerSettings = new() 37 | { 38 | WriteIndented = false, 39 | }; 40 | 41 | public GraylogSinkOptionsBase() 42 | { 43 | MessageGeneratorType = MessageIdGeneratorType.Timestamp; 44 | ShortMessageMaxLength = DefaultShortMessageMaxLength; 45 | MinimumLogEventLevel = DefaultMinimumLogEventLevel; 46 | Facility = DefaultFacility; 47 | StackTraceDepth = DefaultStackTraceDepth; 48 | MaxMessageSizeInUdp = DefaultMaxMessageSizeInUdp; 49 | HostnameOverride = DefaultHost; 50 | IncludeMessageTemplate = false; 51 | ExcludeMessageTemplateProperties = false; 52 | MessageTemplateFieldName = DefaultMessageTemplateFieldName; 53 | JsonSerializerOptions = DefaultSerializerSettings; 54 | ParseArrayValues = false; 55 | //use gzip by default 56 | UseGzip = true; 57 | Port = DefaultPort; 58 | } 59 | 60 | /// 61 | /// Should parse values in arrays 62 | /// 63 | public bool ParseArrayValues { get; set; } 64 | 65 | /// 66 | /// Gets or sets the name of the message template field. 67 | /// 68 | /// 69 | /// The name of the message template field. 70 | /// 71 | public string MessageTemplateFieldName { get; set; } 72 | 73 | /// 74 | /// Gets or sets a value indicating whether include message template to graylog. 75 | /// 76 | /// 77 | /// true if include message template; otherwise, false. 78 | /// 79 | /// 80 | public bool IncludeMessageTemplate { get; set; } 81 | 82 | /// 83 | /// Exclude message template properties. 84 | /// 85 | /// 86 | /// true if exclude message template properties; otherwise, false. 87 | /// 88 | public bool ExcludeMessageTemplateProperties { get; set; } 89 | 90 | /// 91 | /// Gets or sets the minimum log event level. 92 | /// 93 | /// 94 | /// The minimum log event level. 95 | /// 96 | public LogEventLevel MinimumLogEventLevel { get; set; } 97 | 98 | /// 99 | /// Gets or sets the hostname or address of graylog server. 100 | /// 101 | /// 102 | /// The hostname or address. 103 | /// 104 | public string? HostnameOrAddress { get; set; } 105 | 106 | /// 107 | /// Gets or sets the facility name. 108 | /// 109 | /// 110 | /// The facility. 111 | /// 112 | public string? Facility { get; set; } 113 | 114 | /// 115 | /// Gets or sets the server port. 116 | /// 117 | /// 118 | /// The port. 119 | /// 120 | public int? Port { get; set; } 121 | 122 | /// 123 | /// Gets or sets the transport. 124 | /// 125 | /// 126 | /// The transport. 127 | /// 128 | /// 129 | /// You can implement another one or use default udp transport 130 | /// 131 | public TransportType TransportType { get; set; } 132 | 133 | /// 134 | /// Gets or sets the gelf converter. 135 | /// 136 | /// 137 | /// The gelf converter. 138 | /// 139 | /// 140 | /// You can implement another one for customize fields or use default 141 | /// 142 | public IGelfConverter? GelfConverter { get; set; } 143 | 144 | /// 145 | /// Gets or sets the maximal length of the ShortMessage 146 | /// 147 | /// 148 | /// ShortMessage Length 149 | /// 150 | public int ShortMessageMaxLength { get; set; } 151 | 152 | /// 153 | /// Gets or sets the type of the message generator. 154 | /// 155 | /// 156 | /// The type of the message generator. 157 | /// 158 | /// 159 | /// its timestamp or first 8 bytes of md5 hash 160 | /// 161 | public MessageIdGeneratorType MessageGeneratorType { get; set; } 162 | 163 | /// 164 | /// Gets or sets the stack trace depth. 165 | /// 166 | /// 167 | /// The stack trace depth. 168 | /// 169 | public int StackTraceDepth { get; set; } 170 | 171 | /// 172 | /// Gets or sets the maximum udp message size. 173 | /// 174 | /// 175 | /// The maximum udp message size 176 | /// 177 | public int MaxMessageSizeInUdp { get; set; } 178 | 179 | /// 180 | /// Gets or sets the host property required by the GELF format. If set to null, DNS hostname will be used instead. 181 | /// Override computer host name in logs 182 | /// 183 | public string HostnameOverride { get; set; } 184 | 185 | /// 186 | /// Is this a secure connection (SSL)? If so, it gets validated with the host 187 | /// 188 | public bool UseSsl { get; set; } 189 | 190 | /// 191 | /// JsonSerializer options 192 | /// 193 | public JsonSerializerOptions JsonSerializerOptions { get; set; } 194 | 195 | /// 196 | /// Gets or sets the username in http 197 | /// 198 | public string? UsernameInHttp { get; set; } 199 | 200 | /// 201 | /// Gets or sets the password in http 202 | /// 203 | public string? PasswordInHttp { get; set; } 204 | 205 | /// 206 | /// For custom implementations of ITransport 207 | /// 208 | /// 209 | /// should be set to 210 | /// 211 | public Func? TransportFactory { get; set; } 212 | 213 | /// 214 | /// Should use gzip for sending logs 215 | /// 216 | public bool UseGzip { get; set; } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Helpers/HttpBasicAuthenticationGenerator.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Debugging; 2 | using System; 3 | using System.Net.Http.Headers; 4 | using System.Text; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.Helpers 7 | { 8 | public class HttpBasicAuthenticationGenerator 9 | { 10 | private readonly string? _usernameInHttp; 11 | private readonly string? _passwordInHttp; 12 | 13 | public HttpBasicAuthenticationGenerator(string? usernameInHttp, string? passwordInHttp) 14 | { 15 | _usernameInHttp = usernameInHttp; 16 | _passwordInHttp = passwordInHttp; 17 | } 18 | 19 | public AuthenticationHeaderValue? Generate() 20 | { 21 | if (!Validate()) return null; 22 | 23 | var byteArray = Encoding.ASCII.GetBytes(string.Concat(_usernameInHttp, ":", _passwordInHttp)); 24 | 25 | return new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); 26 | } 27 | 28 | private bool Validate() 29 | { 30 | if (_usernameInHttp == null && _passwordInHttp == null) return false; 31 | 32 | if (_passwordInHttp == null) 33 | { 34 | SelfLog.WriteLine("UsernameInHttp has value, but passwordInHttp is empty. Therefore basic authentication not used"); 35 | return false; 36 | } 37 | 38 | if (_usernameInHttp == null) 39 | { 40 | SelfLog.WriteLine("PasswordInHttp has value, but UsernameInHttp is empty. Therefore basic authentication not used"); 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Helpers/LogLevelMapper.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System.Collections.Generic; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Helpers 5 | { 6 | public static class LogLevelMapper 7 | { 8 | private static readonly Dictionary LogLevelMap = new() 9 | { 10 | [LogEventLevel.Verbose] = 7, 11 | [LogEventLevel.Debug] = 7, 12 | [LogEventLevel.Information] = 6, 13 | [LogEventLevel.Warning] = 4, 14 | [LogEventLevel.Error] = 3, 15 | [LogEventLevel.Fatal] = 0 16 | }; 17 | 18 | /// 19 | /// Gets the mapped level. 20 | /// 21 | /// The log event level. 22 | /// Syslog level 23 | /// 24 | /// SyslogLevels: 25 | /// 0 Emergency: system is unusable 26 | /// 1 Alert: action must be taken immediately 27 | /// 2 Critical: critical conditions 28 | /// 3 Error: error conditions 29 | /// 4 Warning: warning conditions 30 | /// 5 Notice: normal but significant condition 31 | /// 6 Informational: informational messages 32 | /// 7 Debug: debug-level messages 33 | /// 34 | internal static int GetMappedLevel(LogEventLevel logEventLevel) 35 | { 36 | return LogLevelMap[logEventLevel]; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Helpers/MessageIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.Helpers 7 | { 8 | public interface IMessageIdGenerator 9 | { 10 | /// 11 | /// Generates the message identifier. 12 | /// 13 | /// 14 | byte[] GenerateMessageId(byte[] message); 15 | } 16 | 17 | public enum MessageIdGeneratorType 18 | { 19 | Timestamp, 20 | Md5 21 | } 22 | 23 | /// 24 | /// Generate message id from new GUID 25 | /// 26 | /// 27 | public sealed class TimestampMessageIdGenerator : IMessageIdGenerator 28 | { 29 | public byte[] GenerateMessageId(byte[] message) 30 | { 31 | return BitConverter.GetBytes(DateTime.UtcNow.Ticks); 32 | } 33 | } 34 | 35 | /// 36 | /// Generate message Id from first 8 bytes of MD5 hash 37 | /// 38 | /// 39 | public sealed class Md5MessageIdGenerator : IMessageIdGenerator 40 | { 41 | public byte[] GenerateMessageId(byte[] message) 42 | { 43 | #if NETSTANDARD2_0 44 | using MD5 md5 = MD5.Create(); 45 | 46 | byte[] messageHash = md5.ComputeHash(message); 47 | #else 48 | byte[] messageHash = MD5.HashData(message); 49 | #endif 50 | 51 | return messageHash.Take(8).ToArray(); 52 | } 53 | } 54 | 55 | public interface IMessageIdGeneratorResolver 56 | { 57 | IMessageIdGenerator Resolve(MessageIdGeneratorType generatorType); 58 | } 59 | 60 | public sealed class MessageIdGeneratorResolver : IMessageIdGeneratorResolver 61 | { 62 | private readonly Dictionary> _messageGenerators = new() 63 | { 64 | [MessageIdGeneratorType.Timestamp] = new Lazy(() => new TimestampMessageIdGenerator()), 65 | [MessageIdGeneratorType.Md5] = new Lazy(() => new Md5MessageIdGenerator()) 66 | }; 67 | 68 | /// Condition. 69 | public IMessageIdGenerator Resolve(MessageIdGeneratorType generatorType) 70 | { 71 | return _messageGenerators[generatorType].Value; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/MessageBuilders/ExceptionMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System; 3 | using System.Text; 4 | using System.Text.Json.Nodes; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.MessageBuilders 7 | { 8 | /// 9 | /// Exception builder 10 | /// 11 | /// 12 | public class ExceptionMessageBuilder : GelfMessageBuilder 13 | { 14 | private const string DefaultExceptionDelimiter = " - "; 15 | private const string DefaultStackTraceDelimiter = "--- Inner exception stack trace ---"; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// Name of the host. 21 | /// The options. 22 | public ExceptionMessageBuilder(string hostName, GraylogSinkOptionsBase options) : base(hostName, options) 23 | { 24 | } 25 | 26 | public override JsonObject Build(LogEvent logEvent) 27 | { 28 | Tuple excMessageTuple = GetExceptionMessages(logEvent.Exception); 29 | string exceptionDetail = excMessageTuple.Item1; 30 | string stackTrace = excMessageTuple.Item2!; 31 | string source = logEvent.Exception.Source!; 32 | string type = logEvent.Exception.GetType().FullName!; 33 | 34 | logEvent.AddOrUpdateProperty(new LogEventProperty("ExceptionSource", new ScalarValue(source))); 35 | logEvent.AddOrUpdateProperty(new LogEventProperty("ExceptionType", new ScalarValue(type))); 36 | logEvent.AddOrUpdateProperty(new LogEventProperty("ExceptionMessage", new ScalarValue(exceptionDetail))); 37 | logEvent.AddOrUpdateProperty(new LogEventProperty("StackTrace", new ScalarValue(stackTrace))); 38 | 39 | return base.Build(logEvent); 40 | } 41 | 42 | /// 43 | /// Get the message details from all nested exceptions, up to 10 in depth. 44 | /// 45 | /// Exception to get details for 46 | private Tuple GetExceptionMessages(Exception ex) 47 | { 48 | var exceptionSb = new StringBuilder(); 49 | var stackSb = new StringBuilder(); 50 | Exception? nestedException = ex; 51 | string? stackDetail = null; 52 | 53 | var counter = 0; 54 | do 55 | { 56 | exceptionSb.Append(nestedException.Message).Append(DefaultExceptionDelimiter); 57 | if (nestedException.StackTrace != null) 58 | { 59 | stackSb.AppendLine(nestedException.StackTrace).AppendLine(DefaultStackTraceDelimiter); 60 | } 61 | nestedException = nestedException.InnerException; 62 | counter++; 63 | } 64 | while (nestedException != null && counter < Options.StackTraceDepth); 65 | 66 | string exceptionDetail = exceptionSb.ToString().Substring(0, exceptionSb.Length - DefaultExceptionDelimiter.Length).Trim(); 67 | 68 | if (stackSb.Length > 0) 69 | { 70 | stackDetail = stackSb.ToString().Substring(0, stackSb.Length - DefaultStackTraceDelimiter.Length - 2).Trim(); 71 | } 72 | 73 | return new Tuple(exceptionDetail, stackDetail); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/MessageBuilders/GelfMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Parsing; 3 | using Serilog.Sinks.Graylog.Core.Extensions; 4 | using Serilog.Sinks.Graylog.Core.Helpers; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text.Json; 10 | using System.Text.Json.Nodes; 11 | 12 | namespace Serilog.Sinks.Graylog.Core.MessageBuilders 13 | { 14 | /// 15 | /// Message builder 16 | /// 17 | /// 18 | public class GelfMessageBuilder : IMessageBuilder 19 | { 20 | private const string DefaultGelfVersion = "1.1"; 21 | 22 | protected GraylogSinkOptionsBase Options => _options; 23 | 24 | private readonly string _hostName; 25 | private readonly GraylogSinkOptionsBase _options; 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// Name of the host. 31 | /// The options. 32 | public GelfMessageBuilder(string hostName, GraylogSinkOptionsBase options) 33 | { 34 | _hostName = hostName; 35 | _options = options; 36 | } 37 | 38 | /// 39 | /// Builds the specified log event. 40 | /// 41 | /// The log event. 42 | /// 43 | public virtual JsonObject Build(LogEvent logEvent) 44 | { 45 | string message = logEvent.RenderMessage(); 46 | string shortMessage = message.Truncate(Options.ShortMessageMaxLength); 47 | 48 | var jsonObject = new JsonObject 49 | { 50 | ["version"] = DefaultGelfVersion, 51 | ["host"] = Options.HostnameOverride ?? _hostName, 52 | ["short_message"] = shortMessage, 53 | ["timestamp"] = logEvent.Timestamp.ConvertToNix(), 54 | ["level"] = LogLevelMapper.GetMappedLevel(logEvent.Level), 55 | ["_stringLevel"] = logEvent.Level.ToString(), 56 | ["_facility"] = Options.Facility 57 | }; 58 | 59 | if (message.Length > Options.ShortMessageMaxLength) 60 | { 61 | jsonObject.Add("full_message", message); 62 | } 63 | 64 | foreach (KeyValuePair property in logEvent.Properties) 65 | { 66 | if (Options.ExcludeMessageTemplateProperties) 67 | { 68 | var propertyTokens = logEvent.MessageTemplate.Tokens.OfType(); 69 | 70 | if (propertyTokens.Any(x => x.PropertyName == property.Key)) 71 | { 72 | continue; 73 | } 74 | } 75 | 76 | AddAdditionalField(jsonObject, property); 77 | } 78 | 79 | if (Options.IncludeMessageTemplate) 80 | { 81 | string messageTemplate = logEvent.MessageTemplate.Text; 82 | 83 | jsonObject.Add($"_{Options.MessageTemplateFieldName}", messageTemplate); 84 | } 85 | 86 | return jsonObject; 87 | } 88 | 89 | private void AddAdditionalField(JsonObject jObject, 90 | KeyValuePair property, 91 | string memberPath = "") 92 | { 93 | string key = string.IsNullOrEmpty(memberPath) 94 | ? property.Key 95 | : $"{memberPath}.{property.Key}"; 96 | 97 | switch (property.Value) 98 | { 99 | case ScalarValue scalarValue: 100 | if (key.Equals("id", StringComparison.OrdinalIgnoreCase)) 101 | { 102 | key = "id_"; 103 | } 104 | 105 | if (!key.StartsWith("_", StringComparison.OrdinalIgnoreCase)) 106 | { 107 | key = $"_{key}"; 108 | } 109 | 110 | if (scalarValue.Value == null) 111 | { 112 | jObject.Add(key, null); 113 | 114 | break; 115 | } 116 | 117 | var node = JsonSerializer.SerializeToNode(scalarValue.Value, Options.JsonSerializerOptions); 118 | 119 | jObject.Add(key, node); 120 | 121 | break; 122 | case SequenceValue sequenceValue: 123 | var sequenceValueString = RenderPropertyValue(sequenceValue); 124 | 125 | jObject.Add(key, sequenceValueString); 126 | 127 | if (Options.ParseArrayValues) 128 | { 129 | int counter = 0; 130 | 131 | foreach (var sequenceElement in sequenceValue.Elements) 132 | { 133 | AddAdditionalField(jObject, new KeyValuePair(counter.ToString(), sequenceElement), key); 134 | 135 | counter++; 136 | } 137 | } 138 | 139 | break; 140 | case StructureValue structureValue: 141 | foreach (LogEventProperty logEventProperty in structureValue.Properties) 142 | { 143 | AddAdditionalField(jObject, 144 | new KeyValuePair(logEventProperty.Name, logEventProperty.Value), 145 | key); 146 | } 147 | 148 | break; 149 | case DictionaryValue dictionaryValue: 150 | if (Options.ParseArrayValues) 151 | { 152 | foreach (KeyValuePair dictionaryValueElement in dictionaryValue.Elements) 153 | { 154 | var renderedKey = RenderPropertyValue(dictionaryValueElement.Key); 155 | 156 | AddAdditionalField(jObject, new KeyValuePair(renderedKey, dictionaryValueElement.Value), key); 157 | } 158 | } else 159 | { 160 | var dict = dictionaryValue.Elements.ToDictionary(k => k.Key.Value, v => RenderPropertyValue(v.Value)); 161 | var stringDictionary = JsonSerializer.SerializeToNode(dict, Options.JsonSerializerOptions); 162 | 163 | jObject.Add(key, stringDictionary); 164 | } 165 | 166 | break; 167 | } 168 | } 169 | 170 | private static string RenderPropertyValue(LogEventPropertyValue propertyValue) 171 | { 172 | using TextWriter tw = new StringWriter(); 173 | 174 | propertyValue.Render(tw); 175 | 176 | string result = tw.ToString()!; 177 | result = result.Trim('"'); 178 | 179 | return result; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/MessageBuilders/IMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using System.Text.Json.Nodes; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.MessageBuilders 5 | { 6 | /// 7 | /// Build json message for graylog 8 | /// 9 | public interface IMessageBuilder 10 | { 11 | /// 12 | /// Builds the specified log event. 13 | /// 14 | /// The log event. 15 | /// 16 | JsonObject Build(LogEvent logEvent); 17 | } 18 | 19 | /// 20 | /// Builder type 21 | /// 22 | public enum BuilderType 23 | { 24 | /// 25 | /// Exception Builder 26 | /// 27 | Exception, 28 | /// 29 | /// Message Builder 30 | /// 31 | Message 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Serilog.Sinks.Graylog.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11.0 4 | 5 | 6 | net7.0;net6.0;netstandard2.0 7 | true 8 | full 9 | enable 10 | true 11 | 12 | true 13 | key.snk 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/DnsResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Transport 5 | { 6 | using System.Linq; 7 | using System.Net.Sockets; 8 | 9 | /// 10 | /// Base class for resolve dns 11 | /// 12 | public interface IDnsInfoProvider 13 | { 14 | /// 15 | /// Gets the host addresses. 16 | /// 17 | /// The host name or address. 18 | /// 19 | Task GetHostAddresses(string hostNameOrAddress); 20 | Task GetIpAddress(string hostNameOrAddress); 21 | } 22 | 23 | public class DnsWrapper : IDnsInfoProvider 24 | { 25 | /// 26 | /// Gets the host addresses. 27 | /// 28 | /// The host name or address. 29 | /// 30 | /// When resolve trows exception. 31 | public Task GetHostAddresses(string hostNameOrAddress) 32 | { 33 | return Dns.GetHostAddressesAsync(hostNameOrAddress); 34 | } 35 | 36 | public async Task GetIpAddress(string hostNameOrAddress) 37 | { 38 | if (string.IsNullOrEmpty(hostNameOrAddress)) 39 | { 40 | return default; 41 | } 42 | 43 | var addresses = await GetHostAddresses(hostNameOrAddress).ConfigureAwait(false); 44 | var result = addresses.FirstOrDefault(c => c.AddressFamily == AddressFamily.InterNetwork); 45 | return result; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Http/HttpTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Transport.Http 5 | { 6 | public class HttpTransport : ITransport 7 | { 8 | private readonly ITransportClient _transportClient; 9 | 10 | public HttpTransport(ITransportClient transportClient) 11 | { 12 | _transportClient = transportClient; 13 | } 14 | 15 | public Task Send(string message) 16 | { 17 | return _transportClient.Send(message); 18 | } 19 | 20 | public void Dispose() 21 | { 22 | Dispose(true); 23 | GC.SuppressFinalize(this); 24 | } 25 | 26 | protected virtual void Dispose(bool disposing) 27 | { 28 | if (disposing) 29 | { 30 | _transportClient?.Dispose(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Http/HttpTransportClient.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Debugging; 2 | using Serilog.Sinks.Graylog.Core.Helpers; 3 | using System; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Serilog.Sinks.Graylog.Core.Transport.Http 10 | { 11 | public class HttpTransportClient : ITransportClient 12 | { 13 | private const string _defaultHttpUriPath = "/gelf"; 14 | 15 | private HttpClient? _httpClient; 16 | 17 | private readonly GraylogSinkOptionsBase options; 18 | 19 | public HttpTransportClient(GraylogSinkOptionsBase options) 20 | { 21 | this.options = options; 22 | } 23 | 24 | protected virtual HttpClient CreateHttpClient() => new(); 25 | 26 | protected virtual void ConfigureHttpClient(HttpClient httpClient) 27 | { 28 | if (string.IsNullOrEmpty(options.HostnameOrAddress)) 29 | { 30 | throw new InvalidOperationException("The HostnameOrAddress value must be set."); 31 | } 32 | 33 | var builder = new UriBuilder(options.HostnameOrAddress) 34 | { 35 | Port = options.Port.GetValueOrDefault(443) 36 | }; 37 | 38 | if (options.UseSsl) 39 | { 40 | builder.Scheme = "https"; 41 | } 42 | 43 | httpClient.BaseAddress = builder.Uri; 44 | 45 | httpClient.DefaultRequestHeaders.ExpectContinue = false; 46 | httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue { NoCache = true }; 47 | 48 | var authenticationHeaderValue = new HttpBasicAuthenticationGenerator(options.UsernameInHttp, options.PasswordInHttp).Generate(); 49 | 50 | if (authenticationHeaderValue != null) 51 | { 52 | httpClient.DefaultRequestHeaders.Authorization = authenticationHeaderValue; 53 | } 54 | } 55 | 56 | private void EnsureHttpClient() 57 | { 58 | if (_httpClient == null) 59 | { 60 | _httpClient = CreateHttpClient(); 61 | 62 | ConfigureHttpClient(_httpClient); 63 | } 64 | } 65 | 66 | public async Task Send(string message) 67 | { 68 | EnsureHttpClient(); 69 | 70 | var content = new StringContent(message, Encoding.UTF8, "application/json"); 71 | 72 | HttpResponseMessage result = await _httpClient!.PostAsync(_defaultHttpUriPath, content).ConfigureAwait(false); 73 | 74 | if (!result.IsSuccessStatusCode) 75 | { 76 | SelfLog.WriteLine("Unable send log message to graylog via HTTP transport"); 77 | } 78 | } 79 | 80 | public void Dispose() 81 | { 82 | Dispose(true); 83 | GC.SuppressFinalize(this); 84 | } 85 | 86 | protected virtual void Dispose(bool disposing) 87 | { 88 | if (disposing) 89 | { 90 | _httpClient?.Dispose(); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/ITransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Transport 5 | { 6 | /// 7 | /// The Transport interface 8 | /// 9 | public interface ITransport : IDisposable 10 | { 11 | /// 12 | /// Sends the specified target. 13 | /// 14 | /// The message. 15 | Task Send(string message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/ITransportClient'.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Transport 5 | { 6 | /// 7 | /// The Transport client interface 8 | /// 9 | /// 10 | public interface ITransportClient : IDisposable 11 | { 12 | /// 13 | /// Sends the specified payload. 14 | /// 15 | /// The payload. 16 | Task Send(T payload); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Tcp/TcpTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Serilog.Sinks.Graylog.Core.Transport.Tcp 5 | { 6 | public class TcpTransport : ITransport 7 | { 8 | private readonly ITransportClient _tcpClient; 9 | 10 | /// 11 | public TcpTransport(ITransportClient tcpClient) 12 | { 13 | _tcpClient = tcpClient; 14 | } 15 | 16 | /// 17 | public Task Send(string message) 18 | { 19 | #if NET 20 | var payload = new byte[message.Length + 1]; 21 | System.Text.Encoding.UTF8.GetBytes(message.AsSpan(), payload.AsSpan()); 22 | payload[^1] = 0x00; 23 | 24 | return _tcpClient.Send(payload); 25 | #else 26 | var payload = System.Text.Encoding.UTF8.GetBytes(message); 27 | 28 | Array.Resize(ref payload, payload.Length + 1); 29 | payload[payload.Length - 1] = 0x00; 30 | 31 | return _tcpClient.Send(payload); 32 | #endif 33 | } 34 | 35 | /// 36 | public void Dispose() 37 | { 38 | Dispose(true); 39 | GC.SuppressFinalize(this); 40 | } 41 | 42 | protected virtual void Dispose(bool disposing) 43 | { 44 | if (disposing) 45 | { 46 | _tcpClient?.Dispose(); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Tcp/TcpTransportClient.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Debugging; 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Security; 7 | using System.Net.Sockets; 8 | using System.Threading.Tasks; 9 | 10 | namespace Serilog.Sinks.Graylog.Core.Transport.Tcp 11 | { 12 | public class TcpTransportClient : ITransportClient 13 | { 14 | private const int DefaultPort = 12201; 15 | 16 | private Stream? _stream; 17 | 18 | private readonly GraylogSinkOptionsBase _options; 19 | private readonly IDnsInfoProvider _dnsInfoProvider; 20 | private readonly TcpClient _client; 21 | 22 | /// 23 | public TcpTransportClient(GraylogSinkOptionsBase options, IDnsInfoProvider dnsInfoProvider) 24 | { 25 | _options = options; 26 | _dnsInfoProvider = dnsInfoProvider; 27 | 28 | _client = new TcpClient(); 29 | } 30 | 31 | /// 32 | public async Task Send(byte[] payload) 33 | { 34 | await EnsureConnection().ConfigureAwait(false); 35 | 36 | #if NETSTANDARD2_0 37 | await _stream!.WriteAsync(payload, 0, payload.Length).ConfigureAwait(false); 38 | #else 39 | await _stream!.WriteAsync(payload).ConfigureAwait(false); 40 | #endif 41 | 42 | await _stream.FlushAsync().ConfigureAwait(false); 43 | } 44 | 45 | private async Task EnsureConnection() 46 | { 47 | if (!_client.Connected) 48 | { 49 | await Connect().ConfigureAwait(false); 50 | } 51 | } 52 | 53 | private async Task Connect() 54 | { 55 | IPAddress? _address = await _dnsInfoProvider.GetIpAddress(_options.HostnameOrAddress!); 56 | if (_address == default) 57 | { 58 | SelfLog.WriteLine("IP address could not be resolved."); 59 | return; 60 | } 61 | 62 | int port = _options.Port.GetValueOrDefault(DefaultPort); 63 | string? sslHost = _options.UseSsl ? _options.HostnameOrAddress : null; 64 | 65 | await _client.ConnectAsync(_address, port).ConfigureAwait(false); 66 | 67 | _stream = _client.GetStream(); 68 | 69 | if (!string.IsNullOrWhiteSpace(sslHost)) 70 | { 71 | var _sslStream = new SslStream(_stream, false); 72 | 73 | await _sslStream.AuthenticateAsClientAsync(sslHost).ConfigureAwait(false); 74 | 75 | if (_sslStream.RemoteCertificate != null) 76 | { 77 | SelfLog.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.", 78 | _sslStream.RemoteCertificate.Subject, 79 | _sslStream.RemoteCertificate.GetEffectiveDateString(), 80 | _sslStream.RemoteCertificate.GetExpirationDateString()); 81 | 82 | _stream = _sslStream; 83 | } else 84 | { 85 | SelfLog.WriteLine("Remote certificate is null."); 86 | } 87 | } 88 | } 89 | 90 | /// 91 | public void Dispose() 92 | { 93 | Dispose(true); 94 | GC.SuppressFinalize(this); 95 | } 96 | 97 | protected virtual void Dispose(bool disposing) 98 | { 99 | if (disposing) 100 | { 101 | _stream?.Dispose(); 102 | _client.Dispose(); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/TransportType.cs: -------------------------------------------------------------------------------- 1 | namespace Serilog.Sinks.Graylog.Core.Transport 2 | { 3 | public enum TransportType 4 | { 5 | Udp, 6 | Http, 7 | Tcp, 8 | // Custom implementations of transports 9 | Custom 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Udp/ChunkSettings.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core.Helpers; 2 | 3 | namespace Serilog.Sinks.Graylog.Core.Transport.Udp 4 | { 5 | public sealed class ChunkSettings 6 | { 7 | /// 8 | public ChunkSettings(MessageIdGeneratorType messageIdGeneratorType, int maxMessageSizeInUdp) 9 | { 10 | MessageIdGeneratorType = messageIdGeneratorType; 11 | MaxMessageSizeInUdp = maxMessageSizeInUdp; 12 | } 13 | 14 | public MessageIdGeneratorType MessageIdGeneratorType { get; } 15 | 16 | /// 17 | /// The prefix size 18 | /// 19 | /// 20 | public const byte PrefixSize = 12; 21 | 22 | /// 23 | /// The maximum number of chunks allowed 24 | /// 25 | /// 26 | public const byte MaxNumberOfChunksAllowed = 128; 27 | 28 | /// 29 | /// The maximum message size in UDP 30 | /// 31 | /// UDP chunks are usually limited to a size of 8192 bytes 32 | /// 33 | /// 34 | public int MaxMessageSizeInUdp { get; } 35 | 36 | public static readonly byte[] GelfMagicBytes = { 0x1e, 0x0f }; 37 | 38 | /// 39 | /// The maximum message size in chunk 40 | /// 41 | public int MaxMessageSizeInChunk => MaxMessageSizeInUdp - PrefixSize; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Udp/DataToChunkConverter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Serilog.Sinks.Graylog.Core.Transport.Udp 7 | { 8 | /// 9 | /// Converts Data to udp chunks 10 | /// 11 | public interface IDataToChunkConverter 12 | { 13 | /// 14 | /// Converts to chunks. 15 | /// 16 | /// The message. 17 | /// array of chunks to save 18 | /// message was too long 19 | /// message was too long 20 | IList ConvertToChunks(byte[] message); 21 | } 22 | 23 | /// 24 | public sealed class DataToChunkConverter : IDataToChunkConverter 25 | { 26 | private readonly ChunkSettings _settings; 27 | private readonly IMessageIdGeneratorResolver _generatorResolver; 28 | 29 | /// 30 | /// Initializes a new instance of the class. 31 | /// 32 | /// The settings. 33 | /// The generator resolver. 34 | public DataToChunkConverter(ChunkSettings settings, 35 | IMessageIdGeneratorResolver generatorResolver) 36 | { 37 | _settings = settings; 38 | _generatorResolver = generatorResolver; 39 | } 40 | 41 | /// 42 | /// Converts to chunks. 43 | /// 44 | /// The message. 45 | /// array of chunks to save 46 | /// message was too long 47 | /// message was too long 48 | public IList ConvertToChunks(byte[] message) 49 | { 50 | int messageLength = message.Length; 51 | if (messageLength <= _settings.MaxMessageSizeInUdp) 52 | { 53 | return new List(1) { message }; 54 | } 55 | 56 | int chunksCount = messageLength / _settings.MaxMessageSizeInChunk + 1; 57 | if (chunksCount > ChunkSettings.MaxNumberOfChunksAllowed) 58 | { 59 | throw new ArgumentException("message was too long", nameof(message)); 60 | } 61 | 62 | IMessageIdGenerator messageIdGenerator = _generatorResolver.Resolve(_settings.MessageIdGeneratorType); 63 | byte[] messageId = messageIdGenerator.GenerateMessageId(message); 64 | 65 | var result = new List(); 66 | for (byte i = 0; i < chunksCount; i++) 67 | { 68 | IList chunkHeader = ConstructChunkHeader(messageId, i, (byte)chunksCount); 69 | 70 | int skip = i * _settings.MaxMessageSizeInChunk; 71 | byte[] chunkData = message.Skip(skip).Take(_settings.MaxMessageSizeInChunk).ToArray(); 72 | 73 | var messageChunkFull = new List(chunkHeader.Count + chunkData.Length); 74 | messageChunkFull.AddRange(chunkHeader); 75 | messageChunkFull.AddRange(chunkData); 76 | result.Add(messageChunkFull.ToArray()); 77 | } 78 | return result; 79 | } 80 | 81 | private static IList ConstructChunkHeader(byte[] messageId, byte chunkNumber, byte chunksCount) 82 | { 83 | var result = new List(ChunkSettings.PrefixSize); 84 | result.AddRange(ChunkSettings.GelfMagicBytes); 85 | result.AddRange(messageId); 86 | result.Add(chunkNumber); 87 | result.Add(chunksCount); 88 | return result; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Udp/UdpTransport.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core.Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Serilog.Sinks.Graylog.Core.Transport.Udp 8 | { 9 | public sealed class UdpTransport : ITransport 10 | { 11 | private readonly ITransportClient _transportClient; 12 | private readonly IDataToChunkConverter _chunkConverter; 13 | private readonly GraylogSinkOptionsBase _options; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | /// The transport client. 19 | /// 20 | public UdpTransport(ITransportClient transportClient, IDataToChunkConverter chunkConverter, GraylogSinkOptionsBase options) 21 | { 22 | _transportClient = transportClient; 23 | _chunkConverter = chunkConverter; 24 | _options = options; 25 | } 26 | 27 | /// 28 | /// Sends the specified target. 29 | /// 30 | /// The message. 31 | /// message was too long 32 | public Task Send(string message) 33 | { 34 | var payload = _options.UseGzip ? message.ToGzip() : message.ToByteArray(); 35 | IList chunks = _chunkConverter.ConvertToChunks(payload); 36 | 37 | IEnumerable sendTasks = chunks.Select(c => _transportClient.Send(c)); 38 | return Task.WhenAll(sendTasks.ToArray()); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | Dispose(true); 44 | GC.SuppressFinalize(this); 45 | } 46 | 47 | private void Dispose(bool disposing) 48 | { 49 | if (disposing) 50 | { 51 | _transportClient.Dispose(); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/Transport/Udp/UdpTransportClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Threading.Tasks; 6 | 7 | namespace Serilog.Sinks.Graylog.Core.Transport.Udp 8 | { 9 | using Debugging; 10 | 11 | /// 12 | /// Udp transport client 13 | /// 14 | /// 15 | public sealed class UdpTransportClient : ITransportClient 16 | { 17 | private IPEndPoint? _ipEndPoint; 18 | 19 | private readonly GraylogSinkOptionsBase _options; 20 | private readonly IDnsInfoProvider _dnsInfoProvider; 21 | private readonly UdpClient _client; 22 | 23 | public UdpTransportClient(GraylogSinkOptionsBase options, IDnsInfoProvider dnsInfoProvider) 24 | { 25 | _options = options; 26 | _dnsInfoProvider = dnsInfoProvider; 27 | 28 | _client = new UdpClient(); 29 | } 30 | 31 | private async Task EnsureTarget() 32 | { 33 | if (_ipEndPoint == null) 34 | { 35 | var ipAddress = await _dnsInfoProvider.GetIpAddress(_options.HostnameOrAddress!).ConfigureAwait(false); 36 | if (ipAddress == default) 37 | { 38 | SelfLog.WriteLine("IP address could not be resolved."); 39 | return; 40 | } 41 | _ipEndPoint = new IPEndPoint(ipAddress, _options.Port.GetValueOrDefault()); 42 | } 43 | } 44 | 45 | /// 46 | /// Sends the specified payload. 47 | /// 48 | /// The payload. 49 | public async Task Send(byte[] payload) 50 | { 51 | await EnsureTarget().ConfigureAwait(false); 52 | 53 | await _client.SendAsync(payload, payload.Length, _ipEndPoint).ConfigureAwait(false); 54 | } 55 | 56 | public void Dispose() => _client.Dispose(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/TransportFactory.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core.Helpers; 2 | using Serilog.Sinks.Graylog.Core.MessageBuilders; 3 | using Serilog.Sinks.Graylog.Core.Transport; 4 | using Serilog.Sinks.Graylog.Core.Transport.Http; 5 | using Serilog.Sinks.Graylog.Core.Transport.Tcp; 6 | using Serilog.Sinks.Graylog.Core.Transport.Udp; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Net; 10 | using SinkTransportType = Serilog.Sinks.Graylog.Core.Transport.TransportType; 11 | 12 | namespace Serilog.Sinks.Graylog.Core 13 | { 14 | public interface ISinkComponentsBuilder 15 | { 16 | ITransport MakeTransport(); 17 | IGelfConverter MakeGelfConverter(); 18 | } 19 | 20 | public class SinkComponentsBuilder : ISinkComponentsBuilder 21 | { 22 | private readonly GraylogSinkOptionsBase _options; 23 | private readonly Dictionary> _builders; 24 | 25 | public SinkComponentsBuilder(GraylogSinkOptionsBase options) 26 | { 27 | _options = options; 28 | 29 | _builders = new Dictionary> 30 | { 31 | [BuilderType.Exception] = new(() => 32 | { 33 | string hostName = Dns.GetHostName(); 34 | return new ExceptionMessageBuilder(hostName, _options); 35 | }), 36 | [BuilderType.Message] = new(() => 37 | { 38 | string hostName = Dns.GetHostName(); 39 | return new GelfMessageBuilder(hostName, _options); 40 | }) 41 | }; 42 | } 43 | 44 | public ITransport MakeTransport() 45 | { 46 | switch (_options.TransportType) 47 | { 48 | case SinkTransportType.Udp: 49 | var chunkSettings = new ChunkSettings(_options.MessageGeneratorType, _options.MaxMessageSizeInUdp); 50 | IDataToChunkConverter chunkConverter = new DataToChunkConverter(chunkSettings, new MessageIdGeneratorResolver()); 51 | 52 | var udpClient = new UdpTransportClient(_options, new DnsWrapper()); 53 | var udpTransport = new UdpTransport(udpClient, chunkConverter, _options); 54 | 55 | return udpTransport; 56 | case SinkTransportType.Http: 57 | var httpClient = new HttpTransportClient(_options); 58 | 59 | return new HttpTransport(httpClient); 60 | case SinkTransportType.Tcp: 61 | var tcpClient = new TcpTransportClient(_options, new DnsWrapper()); 62 | 63 | return new TcpTransport(tcpClient); 64 | case SinkTransportType.Custom: 65 | if (_options.TransportFactory == null) 66 | { 67 | throw new InvalidOperationException("The TransportFactory value must have a value."); 68 | } 69 | 70 | return _options.TransportFactory(); 71 | default: 72 | throw new ArgumentOutOfRangeException(nameof(_options), _options.TransportType, null); 73 | } 74 | } 75 | 76 | public IGelfConverter MakeGelfConverter() => _options.GelfConverter ?? new GelfConverter(_builders); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Core/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/src/Serilog.Sinks.Graylog.Core/key.snk -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/Configurations/AppSettingsWithGraylogSinkContainingHostProperty.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "WriteTo": [ 4 | { 5 | "Name": "Console", 6 | "Args": { 7 | "restrictedToMinimumLevel": "Information", 8 | "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u4}] {Message:lj} {NewLine}{Exception}" 9 | } 10 | }, 11 | { 12 | "Name": "Graylog", 13 | "Args": { 14 | "facility": "MicroserviceTemplate.Host.WebApi", 15 | "hostnameOrAddress": "http://localhost", 16 | "port": 12201, 17 | "transportType": "Http", 18 | "host": "my_host", 19 | "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u4}] {Message:lj} {NewLine}{Exception}" 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/GraylogSinkFixture.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using Newtonsoft.Json.Linq; 3 | using Serilog.Events; 4 | using Serilog.Parsing; 5 | using Serilog.Sinks.Graylog.Core; 6 | using Serilog.Sinks.Graylog.Core.Transport; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace Serilog.Sinks.Graylog.Tests 13 | { 14 | public class GraylogSinkFixture 15 | { 16 | [Fact(Skip = "This test not work anymore because IMessageBuilder gets from internal dictionary")] 17 | public void WhenEmit_ThenSendData() 18 | { 19 | var gelfConverter = new Mock(); 20 | var transport = new Mock(); 21 | 22 | var options = new GraylogSinkOptions 23 | { 24 | GelfConverter = gelfConverter.Object, 25 | TransportType = TransportType.Udp, 26 | HostnameOrAddress = "localhost" 27 | }; 28 | 29 | GraylogSink target = new(options); 30 | 31 | var logEvent = new LogEvent(DateTimeOffset.Now, LogEventLevel.Fatal, null, 32 | new MessageTemplate("O_o", new List()), new List()); 33 | 34 | var jObject = new JObject(); 35 | transport.Setup(c => c.Send(jObject.ToString(Newtonsoft.Json.Formatting.None))).Returns(Task.CompletedTask); 36 | 37 | 38 | //gelfConverter.Setup(c => c.GetGelfJson(logEvent)).Returns(jObject); 39 | 40 | target.Emit(logEvent); 41 | 42 | gelfConverter.VerifyAll(); 43 | 44 | transport.Verify(c => c.Send(It.IsAny())); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/IntegrateSinkTestWithHttp.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Serilog.Events; 3 | using Serilog.Exceptions; 4 | using Serilog.Sinks.Graylog.Core.Helpers; 5 | using Serilog.Sinks.Graylog.Core.Transport; 6 | using Serilog.Sinks.Graylog.Tests.ComplexIntegrationTest; 7 | using System; 8 | using System.Linq; 9 | using Xunit; 10 | 11 | namespace Serilog.Sinks.Graylog.Tests 12 | { 13 | [Trait("Category", "Integration")] 14 | public class IntegrateSinkTestWithHttp 15 | { 16 | [Fact] 17 | [Trait("Category", "Integration")] 18 | public void TestComplex() 19 | { 20 | var loggerConfig = new LoggerConfiguration(); 21 | 22 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 23 | { 24 | ShortMessageMaxLength = 50, 25 | MinimumLogEventLevel = LogEventLevel.Information, 26 | TransportType = TransportType.Http, 27 | Facility = "VolkovTestFacility", 28 | HostnameOrAddress = "http://logs.aeroclub.int", 29 | Port = 12201 30 | }); 31 | 32 | var logger = loggerConfig.CreateLogger(); 33 | 34 | var test = new TestClass 35 | { 36 | Id = 1, 37 | Bar = new Bar 38 | { 39 | Id = 2, 40 | Prop = "123" 41 | }, 42 | TestPropertyOne = "1", 43 | TestPropertyThree = "3", 44 | TestPropertyTwo = "2" 45 | }; 46 | 47 | logger.Information("SomeComplexTestEntry {@test}", test); 48 | } 49 | 50 | [Fact] 51 | public void WhenHostIsWrong_ThenLoggerCreationShouldNotBeFail() 52 | { 53 | var loggerConfig = new LoggerConfiguration(); 54 | 55 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 56 | { 57 | ShortMessageMaxLength = 50, 58 | MinimumLogEventLevel = LogEventLevel.Information, 59 | TransportType = TransportType.Http, 60 | Facility = "VolkovTestFacility", 61 | HostnameOrAddress = "abracadabra", 62 | Port = 12201, 63 | //SerializerSettings = new JsonSerializerSettings() 64 | }); 65 | 66 | var logger = loggerConfig.CreateLogger(); 67 | 68 | var test = new TestClass 69 | { 70 | Id = 1, 71 | Bar = new Bar 72 | { 73 | Id = 2, 74 | Prop = "123" 75 | }, 76 | TestPropertyOne = "1", 77 | TestPropertyThree = "3", 78 | TestPropertyTwo = "2", 79 | EnumVal = TestEnumOne.Three, 80 | SomeTestDateTime = DateTime.Now 81 | 82 | 83 | }; 84 | 85 | logger.Information("SomeComplexTestEntry {@test}", test); 86 | } 87 | 88 | [Fact] 89 | [Trait("Category", "Integration")] 90 | public void LogInformationWithLevel() 91 | { 92 | var fixture = new Fixture(); 93 | fixture.Behaviors.Clear(); 94 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 95 | var profile = fixture.Create(); 96 | 97 | var loggerConfig = new LoggerConfiguration(); 98 | 99 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 100 | { 101 | MinimumLogEventLevel = LogEventLevel.Error, 102 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 103 | TransportType = TransportType.Http, 104 | Facility = "VolkovTestFacility", 105 | HostnameOrAddress = "http://logs.aeroclub.int", 106 | Port = 12201 107 | }); 108 | 109 | var logger = loggerConfig.CreateLogger(); 110 | 111 | logger.Information("battle profile: {@BattleProfile}", profile); 112 | } 113 | 114 | 115 | [Fact] 116 | [Trait("Category", "Integration")] 117 | public void LogInformationWithOneProfile() 118 | { 119 | var fixture = new Fixture(); 120 | fixture.Behaviors.Clear(); 121 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 122 | var profile = fixture.Create(); 123 | 124 | var loggerConfig = new LoggerConfiguration(); 125 | 126 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 127 | { 128 | MinimumLogEventLevel = LogEventLevel.Information, 129 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 130 | TransportType = TransportType.Http, 131 | Facility = "VolkovTestFacility", 132 | HostnameOrAddress = "http://logs.aeroclub.int", 133 | Port = 12201 134 | }); 135 | 136 | var logger = loggerConfig.CreateLogger(); 137 | 138 | logger.Information("battle profile: {@BattleProfile}", profile); 139 | } 140 | 141 | [Fact] 142 | [Trait("Ignore", "Integration")] 143 | public void Log10Profiles() 144 | { 145 | var fixture = new Fixture(); 146 | fixture.Behaviors.Clear(); 147 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 148 | var profiles = fixture.CreateMany(10).ToList(); 149 | 150 | var loggerConfig = new LoggerConfiguration(); 151 | 152 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 153 | { 154 | MinimumLogEventLevel = LogEventLevel.Information, 155 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 156 | TransportType = TransportType.Http, 157 | Facility = "VolkovTestFacility", 158 | HostnameOrAddress = "http://logs.aeroclub.int", 159 | Port = 12201 160 | }); 161 | 162 | var logger = loggerConfig.CreateLogger(); 163 | 164 | profiles.AsParallel().ForAll(profile => 165 | { 166 | logger.Information("TestSend {@BattleProfile}", profile); 167 | }); 168 | } 169 | 170 | [Fact] 171 | [Trait("Category", "Integration")] 172 | public void LogInformationWithUsernamePassword() 173 | { 174 | var fixture = new Fixture(); 175 | fixture.Behaviors.Clear(); 176 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 177 | var profile = fixture.Create(); 178 | 179 | var loggerConfig = new LoggerConfiguration(); 180 | 181 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 182 | { 183 | MinimumLogEventLevel = LogEventLevel.Information, 184 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 185 | TransportType = TransportType.Http, 186 | Facility = "VolkovTestFacility", 187 | HostnameOrAddress = "http://logs.aeroclub.int", 188 | Port = 12201, 189 | UsernameInHttp = "username", 190 | PasswordInHttp = "password" 191 | }); 192 | 193 | var logger = loggerConfig.CreateLogger(); 194 | 195 | logger.Information("battle profile: {@BattleProfile}", profile); 196 | } 197 | 198 | [Fact] 199 | [Trait("Category", "Integration")] 200 | public void LogInformationWithHostnameOrAddressEndsWithoutPath() 201 | { 202 | var fixture = new Fixture(); 203 | fixture.Behaviors.Clear(); 204 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 205 | var profile = fixture.Create(); 206 | 207 | var loggerConfig = new LoggerConfiguration(); 208 | 209 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 210 | { 211 | MinimumLogEventLevel = LogEventLevel.Information, 212 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 213 | TransportType = TransportType.Http, 214 | Facility = "VolkovTestFacility", 215 | HostnameOrAddress = "http://logs.aeroclub.int", 216 | Port = 12201 217 | }); 218 | 219 | var logger = loggerConfig.CreateLogger(); 220 | 221 | logger.Information("battle profile: {@BattleProfile}", profile); 222 | } 223 | 224 | [Fact] 225 | [Trait("Category", "Integration")] 226 | public void LogInformationWithHostnameOrAddressEndsWithSlash() 227 | { 228 | var fixture = new Fixture(); 229 | fixture.Behaviors.Clear(); 230 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 231 | var profile = fixture.Create(); 232 | 233 | var loggerConfig = new LoggerConfiguration(); 234 | 235 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 236 | { 237 | MinimumLogEventLevel = LogEventLevel.Information, 238 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 239 | TransportType = TransportType.Http, 240 | Facility = "VolkovTestFacility", 241 | HostnameOrAddress = "http://logs.aeroclub.int/", 242 | Port = 12201 243 | }); 244 | 245 | var logger = loggerConfig.CreateLogger(); 246 | 247 | logger.Information("battle profile: {@BattleProfile}", profile); 248 | } 249 | 250 | [Fact] 251 | [Trait("Category", "Integration")] 252 | public void LogInformationWithHostnameOrAddressEndsWithPath() 253 | { 254 | var fixture = new Fixture(); 255 | fixture.Behaviors.Clear(); 256 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 257 | var profile = fixture.Create(); 258 | 259 | var loggerConfig = new LoggerConfiguration(); 260 | 261 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 262 | { 263 | MinimumLogEventLevel = LogEventLevel.Information, 264 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 265 | TransportType = TransportType.Http, 266 | Facility = "VolkovTestFacility", 267 | HostnameOrAddress = "http://logs.aeroclub.int/testgelf", 268 | Port = 12201 269 | }); 270 | 271 | var logger = loggerConfig.CreateLogger(); 272 | 273 | logger.Information("battle profile: {@BattleProfile}", profile); 274 | } 275 | 276 | [Fact] 277 | [Trait("Category", "Integration")] 278 | public void TestException() 279 | { 280 | var loggerConfig = new LoggerConfiguration(); 281 | 282 | loggerConfig 283 | .Enrich.WithExceptionDetails() 284 | .WriteTo.Graylog(new GraylogSinkOptions 285 | { 286 | MinimumLogEventLevel = LogEventLevel.Information, 287 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 288 | TransportType = TransportType.Http, 289 | Facility = "VolkovTestFacility", 290 | HostnameOrAddress = "http://logs.aeroclub.int", 291 | Port = 12201 292 | }); 293 | 294 | var test = new TestClass 295 | { 296 | Id = 1, 297 | SomeTestDateTime = DateTime.UtcNow, 298 | Bar = new Bar 299 | { 300 | Id = 2 301 | }, 302 | TestPropertyOne = "1", 303 | TestPropertyThree = "3", 304 | TestPropertyTwo = "2" 305 | }; 306 | 307 | 308 | var logger = loggerConfig.CreateLogger(); 309 | 310 | try 311 | { 312 | try 313 | { 314 | throw new InvalidOperationException("Level One exception"); 315 | } catch (Exception exc) 316 | { 317 | throw new NotImplementedException("Nested Exception", exc); 318 | } 319 | } catch (Exception exc) 320 | { 321 | logger.Error(exc, "test exception with object {@test}", test); 322 | } 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/IntegrateSinkTestWithTcp.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Serilog.Events; 3 | using Serilog.Sinks.Graylog.Core.Helpers; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using Serilog.Sinks.Graylog.Tests.ComplexIntegrationTest; 6 | using System; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Serilog.Sinks.Graylog.Tests 12 | { 13 | [Trait("Category", "Integration")] 14 | public class IntegrateSinkTestWithTcp 15 | { 16 | [Fact] 17 | [Trait("Category", "Integration")] 18 | public void VerifyLoggerVerbocity() 19 | { 20 | var loggerConfig = new LoggerConfiguration(); 21 | 22 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 23 | { 24 | ShortMessageMaxLength = 50, 25 | MinimumLogEventLevel = LogEventLevel.Fatal, 26 | Facility = "VolkovTestFacility", 27 | HostnameOrAddress = "logs.aeroclub.int", 28 | Port = 12202, 29 | TransportType = TransportType.Tcp 30 | 31 | }); 32 | 33 | var logger = loggerConfig.CreateLogger(); 34 | 35 | var test = new TestClass 36 | { 37 | Id = 1, 38 | SomeTestDateTime = DateTime.UtcNow, 39 | Bar = new Bar 40 | { 41 | Id = 2, 42 | Prop = "123", 43 | TestBarBooleanProperty = false 44 | 45 | }, 46 | TestClassBooleanProperty = true, 47 | TestPropertyOne = "1", 48 | TestPropertyThree = "3", 49 | TestPropertyTwo = "2" 50 | }; 51 | 52 | logger.Information("SomeComplexTestEntry {@test}", test); 53 | 54 | logger.Debug("SomeComplexTestEntry {@test}", test); 55 | 56 | logger.Fatal("SomeComplexTestEntry {@test}", test); 57 | 58 | logger.Error("SomeComplexTestEntry {@test}", test); 59 | 60 | } 61 | 62 | [Fact] 63 | [Trait("Category", "Integration")] 64 | public void TestComplex() 65 | { 66 | var loggerConfig = new LoggerConfiguration(); 67 | 68 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 69 | { 70 | ShortMessageMaxLength = 50, 71 | MinimumLogEventLevel = LogEventLevel.Information, 72 | TransportType = TransportType.Tcp, 73 | Facility = "VolkovTestFacility", 74 | HostnameOrAddress = "logs.aeroclub.int", 75 | Port = 12202 76 | }); 77 | 78 | var logger = loggerConfig.CreateLogger(); 79 | 80 | var test = new TestClass 81 | { 82 | Id = 1, 83 | Type = "TCP", 84 | SomeTestDateTime = DateTime.UtcNow, 85 | Bar = new Bar 86 | { 87 | Id = 2, 88 | Prop = "123", 89 | TestBarBooleanProperty = false 90 | 91 | }, 92 | TestClassBooleanProperty = true, 93 | TestPropertyOne = "1", 94 | TestPropertyThree = "3", 95 | TestPropertyTwo = "2" 96 | }; 97 | 98 | logger.Information("SomeComplexTestEntry {@test}", test); 99 | } 100 | 101 | [Fact()] 102 | [Trait("Category", "Integration")] 103 | public Task SendManyMessages() 104 | { 105 | var fixture = new Fixture(); 106 | fixture.Behaviors.Clear(); 107 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 108 | var profiles = fixture.CreateMany(10).ToList(); 109 | 110 | foreach (var profile in profiles) 111 | { 112 | profile.Type = "TCP"; 113 | } 114 | 115 | var loggerConfig = new LoggerConfiguration(); 116 | 117 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 118 | { 119 | MinimumLogEventLevel = LogEventLevel.Information, 120 | MessageGeneratorType = MessageIdGeneratorType.Md5, 121 | TransportType = TransportType.Tcp, 122 | Facility = "VolkovTestFacility", 123 | HostnameOrAddress = "logs.aeroclub.int", 124 | Port = 12202 125 | }); 126 | 127 | var logger = loggerConfig.CreateLogger(); 128 | 129 | var tasks = profiles.Select(c => 130 | { 131 | return Task.Run(() => logger.Information("TestSend {@BattleProfile}", c)); 132 | }); 133 | 134 | 135 | return Task.WhenAll(tasks.ToArray()); 136 | } 137 | 138 | [Fact] 139 | [Trait("Category", "Integration")] 140 | public void TestSimple() 141 | { 142 | var fixture = new Fixture(); 143 | fixture.Behaviors.Clear(); 144 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 145 | var profile = fixture.Create(); 146 | 147 | var loggerConfig = new LoggerConfiguration(); 148 | 149 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 150 | { 151 | MinimumLogEventLevel = LogEventLevel.Information, 152 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 153 | TransportType = TransportType.Tcp, 154 | Facility = "VolkovTestFacility", 155 | HostnameOrAddress = "logs.aeroclub.int", 156 | Port = 12202 157 | }); 158 | 159 | var logger = loggerConfig.CreateLogger(); 160 | 161 | logger.Information("battle profile: {@BattleProfile}", profile); 162 | } 163 | 164 | [Fact] 165 | [Trait("Category", "Integration")] 166 | public void IncludeTemplate() 167 | { 168 | var fixture = new Fixture(); 169 | fixture.Behaviors.Clear(); 170 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 171 | var profile = fixture.Create(); 172 | 173 | var loggerConfig = new LoggerConfiguration(); 174 | 175 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 176 | { 177 | MinimumLogEventLevel = LogEventLevel.Information, 178 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 179 | TransportType = TransportType.Tcp, 180 | Facility = "VolkovTestFacility", 181 | HostnameOrAddress = "logs.aeroclub.int", 182 | Port = 12202, 183 | IncludeMessageTemplate = true 184 | }); 185 | 186 | var logger = loggerConfig.CreateLogger(); 187 | 188 | logger.Information("battle profile: {@BattleProfile}", profile); 189 | } 190 | 191 | [Fact] 192 | [Trait("Category", "Integration")] 193 | public void TestException() 194 | { 195 | var loggerConfig = new LoggerConfiguration(); 196 | 197 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 198 | { 199 | MinimumLogEventLevel = LogEventLevel.Information, 200 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 201 | TransportType = TransportType.Tcp, 202 | Facility = "VolkovTestFacility", 203 | HostnameOrAddress = "logs.aeroclub.int", 204 | Port = 12202 205 | }); 206 | 207 | var test = new TestClass 208 | { 209 | Id = 1, 210 | SomeTestDateTime = DateTime.UtcNow, 211 | Bar = new Bar 212 | { 213 | Id = 2 214 | }, 215 | TestPropertyOne = "1", 216 | TestPropertyThree = "3", 217 | TestPropertyTwo = "2" 218 | }; 219 | 220 | 221 | var logger = loggerConfig.CreateLogger(); 222 | 223 | try 224 | { 225 | try 226 | { 227 | throw new InvalidOperationException("Level One exception"); 228 | } catch (Exception exc) 229 | { 230 | throw new NotImplementedException("Nested Exception", exc); 231 | } 232 | } catch (Exception exc) 233 | { 234 | logger.Error(exc, "test exception with object {@test}", test); 235 | } 236 | } 237 | 238 | [Fact] 239 | [Trait("Category", "Integration")] 240 | public void SerializeEvent() 241 | { 242 | var loggerConfig = new LoggerConfiguration(); 243 | 244 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 245 | { 246 | MinimumLogEventLevel = LogEventLevel.Information, 247 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 248 | TransportType = TransportType.Tcp, 249 | Facility = "VolkovTestFacility", 250 | HostnameOrAddress = "logs.aeroclub.int", 251 | Port = 12202 252 | }); 253 | 254 | var payload = new Event("123"); 255 | 256 | var logger = loggerConfig.CreateLogger(); 257 | 258 | logger.Information("test event {@payload}, type:{type}", payload, "TCP"); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/IntegrateSinkTestWithUdp.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using Serilog.Events; 3 | using Serilog.Sinks.Graylog.Core.Helpers; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using Serilog.Sinks.Graylog.Tests.ComplexIntegrationTest; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text.Json; 10 | using System.Text.Json.Serialization; 11 | using System.Threading.Tasks; 12 | using Xunit; 13 | 14 | namespace Serilog.Sinks.Graylog.Tests 15 | { 16 | public enum TestEnumOne 17 | { 18 | One, Two, Three 19 | } 20 | 21 | [Trait("Category", "Integration")] 22 | public class IntegrateSinkTestWithUdp 23 | { 24 | [Fact] 25 | [Trait("Category", "Integration")] 26 | public void VerifyLoggerVerbocity() 27 | { 28 | var loggerConfig = new LoggerConfiguration(); 29 | 30 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 31 | { 32 | ShortMessageMaxLength = 50, 33 | MinimumLogEventLevel = LogEventLevel.Information, 34 | Facility = "edox-accounts", 35 | HostnameOrAddress = "msa-edor02-lg01", 36 | Port = 12209, 37 | UseGzip = false, 38 | JsonSerializerOptions = new JsonSerializerOptions 39 | { 40 | WriteIndented = true, 41 | Converters = { new JsonStringEnumConverter() } 42 | }, 43 | TransportType = TransportType.Udp 44 | }); 45 | 46 | var logger = loggerConfig.CreateLogger(); 47 | 48 | var test = new TestClass 49 | { 50 | Id = 1, 51 | SomeTestDateTime = DateTime.UtcNow, 52 | Bar = new Bar 53 | { 54 | Id = 2, 55 | Prop = "whirlwind", 56 | TestBarBooleanProperty = false, 57 | EnumVal = TestEnumOne.Three 58 | }, 59 | TestClassBooleanProperty = true, 60 | TestPropertyOne = "1", 61 | TestPropertyThree = "3", 62 | TestPropertyTwo = "2", 63 | EnumVal = TestEnumOne.Three 64 | }; 65 | logger.Error("SomeComplexTestEntry {@test}", test); 66 | logger.Information("SomeComplexTestEntry {@test}", test); 67 | logger.Fatal("SomeComplexTestEntry {@test}", test); 68 | } 69 | 70 | [Fact] 71 | [Trait("Category", "Integration")] 72 | public void TestArrays() 73 | { 74 | var loggerConfig = new LoggerConfiguration(); 75 | 76 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 77 | { 78 | ShortMessageMaxLength = 50, 79 | MinimumLogEventLevel = LogEventLevel.Information, 80 | Facility = "edox-accounts", 81 | HostnameOrAddress = "msa-edor02-lg01", 82 | Port = 12209, 83 | UseGzip = false, 84 | ParseArrayValues = true, 85 | JsonSerializerOptions = new JsonSerializerOptions 86 | { 87 | WriteIndented = true, 88 | Converters = { new JsonStringEnumConverter() } 89 | }, 90 | TransportType = TransportType.Udp 91 | }); 92 | 93 | var logValue = new 94 | { 95 | Bars = new List 96 | { 97 | new Bar 98 | { 99 | Id = 1, 100 | Prop = "1", 101 | TestBarBooleanProperty = true 102 | }, 103 | new Bar 104 | { 105 | Id = 2, 106 | Prop = "2", 107 | TestBarBooleanProperty = false 108 | }, 109 | new Bar 110 | { 111 | Id = 3, 112 | Prop = "3", 113 | TestBarBooleanProperty = true 114 | } 115 | } 116 | }; 117 | 118 | var logger = loggerConfig.CreateLogger(); 119 | 120 | logger.Error("SomeComplexTestEntry {@test}", logValue); 121 | 122 | } 123 | 124 | [Fact] 125 | [Trait("Category", "Integration")] 126 | public void TestArrays2() 127 | { 128 | var loggerConfig = new LoggerConfiguration(); 129 | 130 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 131 | { 132 | ShortMessageMaxLength = 50, 133 | MinimumLogEventLevel = LogEventLevel.Information, 134 | Facility = "edox-accounts", 135 | HostnameOrAddress = "msa-edor02-lg01", 136 | Port = 12209, 137 | UseGzip = false, 138 | ParseArrayValues = true, 139 | IncludeMessageTemplate = true, 140 | JsonSerializerOptions = new JsonSerializerOptions 141 | { 142 | WriteIndented = true, 143 | Converters = { new JsonStringEnumConverter() } 144 | }, 145 | TransportType = TransportType.Udp 146 | }); 147 | 148 | var test = new[] 149 | { 150 | new Bar 151 | { 152 | Id = 1, 153 | Prop = "1", 154 | TestBarBooleanProperty = true 155 | }, 156 | new Bar 157 | { 158 | Id = 2, 159 | Prop = "2", 160 | TestBarBooleanProperty = false 161 | }, 162 | new Bar 163 | { 164 | Id = 3, 165 | Prop = "3", 166 | TestBarBooleanProperty = true 167 | } 168 | }; 169 | 170 | var logger = loggerConfig.CreateLogger(); 171 | 172 | logger.Error("SomeComplexTestEntry {@test}", test); 173 | 174 | } 175 | 176 | [Fact] 177 | [Trait("Category", "Integration")] 178 | public void TestDictionary() 179 | { 180 | var loggerConfig = new LoggerConfiguration(); 181 | 182 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 183 | { 184 | ShortMessageMaxLength = 50, 185 | MinimumLogEventLevel = LogEventLevel.Information, 186 | HostnameOrAddress = "msa-edor02-lg01", 187 | Port = 12209, 188 | UseGzip = false, 189 | ParseArrayValues = false, 190 | IncludeMessageTemplate = true, 191 | JsonSerializerOptions = new JsonSerializerOptions 192 | { 193 | WriteIndented = true, 194 | }, 195 | TransportType = TransportType.Udp 196 | }); 197 | 198 | var response = new Dictionary() 199 | { 200 | [0] = new Bar 201 | { 202 | 203 | Id = 1, 204 | Prop = "1", 205 | TestBarBooleanProperty = true, 206 | EnumVal = TestEnumOne.One 207 | }, 208 | [1] = new Bar 209 | { 210 | Id = 2, 211 | Prop = "2", 212 | TestBarBooleanProperty = false, 213 | EnumVal = TestEnumOne.Two 214 | }, 215 | [2] = new Bar 216 | { 217 | Id = 3, 218 | Prop = "3", 219 | TestBarBooleanProperty = true, 220 | EnumVal = TestEnumOne.Three 221 | } 222 | }; 223 | 224 | var logger = loggerConfig.CreateLogger(); 225 | 226 | logger.Information("Ответ: {@CommandResponse}", (object)response); 227 | } 228 | 229 | [Fact] 230 | [Trait("Category", "Integration")] 231 | public void TestComplex() 232 | { 233 | var loggerConfig = new LoggerConfiguration(); 234 | 235 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 236 | { 237 | ShortMessageMaxLength = 50, 238 | MinimumLogEventLevel = LogEventLevel.Information, 239 | Facility = "VolkovTestFacility", 240 | HostnameOrAddress = "logs.aeroclub.int", 241 | Port = 12201 242 | }); 243 | 244 | var logger = loggerConfig.CreateLogger(); 245 | 246 | var test = new TestClass 247 | { 248 | Id = 1, 249 | Type = "UDP", 250 | SomeTestDateTime = DateTime.UtcNow, 251 | Bar = new Bar 252 | { 253 | Id = 2, 254 | Prop = "123", 255 | TestBarBooleanProperty = false 256 | 257 | }, 258 | TestClassBooleanProperty = true, 259 | TestPropertyOne = "1", 260 | TestPropertyThree = "3", 261 | TestPropertyTwo = "2" 262 | }; 263 | 264 | logger.Information("SomeComplexTestEntry {@test}", test); 265 | } 266 | 267 | [Fact()] 268 | [Trait("Category", "Integration")] 269 | public Task SendManyMessages() 270 | { 271 | var fixture = new Fixture(); 272 | fixture.Behaviors.Clear(); 273 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 274 | var profiles = fixture.CreateMany(10).ToList(); 275 | 276 | foreach (var profile in profiles) 277 | { 278 | profile.Type = "UDP"; 279 | } 280 | 281 | var loggerConfig = new LoggerConfiguration(); 282 | 283 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 284 | { 285 | MinimumLogEventLevel = LogEventLevel.Information, 286 | MessageGeneratorType = MessageIdGeneratorType.Md5, 287 | Facility = "VolkovTestFacility", 288 | HostnameOrAddress = "logs.aeroclub.int", 289 | Port = 12201 290 | }); 291 | 292 | var logger = loggerConfig.CreateLogger(); 293 | 294 | var tasks = profiles.Select(c => 295 | { 296 | return Task.Run(() => logger.Information("TestSend {@BattleProfile}", c)); 297 | }); 298 | 299 | 300 | return Task.WhenAll(tasks.ToArray()); 301 | } 302 | 303 | [Fact] 304 | [Trait("Category", "Integration")] 305 | public void TestSimple() 306 | { 307 | var fixture = new Fixture(); 308 | fixture.Behaviors.Clear(); 309 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 310 | var profile = fixture.Create(); 311 | 312 | var loggerConfig = new LoggerConfiguration(); 313 | 314 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 315 | { 316 | MinimumLogEventLevel = LogEventLevel.Information, 317 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 318 | Facility = "VolkovTestFacility", 319 | HostnameOrAddress = "logs.aeroclub.int", 320 | Port = 12201 321 | }); 322 | 323 | var logger = loggerConfig.CreateLogger(); 324 | 325 | logger.Information("battle profile: {@BattleProfile}", profile); 326 | } 327 | 328 | [Fact] 329 | [Trait("Category", "Integration")] 330 | public void IncludeTemplate() 331 | { 332 | var fixture = new Fixture(); 333 | fixture.Behaviors.Clear(); 334 | fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); 335 | var profile = fixture.Create(); 336 | 337 | var loggerConfig = new LoggerConfiguration(); 338 | 339 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 340 | { 341 | MinimumLogEventLevel = LogEventLevel.Information, 342 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 343 | Facility = "VolkovTestFacility", 344 | HostnameOrAddress = "logs.aeroclub.int", 345 | Port = 12201, 346 | IncludeMessageTemplate = true 347 | }); 348 | 349 | var logger = loggerConfig.CreateLogger(); 350 | 351 | logger.Information("battle profile: {@BattleProfile}", profile); 352 | } 353 | 354 | [Fact] 355 | [Trait("Category", "Integration")] 356 | public void TestException() 357 | { 358 | var loggerConfig = new LoggerConfiguration(); 359 | 360 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 361 | { 362 | MinimumLogEventLevel = LogEventLevel.Information, 363 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 364 | TransportType = TransportType.Udp, 365 | Facility = "VolkovTestFacility", 366 | HostnameOrAddress = "logs.aeroclub.int", 367 | Port = 12201 368 | }); 369 | 370 | var test = new TestClass 371 | { 372 | Id = 1, 373 | SomeTestDateTime = DateTime.UtcNow, 374 | Bar = new Bar 375 | { 376 | Id = 2 377 | }, 378 | TestPropertyOne = "1", 379 | TestPropertyThree = "3", 380 | TestPropertyTwo = "2" 381 | }; 382 | 383 | 384 | var logger = loggerConfig.CreateLogger(); 385 | 386 | try 387 | { 388 | try 389 | { 390 | throw new InvalidOperationException("Level One exception"); 391 | } catch (Exception exc) 392 | { 393 | throw new NotImplementedException("Nested Exception", exc); 394 | } 395 | } catch (Exception exc) 396 | { 397 | logger.Error(exc, "test exception with object {@test}", test); 398 | } 399 | } 400 | 401 | [Fact] 402 | [Trait("Category", "Integration")] 403 | public void SerializeEvent() 404 | { 405 | var loggerConfig = new LoggerConfiguration(); 406 | 407 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 408 | { 409 | MinimumLogEventLevel = LogEventLevel.Information, 410 | MessageGeneratorType = MessageIdGeneratorType.Timestamp, 411 | TransportType = TransportType.Udp, 412 | Facility = "VolkovTestFacility", 413 | HostnameOrAddress = "logs.aeroclub.int", 414 | Port = 12201 415 | }); 416 | 417 | var payload = new Event("123"); 418 | 419 | var logger = loggerConfig.CreateLogger(); 420 | 421 | logger.Information("test event {@payload}", payload); 422 | } 423 | } 424 | 425 | public class Event 426 | { 427 | public Event(string eventId) 428 | { 429 | EventId = eventId; 430 | Timestamp = DateTime.UtcNow; 431 | } 432 | 433 | public DateTime Timestamp { get; set; } 434 | 435 | public string EventId { get; set; } 436 | } 437 | 438 | public class Bar 439 | { 440 | public int Id { get; set; } 441 | public string Prop { get; set; } 442 | 443 | public bool TestBarBooleanProperty { get; set; } 444 | public TestEnumOne EnumVal { get; set; } 445 | } 446 | 447 | public class TestClass 448 | { 449 | public int Id { get; set; } 450 | 451 | public bool TestClassBooleanProperty { get; set; } 452 | 453 | public string TestPropertyOne { get; set; } 454 | 455 | public Bar Bar { get; set; } 456 | 457 | public string TestPropertyTwo { get; set; } 458 | 459 | public string TestPropertyThree { get; set; } 460 | public DateTime SomeTestDateTime { get; set; } 461 | public string Type { get; set; } 462 | public TestEnumOne EnumVal { get; set; } 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/LogEventSource.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Parsing; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Serilog.Sinks.Graylog.Tests 7 | { 8 | public class LogEventSource 9 | { 10 | public static LogEvent GetSimpleLogEvent(DateTimeOffset date) 11 | { 12 | var logEvent = new LogEvent(date, LogEventLevel.Information, null, 13 | new MessageTemplate("abcdef{TestProp}", new List 14 | { 15 | new TextToken("abcdef", 0), 16 | new PropertyToken("TestProp", "zxc", alignment:new Alignment(AlignmentDirection.Left, 3)) 17 | 18 | }), new List 19 | { 20 | new LogEventProperty("TestProp", new ScalarValue("zxc")), 21 | new LogEventProperty("id", new ScalarValue("asd")) 22 | }); 23 | return logEvent; 24 | } 25 | 26 | public static LogEvent GetErrorEvent(DateTimeOffset date) 27 | { 28 | var logEvent = new LogEvent(date, LogEventLevel.Information, new InvalidCastException("Some errror"), 29 | new MessageTemplate("", new List()), 30 | new List(new List())); 31 | return logEvent; 32 | } 33 | 34 | public static LogEvent GetComplexEvent(DateTimeOffset date) 35 | { 36 | var logEvent = new LogEvent(date, LogEventLevel.Information, null, 37 | new MessageTemplate("abcdef{TestProp}", new List 38 | { 39 | new TextToken("abcdef", 0), 40 | new PropertyToken("TestProp", "zxc", alignment:new Alignment(AlignmentDirection.Left, 3)) 41 | 42 | }), new List 43 | { 44 | new LogEventProperty("TestProp", new ScalarValue("zxc")), 45 | new LogEventProperty("id", new ScalarValue("asd")), 46 | new LogEventProperty("StructuredProperty", 47 | new StructureValue(new List 48 | { 49 | new LogEventProperty("id", new ScalarValue(1)), 50 | new LogEventProperty("_TestProp", new ScalarValue(3)), 51 | }, "TypeTag")) 52 | }); 53 | return logEvent; 54 | } 55 | 56 | public static LogEvent GetExceptionLogEvent(DateTimeOffset date, Exception testExc) 57 | { 58 | var logevent = new LogEvent(date, LogEventLevel.Error, testExc, new MessageTemplate("", new List()), 59 | new List(new List())); 60 | return logevent; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/LoggerConfigurationGrayLogExtensionsFixture.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.Configuration; 3 | using Serilog.Events; 4 | using Serilog.Sinks.Graylog.Core.Transport; 5 | using System.IO; 6 | using System.Reflection; 7 | using Xunit; 8 | 9 | namespace Serilog.Sinks.Graylog.Tests 10 | { 11 | public class LoggerConfigurationGrayLogExtensionsFixture 12 | { 13 | [Fact] 14 | public void CanApplyExtension() 15 | { 16 | var loggerConfig = new LoggerConfiguration(); 17 | 18 | loggerConfig.WriteTo.Graylog(new GraylogSinkOptions 19 | { 20 | MinimumLogEventLevel = LogEventLevel.Information, 21 | Facility = "VolkovTestFacility", 22 | HostnameOrAddress = "localhost", 23 | Port = 12201 24 | }); 25 | 26 | var logger = loggerConfig.CreateLogger(); 27 | logger.Should().NotBeNull(); 28 | } 29 | 30 | [Fact] 31 | public void CanApplyExtensionWithIntegralParameterTypes() 32 | { 33 | var loggerConfig = new LoggerConfiguration(); 34 | 35 | loggerConfig.WriteTo.Graylog("localhost", 12201, TransportType.Udp, false, 36 | LogEventLevel.Information); 37 | 38 | var logger = loggerConfig.CreateLogger(); 39 | logger.Should().NotBeNull(); 40 | } 41 | 42 | //[Fact(Skip="Integration test")] 43 | [Fact] 44 | public void CanReadHostPropertyConfiguration() 45 | { 46 | //arrange 47 | // 48 | IConfigurationRoot configuration; 49 | using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("Serilog.Sinks.Graylog.Tests.Configurations.AppSettingsWithGraylogSinkContainingHostProperty.json")) 50 | { 51 | configuration = new ConfigurationBuilder() 52 | .AddJsonStream(s) 53 | .Build(); 54 | } 55 | Log.Logger = new LoggerConfiguration() 56 | .ReadFrom.Configuration(configuration) 57 | .CreateLogger(); 58 | 59 | //act 60 | Log.Information("Hello {ApplicationName}.", "SerilogGraylogSink"); 61 | 62 | //assert 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/Serilog.Sinks.Graylog.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net7.0;net6.0 4 | full 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/SerilogExceptionsFixture.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog.Sinks.Graylog.Core.Transport; 3 | using System; 4 | using Xunit; 5 | 6 | namespace Serilog.Sinks.Graylog.Tests 7 | { 8 | public class SerilogExceptionsFixture 9 | { 10 | [Fact] 11 | [Trait("Category", "Integration")] 12 | public void WhenUseSerilogExceptions_ThenExceptionDetailsShouldBeSent() 13 | { 14 | var loggerConfig = new LoggerConfiguration(); 15 | 16 | loggerConfig 17 | //.Enrich.WithExceptionDetails() 18 | .WriteTo.Graylog(new GraylogSinkOptions 19 | { 20 | ShortMessageMaxLength = 50, 21 | MinimumLogEventLevel = LogEventLevel.Information, 22 | TransportType = TransportType.Http, 23 | Facility = "VolkovTestFacility", 24 | HostnameOrAddress = "http://logs.aeroclub.int", 25 | Port = 12201 26 | }); 27 | 28 | var logger = loggerConfig.CreateLogger(); 29 | 30 | try 31 | { 32 | throw new InvalidOperationException("Test exception"); 33 | } catch (Exception e) 34 | { 35 | var test = new TestClass 36 | { 37 | Id = 1, 38 | Bar = new Bar 39 | { 40 | Id = 2, 41 | Prop = "123" 42 | }, 43 | TestPropertyOne = "1", 44 | TestPropertyThree = "3", 45 | TestPropertyTwo = "2" 46 | }; 47 | 48 | logger.Error(e, "Exception {@entry}", test); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog.Tests/TestProfile/Profile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Serilog.Sinks.Graylog.Tests.ComplexIntegrationTest 5 | { 6 | public class Profile 7 | { 8 | public string Type { get; set; } 9 | public int Id { get; set; } 10 | public LocalizedString FirstName { get; set; } 11 | public LocalizedString MiddleName { get; set; } 12 | public LocalizedString LastName { get; set; } 13 | public DateTime? DateOfBirth { get; set; } 14 | public string Sex { get; set; } 15 | public Document[] Documents { get; set; } 16 | public Contacts Contacts { get; set; } 17 | public DateTime CreatedAt { get; set; } 18 | public DateTime? UpdatedAt { get; set; } 19 | public string Version { get; set; } 20 | public Company CompanyInfo { get; set; } 21 | public Location WorkLocationCity { get; set; } 22 | public string PhotoFingerprint { get; set; } 23 | 24 | public Preference Preference { get; set; } 25 | 26 | /// Gets or sets the additional information. 27 | /// The additional information. 28 | public IList AdditionalInfo { get; set; } 29 | } 30 | 31 | public class LocalizedString 32 | { 33 | public string Ru { get; set; } 34 | public string En { get; set; } 35 | } 36 | 37 | public class Document 38 | { 39 | public string Type { get; set; } 40 | public string Series { get; set; } 41 | public string Number { get; set; } 42 | public string PlaceOfBirth { get; set; } 43 | public DateTime? IssuedOn { get; set; } 44 | public DateTime? ExpiresOn { get; set; } 45 | public string FirstName { get; set; } 46 | public string LastName { get; set; } 47 | 48 | /// 49 | /// В рамках виз это используется как "страна, на которую выдана виза" 50 | /// В рамках остальных документов - пока никак. 51 | /// 52 | public Location CitizenshipCountry { get; set; } 53 | } 54 | 55 | public class Contacts 56 | { 57 | public EmailAddress[] EmailAddresses { get; set; } 58 | public PhoneNumberContact[] PhoneNumbers { get; set; } 59 | 60 | } 61 | 62 | public class EmailAddress 63 | { 64 | public string Type { get; set; } 65 | public string Address { get; set; } 66 | } 67 | 68 | public class PhoneNumberContact 69 | { 70 | public string Type { get; set; } 71 | public string CountryCode { get; set; } 72 | public string AreaCode { get; set; } 73 | public string PhoneNumber { get; set; } 74 | public string ExtensionNumber { get; set; } 75 | } 76 | 77 | public class Location 78 | { 79 | public string Type { get; set; } 80 | public LocalizedString Name { get; set; } 81 | public string Code { get; set; } 82 | 83 | public Location LocatedIn { get; set; } 84 | 85 | } 86 | 87 | public class Company 88 | { 89 | public LocalizedString CompanyName { get; set; } 90 | public LocalizedString HoldingName { get; set; } 91 | } 92 | 93 | public class Preference 94 | { 95 | public int Id { get; set; } 96 | public string Comments { get; set; } 97 | public string InternalComments { get; set; } 98 | public PreferenceInformation[] PreferenceInformations { get; set; } 99 | } 100 | 101 | public class PreferenceInformation 102 | { 103 | //public int Id { get; set; } 104 | public string Type { get; set; } 105 | public string Code { get; set; } 106 | public LocalizedString Name { get; set; } 107 | } 108 | 109 | public class AdditionalInfo 110 | { 111 | /// Gets or sets the name en. 112 | /// The name en. 113 | public string NameEn { get; set; } 114 | /// Gets or sets the name ru. 115 | /// The name ru. 116 | public string NameRu { get; set; } 117 | /// Gets or sets the value en. 118 | /// The value en. 119 | public string ValueEn { get; set; } 120 | /// Gets or sets the value ru. 121 | /// The value ru. 122 | public string ValueRu { get; set; } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/GraylogSink.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Debugging; 3 | using Serilog.Events; 4 | using Serilog.Sinks.Graylog.Core; 5 | using Serilog.Sinks.Graylog.Core.Transport; 6 | using System; 7 | using System.Text.Json; 8 | using System.Threading.Tasks; 9 | 10 | namespace Serilog.Sinks.Graylog 11 | { 12 | public sealed class GraylogSink : ILogEventSink, IDisposable 13 | { 14 | private readonly Lazy _converter; 15 | private readonly Lazy _transport; 16 | private readonly JsonSerializerOptions _options; 17 | 18 | public GraylogSink(GraylogSinkOptions options) 19 | { 20 | ISinkComponentsBuilder sinkComponentsBuilder = new SinkComponentsBuilder(options); 21 | 22 | var jsonSerializerOptions = options.JsonSerializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.General); 23 | _options = new JsonSerializerOptions(jsonSerializerOptions); 24 | 25 | _transport = new Lazy(sinkComponentsBuilder.MakeTransport); 26 | _converter = new Lazy(() => sinkComponentsBuilder.MakeGelfConverter()); 27 | } 28 | 29 | public void Emit(LogEvent logEvent) 30 | { 31 | EmitAsync(logEvent).ContinueWith( 32 | task => 33 | { 34 | SelfLog.WriteLine("Oops something going wrong {0}", task.Exception); 35 | }, 36 | TaskContinuationOptions.OnlyOnFaulted); 37 | } 38 | 39 | private Task EmitAsync(LogEvent logEvent) 40 | { 41 | var json = _converter.Value.GetGelfJson(logEvent); 42 | var payload = json.ToJsonString(_options); 43 | 44 | return _transport.Value.Send(payload); 45 | } 46 | 47 | public void Dispose() 48 | { 49 | _transport.Value.Dispose(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/GraylogSinkOptions.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.Graylog.Core; 2 | 3 | namespace Serilog.Sinks.Graylog 4 | { 5 | public class GraylogSinkOptions : GraylogSinkOptionsBase 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/LoggerConfigurationGrayLogExtensions.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Configuration; 2 | using Serilog.Core; 3 | using Serilog.Events; 4 | using Serilog.Sinks.Graylog.Core; 5 | using Serilog.Sinks.Graylog.Core.Extensions; 6 | using Serilog.Sinks.Graylog.Core.Helpers; 7 | using Serilog.Sinks.Graylog.Core.Transport; 8 | 9 | namespace Serilog.Sinks.Graylog 10 | { 11 | public static class LoggerConfigurationGrayLogExtensions 12 | { 13 | /// 14 | /// Graylogs the specified options. 15 | /// 16 | /// The logger sink configuration. 17 | /// The options. 18 | /// 19 | public static LoggerConfiguration Graylog(this LoggerSinkConfiguration loggerSinkConfiguration, GraylogSinkOptions options) 20 | { 21 | var sink = (ILogEventSink)new GraylogSink(options); 22 | return loggerSinkConfiguration.Sink(sink, options.MinimumLogEventLevel); 23 | } 24 | 25 | /// 26 | /// Graylogs the specified hostname or address. 27 | /// 28 | /// The logger sink configuration. 29 | /// The hostname or address. 30 | /// The port. 31 | /// Type of the transport. 32 | /// Use SSL in Tcp and Http 33 | /// The minimum log event level. 34 | /// Type of the message identifier generator. 35 | /// Short length of the message maximum. 36 | /// The stack trace depth. 37 | /// The facility. 38 | /// the maxMessageSizeInUdp 39 | /// The host property to use in GELF message. If null, DNS hostname will be used instead. 40 | /// if set to true if include message template to graylog. 41 | /// Name of the message template field. 42 | /// The usernameInHttp. Basic authentication property. 43 | /// The passwordInHttp. Basic authentication property. 44 | /// 45 | public static LoggerConfiguration Graylog(this LoggerSinkConfiguration loggerSinkConfiguration, 46 | string hostnameOrAddress, 47 | int port, 48 | TransportType transportType, 49 | bool useSsl = false, 50 | LogEventLevel minimumLogEventLevel = LevelAlias.Minimum, 51 | MessageIdGeneratorType messageIdGeneratorType = GraylogSinkOptionsBase.DefaultMessageGeneratorType, 52 | int shortMessageMaxLength = GraylogSinkOptionsBase.DefaultShortMessageMaxLength, 53 | int stackTraceDepth = GraylogSinkOptionsBase.DefaultStackTraceDepth, 54 | string? facility = GraylogSinkOptionsBase.DefaultFacility, 55 | int maxMessageSizeInUdp = GraylogSinkOptionsBase.DefaultMaxMessageSizeInUdp, 56 | string host = GraylogSinkOptionsBase.DefaultHost, 57 | bool includeMessageTemplate = false, 58 | string messageTemplateFieldName = GraylogSinkOptionsBase.DefaultMessageTemplateFieldName, 59 | string? usernameInHttp = null, 60 | string? passwordInHttp = null, 61 | bool parseArrayValues = false, 62 | bool useGzip = true 63 | ) 64 | { 65 | // ReSharper disable once UseObjectOrCollectionInitializer 66 | var options = new GraylogSinkOptions 67 | { 68 | HostnameOrAddress = hostnameOrAddress.Expand(), 69 | Port = port, 70 | TransportType = transportType, 71 | UseSsl = useSsl, 72 | MinimumLogEventLevel = minimumLogEventLevel, 73 | MessageGeneratorType = messageIdGeneratorType, 74 | ShortMessageMaxLength = shortMessageMaxLength, 75 | StackTraceDepth = stackTraceDepth, 76 | Facility = facility?.Expand(), 77 | MaxMessageSizeInUdp = maxMessageSizeInUdp, 78 | HostnameOverride = host, 79 | IncludeMessageTemplate = includeMessageTemplate, 80 | MessageTemplateFieldName = messageTemplateFieldName, 81 | UsernameInHttp = usernameInHttp, 82 | PasswordInHttp = passwordInHttp, 83 | ParseArrayValues = parseArrayValues, 84 | UseGzip = useGzip 85 | }; 86 | 87 | return loggerSinkConfiguration.Graylog(options); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/Serilog.Sinks.Graylog.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 11.0 4 | 5 | 6 | net7.0;net6.0;netstandard2.0 7 | 8 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage 9 | true 10 | full 11 | enable 12 | true 13 | 14 | Serilog.Sinks.Graylog 15 | Serilog.Sinks.Graylog 16 | Anton Volkov and Contributors 17 | The Serilog Graylog Sink project is a sink (basically a writer) for the Serilog logging framework. Structured log events are written to sinks and each sink is responsible for writing it to its own backend, database, store etc. This sink delivers the data to Graylog2, a NoSQL search engine. 18 | 19 | https://github.com/serilog-contrib/serilog-sinks-graylog 20 | https://github.com/serilog-contrib/serilog-sinks-graylog 21 | serilog-sink-nuget.png 22 | 23 | git 24 | Serilog Sink Graylog 25 | Anton Volkov Copyright © 2023 26 | en 27 | 28 | 3.1.1 29 | 3.0.0.0 30 | 3.0.0.0 31 | true 32 | 33 | sign.snk 34 | 35 | MIT 36 | Delete newtonsoft.json dependency and refactoring 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | README.md 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/Serilog.Sinks.GraylogSingleTarget.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net46 4 | true 5 | 6 | 7 | library 8 | Serilog.Sinks.Graylog 9 | Serilog.Sinks.Graylog 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/Serilog.Sinks.GraylogSingleTarget.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Graylog/sign.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/src/Serilog.Sinks.Graylog/sign.snk -------------------------------------------------------------------------------- /src/TestApplication/CustomException.cs: -------------------------------------------------------------------------------- 1 | namespace TestApplication; 2 | 3 | public class CustomException : Exception 4 | { 5 | public string? CustomExceptionValue { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/TestApplication/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Debugging; 3 | using Serilog.Exceptions; 4 | using TestApplication; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | SelfLog.Enable(Console.WriteLine); 8 | builder.Services.AddEndpointsApiExplorer(); 9 | builder.Services.AddSwaggerGen(); 10 | 11 | builder.Host.UseSerilog((context, config) => 12 | { 13 | var serilogSection = context.Configuration.GetSection("Serilog"); 14 | config.ReadFrom.Configuration(context.Configuration); 15 | }); 16 | 17 | var app = builder.Build(); 18 | 19 | if (app.Environment.IsDevelopment()) 20 | { 21 | app.UseSwagger(); 22 | app.UseSwaggerUI(); 23 | } 24 | 25 | app.UseHttpsRedirection(); 26 | 27 | var summaries = new[] 28 | { 29 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 30 | }; 31 | 32 | app.MapPost("/simple_send", () => 33 | { 34 | var seriloglogger = Log.ForContext(); 35 | 36 | var forecast = Enumerable.Range(1, 5).Select(index => 37 | new WeatherForecast 38 | ( 39 | DateTime.Now.AddDays(index), 40 | Random.Shared.Next(-20, 55), 41 | summaries[Random.Shared.Next(summaries.Length)] 42 | )) 43 | .ToArray(); 44 | seriloglogger.Information("SomeInf {@Forecast}", forecast); 45 | 46 | return forecast; 47 | }) 48 | .WithName("simple_send"); 49 | 50 | app.MapPost("simple_exception", () => 51 | { 52 | var seriloglogger = Log.ForContext(); 53 | try 54 | { 55 | throw new Exception("test"); 56 | } 57 | catch (Exception ex) 58 | { 59 | seriloglogger.Error(ex, ex.Message); 60 | } 61 | 62 | }).WithName("simple_exception"); 63 | 64 | app.MapPost("custom_exception", () => 65 | { 66 | var seriloglogger = Log.ForContext(); 67 | try 68 | { 69 | throw new CustomException() 70 | { 71 | CustomExceptionValue = "test_custom_exception_value", 72 | Data = { ["SomeData"] = "SomeValue" } 73 | }; 74 | } 75 | catch (Exception ex) 76 | { 77 | seriloglogger.Error(ex, ex.Message); 78 | } 79 | 80 | }).WithName("custom_exception"); 81 | 82 | 83 | app.Run(); 84 | 85 | internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) 86 | { 87 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 88 | } 89 | -------------------------------------------------------------------------------- /src/TestApplication/TestApplication.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 10 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/TestApplication/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "Serilog": { 4 | "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Graylog" ], 5 | "MinimumLevel": { 6 | "Default": "Information", 7 | "Override": { 8 | "Microsoft": "Information", 9 | "System": "Warning" 10 | } 11 | }, 12 | "WriteTo": [ 13 | { "Name": "Console" }, 14 | { 15 | "Name": "Graylog", 16 | "Args": { 17 | "hostnameOrAddress": "localhost", 18 | "port": "12205", 19 | "transportType": "Udp" 20 | } 21 | } 22 | ], 23 | "Properties": { 24 | "Application": "GraylogIssueDemo" 25 | } 26 | }, 27 | 28 | "AllowedHosts": "*" 29 | } -------------------------------------------------------------------------------- /src/serilog-sink-nuget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog-contrib/serilog-sinks-graylog/64532fa1aab7a8b3dc5c94e3294a570d6698e77d/src/serilog-sink-nuget.png -------------------------------------------------------------------------------- /src/serilog-sinks-graylog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29215.179 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Graylog", "Serilog.Sinks.Graylog\Serilog.Sinks.Graylog.csproj", "{8FADC837-9237-4447-B6CE-A0FE3806610C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Graylog.Tests", "Serilog.Sinks.Graylog.Tests\Serilog.Sinks.Graylog.Tests.csproj", "{3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Graylog.Core", "Serilog.Sinks.Graylog.Core\Serilog.Sinks.Graylog.Core.csproj", "{5A3A6717-4252-444F-BA49-86F4AFD53027}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Graylog.Batching", "Serilog.Sinks.Graylog.Batching\Serilog.Sinks.Graylog.Batching.csproj", "{BFCDD1CB-3B20-40F9-87C3-392514398C5B}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Graylog.Core.Tests", "Serilog.Sinks.Graylog.Core.Tests\Serilog.Sinks.Graylog.Core.Tests.csproj", "{27F75D09-070E-4E18-BE81-1433BE8A5209}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication", "TestApplication\TestApplication.csproj", "{08E240AD-E2DF-40AA-B2CD-63693674BCCF}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Debug|x64 = Debug|x64 22 | Debug|x86 = Debug|x86 23 | Release|Any CPU = Release|Any CPU 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|x64.ActiveCfg = Debug|Any CPU 31 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|x64.Build.0 = Debug|Any CPU 32 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|x86.ActiveCfg = Debug|Any CPU 33 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Debug|x86.Build.0 = Debug|Any CPU 34 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|x64.ActiveCfg = Release|Any CPU 37 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|x64.Build.0 = Release|Any CPU 38 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|x86.ActiveCfg = Release|Any CPU 39 | {8FADC837-9237-4447-B6CE-A0FE3806610C}.Release|x86.Build.0 = Release|Any CPU 40 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|x64.Build.0 = Debug|Any CPU 44 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Debug|x86.Build.0 = Debug|Any CPU 46 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|x64.ActiveCfg = Release|Any CPU 49 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|x64.Build.0 = Release|Any CPU 50 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|x86.ActiveCfg = Release|Any CPU 51 | {3C61B1BA-FCC8-4589-90BA-08FBF1CCE68B}.Release|x86.Build.0 = Release|Any CPU 52 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|x64.ActiveCfg = Debug|Any CPU 55 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|x64.Build.0 = Debug|Any CPU 56 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|x86.ActiveCfg = Debug|Any CPU 57 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Debug|x86.Build.0 = Debug|Any CPU 58 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|x64.ActiveCfg = Release|Any CPU 61 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|x64.Build.0 = Release|Any CPU 62 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|x86.ActiveCfg = Release|Any CPU 63 | {5A3A6717-4252-444F-BA49-86F4AFD53027}.Release|x86.Build.0 = Release|Any CPU 64 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|x64.ActiveCfg = Debug|Any CPU 67 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|x64.Build.0 = Debug|Any CPU 68 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|x86.ActiveCfg = Debug|Any CPU 69 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Debug|x86.Build.0 = Debug|Any CPU 70 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|x64.ActiveCfg = Release|Any CPU 73 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|x64.Build.0 = Release|Any CPU 74 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|x86.ActiveCfg = Release|Any CPU 75 | {BFCDD1CB-3B20-40F9-87C3-392514398C5B}.Release|x86.Build.0 = Release|Any CPU 76 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|x64.ActiveCfg = Debug|Any CPU 79 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|x64.Build.0 = Debug|Any CPU 80 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|x86.ActiveCfg = Debug|Any CPU 81 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Debug|x86.Build.0 = Debug|Any CPU 82 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|x64.ActiveCfg = Release|Any CPU 85 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|x64.Build.0 = Release|Any CPU 86 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|x86.ActiveCfg = Release|Any CPU 87 | {27F75D09-070E-4E18-BE81-1433BE8A5209}.Release|x86.Build.0 = Release|Any CPU 88 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|x64.ActiveCfg = Debug|Any CPU 91 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|x64.Build.0 = Debug|Any CPU 92 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|x86.ActiveCfg = Debug|Any CPU 93 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Debug|x86.Build.0 = Debug|Any CPU 94 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|x64.ActiveCfg = Release|Any CPU 97 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|x64.Build.0 = Release|Any CPU 98 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|x86.ActiveCfg = Release|Any CPU 99 | {08E240AD-E2DF-40AA-B2CD-63693674BCCF}.Release|x86.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(ExtensibilityGlobals) = postSolution 105 | SolutionGuid = {559CB3DB-A789-4A50-88A2-BDA4348B40C7} 106 | EndGlobalSection 107 | EndGlobal 108 | --------------------------------------------------------------------------------