├── CHANGES.md ├── .editorconfig ├── .gitattributes ├── assets ├── icon.png ├── Serilog.snk ├── Screenshot.png └── Serilog.svg ├── .github ├── .DS_Store ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ └── ci.yml ├── Directory.Version.props ├── CODE_OF_CONDUCT.md ├── .idea └── .idea.serilog-sinks-console │ └── .idea │ ├── encodings.xml │ ├── vcs.xml │ ├── indexLayout.xml │ └── .gitignore ├── sample ├── ConsoleDemo │ ├── ConsoleDemo.csproj │ └── Program.cs └── SyncWritesDemo │ ├── SyncWritesDemo.csproj │ └── Program.cs ├── test └── Serilog.Sinks.Console.Tests │ ├── Support │ ├── TracingConsoleTheme.cs │ └── DelegatingSink.cs │ ├── Formatting │ ├── ThemedDisplayValueFormatterTests.cs │ └── ThemedJsonValueFormatterTests.cs │ ├── Approval │ ├── ApiApprovalTests.cs │ └── Serilog.Sinks.Console.approved.txt │ ├── Serilog.Sinks.Console.Tests.csproj │ ├── Configuration │ ├── ConsoleLoggerConfigurationExtensionsTests.cs │ └── ConsoleAuditLoggerConfigurationExtensionsTests.cs │ ├── Rendering │ └── ThemedMessageTemplateRendererTests.cs │ └── Output │ └── OutputTemplateRendererTests.cs ├── src └── Serilog.Sinks.Console │ ├── Sinks │ └── SystemConsole │ │ ├── Output │ │ ├── OutputTemplateTokenRenderer.cs │ │ ├── SpanIdTokenRenderer.cs │ │ ├── TraceIdTokenRenderer.cs │ │ ├── TextTokenRenderer.cs │ │ ├── NewLineTokenRenderer.cs │ │ ├── ExceptionTokenRenderer.cs │ │ ├── LevelTokenRenderer.cs │ │ ├── EventPropertyTokenRenderer.cs │ │ ├── MessageTemplateOutputTokenRenderer.cs │ │ ├── PropertiesTokenRenderer.cs │ │ ├── OutputTemplateRenderer.cs │ │ ├── LevelOutputFormat.cs │ │ └── TimestampTokenRenderer.cs │ │ ├── Rendering │ │ ├── AlignmentExtensions.cs │ │ ├── Casing.cs │ │ ├── Padding.cs │ │ └── ThemedMessageTemplateRenderer.cs │ │ ├── Formatting │ │ ├── ThemedValueFormatterState.cs │ │ ├── ThemedValueFormatter.cs │ │ ├── ThemedDisplayValueFormatter.cs │ │ └── ThemedJsonValueFormatter.cs │ │ ├── Themes │ │ ├── EmptyConsoleTheme.cs │ │ ├── StyleReset.cs │ │ ├── SystemConsoleThemeStyle.cs │ │ ├── AnsiEscapeSequence.cs │ │ ├── ConsoleTheme.cs │ │ ├── SystemConsoleTheme.cs │ │ ├── ConsoleThemeStyle.cs │ │ ├── AnsiConsoleTheme.cs │ │ ├── AnsiConsoleThemes.cs │ │ └── SystemConsoleThemes.cs │ │ ├── Platform │ │ └── WindowsConsole.cs │ │ └── ConsoleSink.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Serilog.Sinks.Console.csproj │ ├── ConsoleAuditLoggerConfigurationExtensions.cs │ └── ConsoleLoggerConfigurationExtensions.cs ├── Directory.Build.props ├── CONTRIBUTING.md ├── Build.ps1 ├── serilog-sinks-console.sln ├── .gitignore ├── README.md └── LICENSE /CHANGES.md: -------------------------------------------------------------------------------- 1 | See: https://github.com/serilog/serilog-sinks-console/releases 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-console/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.github/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-console/HEAD/.github/.DS_Store -------------------------------------------------------------------------------- /assets/Serilog.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-console/HEAD/assets/Serilog.snk -------------------------------------------------------------------------------- /assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serilog/serilog-sinks-console/HEAD/assets/Screenshot.png -------------------------------------------------------------------------------- /Directory.Version.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6.1.2 4 | 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Please refer to the [Serilog Code of Conduct](https://github.com/serilog/serilog/blob/dev/CODE_OF_CONDUCT.md) which covers all repositories within the Serilog Organization. 4 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-console/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-console/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-console/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/ConsoleDemo/ConsoleDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/SyncWritesDemo/SyncWritesDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/.idea.serilog-sinks-console/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /.idea.serilog-sinks-console.iml 7 | /modules.xml 8 | /projectSettingsUpdater.xml 9 | # Datasource local storage ignored files 10 | /dataSources/ 11 | /dataSources.local.xml 12 | # Editor-based HTTP Client requests 13 | /httpRequests/ 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What issue does this PR address?** 2 | *Please list any GitHub issues here* 3 | 4 | **Does this PR introduce a breaking change?** 5 | *Please list any changes that may cause issues for users, including binary breaking changes* 6 | 7 | **Please check if the PR fulfills these requirements** 8 | - [ ] The commit follows our [guidelines](https://github.com/serilog/serilog-sinks-console/blob/dev/CONTRIBUTING.md) 9 | - [ ] Unit Tests for the changes have been added (for bug fixes / features) 10 | 11 | **Other information**: 12 | *Please list any other relevant information here* 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi 👋 2 | 3 | If you have a question about using Serilog, or something isn't working as you expect it to, please post your question to the [`serilog` tag on Stack Overflow](https://stackoverflow.com/questions/tagged/serilog), where many more people are ready to help you out. 4 | 5 | To report a bug or request a feature, please give us as much information as possible, for example: 6 | 7 | - [ ] the exact package id and version you're using, 8 | - [ ] your `dotnet` toolchain version, target framework, and operating system, 9 | - [ ] the current behavior, and 10 | - [ ] what you expect or want to happen instead. 11 | 12 | Thanks! 13 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Support/TracingConsoleTheme.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Serilog.Sinks.SystemConsole.Themes; 3 | 4 | namespace Serilog.Sinks.SystemConsole.Tests; 5 | 6 | class TracingConsoleTheme : ConsoleTheme 7 | { 8 | const string End = ""; 9 | 10 | public override bool CanBuffer => true; 11 | 12 | protected override int ResetCharCount { get; } = End.Length; 13 | 14 | public override int Set(TextWriter output, ConsoleThemeStyle style) 15 | { 16 | var start = $"<{style.ToString().ToLowerInvariant()}>"; 17 | output.Write(start); 18 | return start.Length; 19 | } 20 | 21 | public override void Reset(TextWriter output) 22 | { 23 | output.Write(End); 24 | } 25 | } -------------------------------------------------------------------------------- /sample/ConsoleDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Sinks.SystemConsole.Themes; 3 | using System; 4 | using System.Threading; 5 | 6 | Log.Logger = new LoggerConfiguration() 7 | .MinimumLevel.Verbose() 8 | .WriteTo.Console(theme: AnsiConsoleTheme.Code) 9 | .CreateLogger(); 10 | 11 | try 12 | { 13 | Log.Debug("Getting started"); 14 | 15 | Log.Information("Hello {Name} from thread {ThreadId}", Environment.GetEnvironmentVariable("USERNAME"), Thread.CurrentThread.ManagedThreadId); 16 | 17 | Log.Warning("No coins remain at position {@Position}", new { Lat = 25, Long = 134 }); 18 | 19 | Fail(); 20 | } 21 | catch (Exception e) 22 | { 23 | Log.Error(e, "Something went wrong"); 24 | } 25 | 26 | await Log.CloseAndFlushAsync(); 27 | 28 | static void Fail() 29 | { 30 | throw new DivideByZeroException(); 31 | } 32 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Formatting/ThemedDisplayValueFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Serilog.Events; 3 | using Serilog.Sinks.SystemConsole.Formatting; 4 | using Serilog.Sinks.SystemConsole.Themes; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.Console.Tests.Formatting; 8 | 9 | public class ThemedDisplayValueFormatterTests 10 | { 11 | [Theory] 12 | [InlineData("Hello", null, "\"Hello\"")] 13 | [InlineData("Hello", "l", "Hello")] 14 | public void StringFormattingIsApplied(string value, string format, string expected) 15 | { 16 | var formatter = new ThemedDisplayValueFormatter(ConsoleTheme.None, null); 17 | var sw = new StringWriter(); 18 | formatter.FormatLiteralValue(new ScalarValue(value), sw, format); 19 | var actual = sw.ToString(); 20 | Assert.Equal(expected, actual); 21 | } 22 | } -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Support/DelegatingSink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Serilog.Core; 3 | using Serilog.Events; 4 | 5 | namespace Serilog.Sinks.Console.Tests.Support; 6 | 7 | public class DelegatingSink : ILogEventSink 8 | { 9 | readonly Action _write; 10 | 11 | public DelegatingSink(Action write) 12 | { 13 | _write = write ?? throw new ArgumentNullException(nameof(write)); 14 | } 15 | 16 | public void Emit(LogEvent logEvent) 17 | { 18 | _write(logEvent); 19 | } 20 | 21 | public static LogEvent GetLogEvent(Action writeAction) 22 | { 23 | LogEvent? result = null; 24 | var logger = new LoggerConfiguration() 25 | .MinimumLevel.Verbose() 26 | .WriteTo.Sink(new DelegatingSink(le => result = le)) 27 | .CreateLogger(); 28 | 29 | writeAction(logger); 30 | return result!; 31 | } 32 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | using Serilog.Events; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Output; 19 | 20 | abstract class OutputTemplateTokenRenderer 21 | { 22 | public abstract void Render(LogEvent logEvent, TextWriter output); 23 | } -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Approval/ApiApprovalTests.cs: -------------------------------------------------------------------------------- 1 | #if NET5_0 2 | 3 | using PublicApiGenerator; 4 | using Shouldly; 5 | using Xunit; 6 | 7 | namespace Serilog.Sinks.Console.Tests.Approval 8 | { 9 | public class ApiApprovalTests 10 | { 11 | [Fact] 12 | public void PublicApi_Should_Not_Change_Unintentionally() 13 | { 14 | var assembly = typeof(ConsoleLoggerConfigurationExtensions).Assembly; 15 | var publicApi = assembly.GeneratePublicApi( 16 | new ApiGeneratorOptions() 17 | { 18 | IncludeAssemblyAttributes = false, 19 | ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" }, 20 | }); 21 | 22 | publicApi.ShouldMatchApproved(options => options.WithFilenameGenerator((_, __, fileType, fileExtension) => $"{assembly.GetName().Name!}.{fileType}.{fileExtension}")); 23 | } 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Serilog.Sinks.Console.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net9.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Rendering/AlignmentExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Serilog.Parsing; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Rendering; 18 | 19 | static class AlignmentExtensions 20 | { 21 | public static Alignment Widen(this Alignment alignment, int amount) 22 | { 23 | return new Alignment(alignment.Direction, alignment.Width + amount); 24 | } 25 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Formatting/ThemedValueFormatterState.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Formatting; 18 | 19 | struct ThemedValueFormatterState 20 | { 21 | public TextWriter Output; 22 | public string? Format; 23 | public bool IsTopLevel; 24 | 25 | public ThemedValueFormatterState Nest() => new ThemedValueFormatterState { Output = Output }; 26 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/SpanIdTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Serilog.Events; 3 | using Serilog.Parsing; 4 | using Serilog.Sinks.SystemConsole.Rendering; 5 | using Serilog.Sinks.SystemConsole.Themes; 6 | 7 | namespace Serilog.Sinks.SystemConsole.Output; 8 | 9 | class SpanIdTokenRenderer : OutputTemplateTokenRenderer 10 | { 11 | readonly ConsoleTheme _theme; 12 | readonly Alignment? _alignment; 13 | 14 | public SpanIdTokenRenderer(ConsoleTheme theme, PropertyToken spanIdToken) 15 | { 16 | _theme = theme; 17 | _alignment = spanIdToken.Alignment; 18 | } 19 | 20 | public override void Render(LogEvent logEvent, TextWriter output) 21 | { 22 | if (logEvent.SpanId is not { } spanId) 23 | return; 24 | 25 | var _ = 0; 26 | using (_theme.Apply(output, ConsoleThemeStyle.Text, ref _)) 27 | { 28 | if (_alignment is {} alignment) 29 | Padding.Apply(output, spanId.ToString(), alignment); 30 | else 31 | output.Write(spanId); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TraceIdTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Serilog.Events; 3 | using Serilog.Parsing; 4 | using Serilog.Sinks.SystemConsole.Rendering; 5 | using Serilog.Sinks.SystemConsole.Themes; 6 | 7 | namespace Serilog.Sinks.SystemConsole.Output; 8 | 9 | class TraceIdTokenRenderer : OutputTemplateTokenRenderer 10 | { 11 | readonly ConsoleTheme _theme; 12 | readonly Alignment? _alignment; 13 | 14 | public TraceIdTokenRenderer(ConsoleTheme theme, PropertyToken traceIdToken) 15 | { 16 | _theme = theme; 17 | _alignment = traceIdToken.Alignment; 18 | } 19 | 20 | public override void Render(LogEvent logEvent, TextWriter output) 21 | { 22 | if (logEvent.TraceId is not { } traceId) 23 | return; 24 | 25 | var _ = 0; 26 | using (_theme.Apply(output, ConsoleThemeStyle.Text, ref _)) 27 | { 28 | if (_alignment is {} alignment) 29 | Padding.Apply(output, traceId.ToString(), alignment); 30 | else 31 | output.Write(traceId); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/EmptyConsoleTheme.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Themes; 18 | 19 | class EmptyConsoleTheme : ConsoleTheme 20 | { 21 | public override bool CanBuffer => true; 22 | 23 | protected override int ResetCharCount { get; } 24 | 25 | public override int Set(TextWriter output, ConsoleThemeStyle style) => 0; 26 | 27 | public override void Reset(TextWriter output) 28 | { 29 | } 30 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/StyleReset.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Themes; 19 | 20 | struct StyleReset : IDisposable 21 | { 22 | readonly ConsoleTheme _theme; 23 | readonly TextWriter _output; 24 | 25 | public StyleReset(ConsoleTheme theme, TextWriter output) 26 | { 27 | _theme = theme; 28 | _output = output; 29 | } 30 | 31 | public void Dispose() 32 | { 33 | _theme.Reset(_output); 34 | } 35 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # If this file is renamed, the incrementing run attempt number will be reset. 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | branches: [ "dev", "main" ] 8 | pull_request: 9 | branches: [ "dev", "main" ] 10 | 11 | env: 12 | CI_BUILD_NUMBER_BASE: ${{ github.run_number }} 13 | CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }} 14 | 15 | jobs: 16 | build: 17 | 18 | # The build must run on Windows so that .NET Framework targets can be built and tested. 19 | runs-on: windows-latest 20 | 21 | permissions: 22 | contents: write 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Setup 27 | uses: actions/setup-dotnet@v4 28 | with: 29 | dotnet-version: 9.0.x 30 | - name: Compute build number 31 | shell: bash 32 | run: | 33 | echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+2300))" >> $GITHUB_ENV 34 | - name: Build and Publish 35 | env: 36 | DOTNET_CLI_TELEMETRY_OPTOUT: true 37 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 38 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | shell: pwsh 40 | run: | 41 | ./Build.ps1 42 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/SystemConsoleThemeStyle.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Themes; 18 | 19 | /// 20 | /// Styling applied using the enumeration. 21 | /// 22 | public struct SystemConsoleThemeStyle 23 | { 24 | /// 25 | /// The foreground color to apply. 26 | /// 27 | public ConsoleColor? Foreground; 28 | 29 | /// 30 | /// The background color to apply. 31 | /// 32 | public ConsoleColor? Background; 33 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Runtime.CompilerServices; 17 | 18 | [assembly: CLSCompliant(true)] 19 | 20 | [assembly: InternalsVisibleTo("Serilog.Sinks.Console.Tests, PublicKey=" + 21 | "0024000004800000940000000602000000240000525341310004000001000100fb8d13fd344a1c" + 22 | "6fe0fe83ef33c1080bf30690765bc6eb0df26ebfdf8f21670c64265b30db09f73a0dea5b3db4c9" + 23 | "d18dbf6d5a25af5ce9016f281014d79dc3b4201ac646c451830fc7e61a2dfd633d34c39f87b818" + 24 | "94191652df5ac63cc40c77f3542f702bda692e6e8a9158353df189007a49da0f3cfd55eb250066" + 25 | "b19485ec")] 26 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TextTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | using Serilog.Events; 17 | using Serilog.Sinks.SystemConsole.Themes; 18 | 19 | namespace Serilog.Sinks.SystemConsole.Output; 20 | 21 | class TextTokenRenderer : OutputTemplateTokenRenderer 22 | { 23 | readonly ConsoleTheme _theme; 24 | readonly string _text; 25 | 26 | public TextTokenRenderer(ConsoleTheme theme, string text) 27 | { 28 | _theme = theme; 29 | _text = text; 30 | } 31 | 32 | public override void Render(LogEvent logEvent, TextWriter output) 33 | { 34 | var _ = 0; 35 | using (_theme.Apply(output, ConsoleThemeStyle.TertiaryText, ref _)) 36 | output.Write(_text); 37 | } 38 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | latest 7 | True 8 | 9 | true 10 | $(MSBuildThisFileDirectory)assets/Serilog.snk 11 | false 12 | enable 13 | enable 14 | true 15 | true 16 | true 17 | true 18 | snupkg 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/NewLineTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Serilog.Events; 18 | using Serilog.Parsing; 19 | using Serilog.Sinks.SystemConsole.Rendering; 20 | 21 | namespace Serilog.Sinks.SystemConsole.Output; 22 | 23 | class NewLineTokenRenderer : OutputTemplateTokenRenderer 24 | { 25 | readonly Alignment? _alignment; 26 | 27 | public NewLineTokenRenderer(Alignment? alignment) 28 | { 29 | _alignment = alignment; 30 | } 31 | 32 | public override void Render(LogEvent logEvent, TextWriter output) 33 | { 34 | if (_alignment.HasValue) 35 | Padding.Apply(output, Environment.NewLine, _alignment.Value.Widen(Environment.NewLine.Length)); 36 | else 37 | output.WriteLine(); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Rendering/Casing.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Serilog.Sinks.SystemConsole.Rendering; 16 | 17 | static class Casing 18 | { 19 | /// 20 | /// Apply upper or lower casing to when is provided. 21 | /// Returns when no or invalid format provided. 22 | /// 23 | /// Provided string for formatting. 24 | /// Format string. 25 | /// The provided with formatting applied. 26 | public static string Format(string value, string? format = null) 27 | { 28 | switch (format) 29 | { 30 | case "u": 31 | return value.ToUpperInvariant(); 32 | case "w": 33 | return value.ToLowerInvariant(); 34 | default: 35 | return value; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/AnsiEscapeSequence.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace Serilog.Sinks.SystemConsole.Themes; 16 | 17 | static class AnsiEscapeSequence 18 | { 19 | public const string Unthemed = ""; 20 | public const string Reset = "\x1b[0m"; 21 | public const string Bold = "\x1b[1m"; 22 | 23 | public const string Black = "\x1b[30m"; 24 | public const string Red = "\x1b[31m"; 25 | public const string Green = "\x1b[32m"; 26 | public const string Yellow = "\x1b[33m"; 27 | public const string Blue = "\x1b[34m"; 28 | public const string Magenta = "\x1b[35m"; 29 | public const string Cyan = "\x1b[36m"; 30 | public const string White = "\x1b[37m"; 31 | 32 | public const string BrightBlack = "\x1b[30;1m"; 33 | public const string BrightRed = "\x1b[31;1m"; 34 | public const string BrightGreen = "\x1b[32;1m"; 35 | public const string BrightYellow = "\x1b[33;1m"; 36 | public const string BrightBlue = "\x1b[34;1m"; 37 | public const string BrightMagenta = "\x1b[35;1m"; 38 | public const string BrightCyan = "\x1b[36;1m"; 39 | public const string BrightWhite = "\x1b[37;1m"; 40 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Formatting/ThemedValueFormatter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Serilog.Data; 18 | using Serilog.Events; 19 | using Serilog.Sinks.SystemConsole.Themes; 20 | 21 | namespace Serilog.Sinks.SystemConsole.Formatting; 22 | 23 | abstract class ThemedValueFormatter : LogEventPropertyValueVisitor 24 | { 25 | readonly ConsoleTheme _theme; 26 | 27 | protected ThemedValueFormatter(ConsoleTheme theme) 28 | { 29 | _theme = theme ?? throw new ArgumentNullException(nameof(theme)); 30 | } 31 | 32 | protected StyleReset ApplyStyle(TextWriter output, ConsoleThemeStyle style, ref int invisibleCharacterCount) 33 | { 34 | return _theme.Apply(output, style, ref invisibleCharacterCount); 35 | } 36 | 37 | public int Format(LogEventPropertyValue value, TextWriter output, string? format, bool literalTopLevel = false) 38 | { 39 | return Visit(new ThemedValueFormatterState { Output = output, Format = format, IsTopLevel = literalTopLevel }, value); 40 | } 41 | 42 | public abstract ThemedValueFormatter SwitchTheme(ConsoleTheme theme); 43 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | A Serilog sink that writes log events to the console/terminal. 4 | Serilog Contributors 5 | net462;net471 6 | $(TargetFrameworks);netstandard2.0;net6.0;net8.0 7 | serilog;console;terminal 8 | icon.png 9 | https://github.com/serilog/serilog-sinks-console 10 | Apache-2.0 11 | Serilog 12 | README.md 13 | 14 | 15 | 16 | $(DefineConstants);RUNTIME_INFORMATION 17 | 18 | 19 | 20 | $(DefineConstants);FEATURE_SPAN 21 | 22 | 23 | 24 | $(DefineConstants);FEATURE_SPAN 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/ExceptionTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | using Serilog.Events; 17 | using Serilog.Parsing; 18 | using Serilog.Sinks.SystemConsole.Themes; 19 | 20 | namespace Serilog.Sinks.SystemConsole.Output; 21 | 22 | class ExceptionTokenRenderer : OutputTemplateTokenRenderer 23 | { 24 | const string StackFrameLinePrefix = " "; 25 | 26 | readonly ConsoleTheme _theme; 27 | 28 | public ExceptionTokenRenderer(ConsoleTheme theme) 29 | { 30 | _theme = theme; 31 | } 32 | 33 | public override void Render(LogEvent logEvent, TextWriter output) 34 | { 35 | // Padding is never applied by this renderer. 36 | 37 | if (logEvent.Exception is null) 38 | return; 39 | 40 | var lines = new StringReader(logEvent.Exception.ToString()); 41 | string? nextLine; 42 | while ((nextLine = lines.ReadLine()) != null) 43 | { 44 | var style = nextLine.StartsWith(StackFrameLinePrefix) ? ConsoleThemeStyle.SecondaryText : ConsoleThemeStyle.Text; 45 | var _ = 0; 46 | using (_theme.Apply(output, style, ref _)) 47 | output.Write(nextLine); 48 | output.WriteLine(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /sample/SyncWritesDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Sinks.SystemConsole.Themes; 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | Console.WriteLine("A sample of how to sync writes to the console sink."); 7 | 8 | if (args is { Length: 1 }) 9 | { 10 | switch (args[0]) 11 | { 12 | case "--sync-root-default": 13 | SystemConsoleSyncTest(syncRootForLogger1: null, syncRootForLogger2: null); 14 | return; 15 | case "--sync-root-separate": 16 | SystemConsoleSyncTest(syncRootForLogger1: new object(), syncRootForLogger2: new object()); 17 | return; 18 | case "--sync-root-same": 19 | var sameSyncRoot = new object(); 20 | SystemConsoleSyncTest(syncRootForLogger1: sameSyncRoot, syncRootForLogger2: sameSyncRoot); 21 | return; 22 | } 23 | } 24 | 25 | Console.WriteLine("Expecting one of the following arguments:{0}--sync-root-default{0}--sync-root-separate{0}--sync-root-same", Environment.NewLine); 26 | 27 | static void SystemConsoleSyncTest(object? syncRootForLogger1, object? syncRootForLogger2) 28 | { 29 | var logger1 = new LoggerConfiguration() 30 | .MinimumLevel.Verbose() 31 | .Enrich.WithProperty("Logger", "logger1") 32 | .WriteTo.Console(theme: SystemConsoleTheme.Literate, syncRoot: syncRootForLogger1) 33 | .CreateLogger(); 34 | 35 | var logger2 = new LoggerConfiguration() 36 | .MinimumLevel.Verbose() 37 | .Enrich.WithProperty("Logger", "logger2") 38 | .WriteTo.Console(theme: SystemConsoleTheme.Literate, syncRoot: syncRootForLogger2) 39 | .CreateLogger(); 40 | 41 | var options = new ParallelOptions { MaxDegreeOfParallelism = 8 }; 42 | Parallel.For(0, 1000, options, (i, _) => 43 | { 44 | var logger = i % 2 == 0 ? logger1 : logger2; 45 | logger.Information("Event {Iteration} generated by {ThreadId}", i, Environment.CurrentManagedThreadId); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Platform/WindowsConsole.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Runtime.InteropServices; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Platform; 19 | 20 | static class WindowsConsole 21 | { 22 | public static void EnableVirtualTerminalProcessing() 23 | { 24 | #if RUNTIME_INFORMATION 25 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 26 | return; 27 | #else 28 | if (Environment.OSVersion.Platform != PlatformID.Win32NT) 29 | return; 30 | #endif 31 | var stdout = GetStdHandle(StandardOutputHandleId); 32 | if (stdout != (IntPtr)InvalidHandleValue && GetConsoleMode(stdout, out var mode)) 33 | { 34 | SetConsoleMode(stdout, mode | EnableVirtualTerminalProcessingMode); 35 | } 36 | } 37 | 38 | const int StandardOutputHandleId = -11; 39 | const uint EnableVirtualTerminalProcessingMode = 4; 40 | const long InvalidHandleValue = -1; 41 | 42 | [DllImport("kernel32.dll", SetLastError = true)] 43 | static extern IntPtr GetStdHandle(int handleId); 44 | 45 | [DllImport("kernel32.dll", SetLastError = true)] 46 | static extern bool GetConsoleMode(IntPtr handle, out uint mode); 47 | 48 | [DllImport("kernel32.dll", SetLastError = true)] 49 | static extern bool SetConsoleMode(IntPtr handle, uint mode); 50 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Rendering/Padding.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | using Serilog.Parsing; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Rendering; 19 | 20 | static class Padding 21 | { 22 | static readonly char[] PaddingChars = new string(' ', 80).ToCharArray(); 23 | 24 | /// 25 | /// Writes the provided value to the output, applying direction-based padding when is provided. 26 | /// 27 | /// Output object to write result. 28 | /// Provided value. 29 | /// The alignment settings to apply when rendering . 30 | public static void Apply(TextWriter output, string value, Alignment? alignment) 31 | { 32 | if (alignment is null || value.Length >= alignment.Value.Width) 33 | { 34 | output.Write(value); 35 | return; 36 | } 37 | 38 | var pad = alignment.Value.Width - value.Length; 39 | 40 | if (alignment.Value.Direction == AlignmentDirection.Left) 41 | output.Write(value); 42 | 43 | if (pad <= PaddingChars.Length) 44 | { 45 | output.Write(PaddingChars, 0, pad); 46 | } 47 | else 48 | { 49 | output.Write(new string(' ', pad)); 50 | } 51 | 52 | if (alignment.Value.Direction == AlignmentDirection.Right) 53 | output.Write(value); 54 | } 55 | } -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Configuration/ConsoleLoggerConfigurationExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Xunit; 5 | using Serilog.Sinks.SystemConsole.Themes; 6 | 7 | namespace Serilog.Sinks.Console.Tests.Configuration; 8 | 9 | [Collection("ConsoleSequentialTests")] 10 | public class ConsoleLoggerConfigurationExtensionsTests 11 | { 12 | [Fact] 13 | public void OutputFormattingIsIgnored() 14 | { 15 | using (var stream = new MemoryStream()) 16 | { 17 | var sw = new StreamWriter(stream); 18 | 19 | System.Console.SetOut(sw); 20 | var config = new LoggerConfiguration() 21 | .WriteTo.Console(theme: AnsiConsoleTheme.Literate, 22 | applyThemeToRedirectedOutput: false); 23 | 24 | var logger = config.CreateLogger(); 25 | 26 | logger.Error("test"); 27 | stream.Position = 0; 28 | 29 | using (var streamReader = new StreamReader(stream)) 30 | { 31 | var result = streamReader.ReadToEnd(); 32 | var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c)); 33 | Assert.Equal(0, controlCharacterCount); 34 | } 35 | } 36 | } 37 | 38 | [Fact] 39 | public void OutputFormattingIsPresent() 40 | { 41 | using (var stream = new MemoryStream()) 42 | { 43 | var sw = new StreamWriter(stream); 44 | 45 | System.Console.SetOut(sw); 46 | var config = new LoggerConfiguration() 47 | .WriteTo.Console(theme: AnsiConsoleTheme.Literate, 48 | applyThemeToRedirectedOutput: true); 49 | 50 | var logger = config.CreateLogger(); 51 | 52 | logger.Error("test"); 53 | stream.Position = 0; 54 | 55 | using (var streamReader = new StreamReader(stream)) 56 | { 57 | var result = streamReader.ReadToEnd(); 58 | var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c)); 59 | Assert.NotEqual(0, controlCharacterCount); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Configuration/ConsoleAuditLoggerConfigurationExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Xunit; 5 | using Serilog.Sinks.SystemConsole.Themes; 6 | 7 | namespace Serilog.Sinks.Console.Tests.Configuration; 8 | 9 | [Collection("ConsoleSequentialTests")] 10 | public class ConsoleAuditLoggerConfigurationExtensionsTests 11 | { 12 | [Fact] 13 | public void OutputFormattingIsIgnored() 14 | { 15 | using (var stream = new MemoryStream()) 16 | { 17 | var sw = new StreamWriter(stream); 18 | 19 | System.Console.SetOut(sw); 20 | var config = new LoggerConfiguration() 21 | .AuditTo.Console(theme: AnsiConsoleTheme.Literate, 22 | applyThemeToRedirectedOutput: false); 23 | 24 | var logger = config.CreateLogger(); 25 | 26 | logger.Error("test"); 27 | stream.Position = 0; 28 | 29 | using (var streamReader = new StreamReader(stream)) 30 | { 31 | var result = streamReader.ReadToEnd(); 32 | var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c)); 33 | Assert.Equal(0, controlCharacterCount); 34 | } 35 | } 36 | } 37 | 38 | [Fact] 39 | public void OutputFormattingIsPresent() 40 | { 41 | using (var stream = new MemoryStream()) 42 | { 43 | var sw = new StreamWriter(stream); 44 | 45 | System.Console.SetOut(sw); 46 | var config = new LoggerConfiguration() 47 | .AuditTo.Console(theme: AnsiConsoleTheme.Literate, 48 | applyThemeToRedirectedOutput: true); 49 | 50 | var logger = config.CreateLogger(); 51 | 52 | logger.Error("test"); 53 | stream.Position = 0; 54 | 55 | using (var streamReader = new StreamReader(stream)) 56 | { 57 | var result = streamReader.ReadToEnd(); 58 | var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c)); 59 | Assert.NotEqual(0, controlCharacterCount); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Collections.Generic; 16 | using System.IO; 17 | using Serilog.Events; 18 | using Serilog.Parsing; 19 | using Serilog.Sinks.SystemConsole.Rendering; 20 | using Serilog.Sinks.SystemConsole.Themes; 21 | 22 | namespace Serilog.Sinks.SystemConsole.Output; 23 | 24 | class LevelTokenRenderer : OutputTemplateTokenRenderer 25 | { 26 | readonly ConsoleTheme _theme; 27 | readonly PropertyToken _levelToken; 28 | 29 | static readonly Dictionary Levels = new Dictionary 30 | { 31 | { LogEventLevel.Verbose, ConsoleThemeStyle.LevelVerbose }, 32 | { LogEventLevel.Debug, ConsoleThemeStyle.LevelDebug }, 33 | { LogEventLevel.Information, ConsoleThemeStyle.LevelInformation }, 34 | { LogEventLevel.Warning, ConsoleThemeStyle.LevelWarning }, 35 | { LogEventLevel.Error, ConsoleThemeStyle.LevelError }, 36 | { LogEventLevel.Fatal, ConsoleThemeStyle.LevelFatal }, 37 | }; 38 | 39 | public LevelTokenRenderer(ConsoleTheme theme, PropertyToken levelToken) 40 | { 41 | _theme = theme; 42 | _levelToken = levelToken; 43 | } 44 | 45 | public override void Render(LogEvent logEvent, TextWriter output) 46 | { 47 | var moniker = LevelOutputFormat.GetLevelMoniker(logEvent.Level, _levelToken.Format); 48 | if (!Levels.TryGetValue(logEvent.Level, out var levelStyle)) 49 | levelStyle = ConsoleThemeStyle.Invalid; 50 | 51 | var _ = 0; 52 | using (_theme.Apply(output, levelStyle, ref _)) 53 | Padding.Apply(output, moniker, _levelToken.Alignment); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/ConsoleTheme.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.IO; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Themes; 18 | 19 | /// 20 | /// The base class for styled terminal output. 21 | /// 22 | public abstract class ConsoleTheme 23 | { 24 | /// 25 | /// No styling applied. 26 | /// 27 | public static ConsoleTheme None { get; } = new EmptyConsoleTheme(); 28 | 29 | /// 30 | /// True if styling applied by the theme is written into the output, and can thus be 31 | /// buffered and measured. 32 | /// 33 | public abstract bool CanBuffer { get; } 34 | 35 | /// 36 | /// Begin a span of text in the specified . 37 | /// 38 | /// Output destination. 39 | /// Style to apply. 40 | /// The number of characters written to . 41 | public abstract int Set(TextWriter output, ConsoleThemeStyle style); 42 | 43 | /// 44 | /// Reset the output to un-styled colors. 45 | /// 46 | /// Output destination. 47 | public abstract void Reset(TextWriter output); 48 | 49 | /// 50 | /// The number of characters written by the method. 51 | /// 52 | protected abstract int ResetCharCount { get; } 53 | 54 | internal StyleReset Apply(TextWriter output, ConsoleThemeStyle style, ref int invisibleCharacterCount) 55 | { 56 | invisibleCharacterCount += Set(output, style); 57 | invisibleCharacterCount += ResetCharCount; 58 | 59 | return new StyleReset(this, output); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/EventPropertyTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Serilog.Events; 18 | using Serilog.Parsing; 19 | using Serilog.Sinks.SystemConsole.Rendering; 20 | using Serilog.Sinks.SystemConsole.Themes; 21 | 22 | namespace Serilog.Sinks.SystemConsole.Output; 23 | 24 | class EventPropertyTokenRenderer : OutputTemplateTokenRenderer 25 | { 26 | readonly ConsoleTheme _theme; 27 | readonly PropertyToken _token; 28 | readonly IFormatProvider? _formatProvider; 29 | 30 | public EventPropertyTokenRenderer(ConsoleTheme theme, PropertyToken token, IFormatProvider? formatProvider) 31 | { 32 | _theme = theme; 33 | _token = token; 34 | _formatProvider = formatProvider; 35 | } 36 | 37 | public override void Render(LogEvent logEvent, TextWriter output) 38 | { 39 | // If a property is missing, don't render anything (message templates render the raw token here). 40 | if (!logEvent.Properties.TryGetValue(_token.PropertyName, out var propertyValue)) 41 | { 42 | Padding.Apply(output, string.Empty, _token.Alignment); 43 | return; 44 | } 45 | 46 | var _ = 0; 47 | using (_theme.Apply(output, ConsoleThemeStyle.SecondaryText, ref _)) 48 | { 49 | var writer = _token.Alignment.HasValue ? new StringWriter() : output; 50 | 51 | // If the value is a scalar string, support some additional formats: 'u' for uppercase 52 | // and 'w' for lowercase. 53 | if (propertyValue is ScalarValue sv && sv.Value is string literalString) 54 | { 55 | var cased = Casing.Format(literalString, _token.Format); 56 | writer.Write(cased); 57 | } 58 | else 59 | { 60 | propertyValue.Render(writer, _token.Format, _formatProvider); 61 | } 62 | 63 | if (_token.Alignment.HasValue) 64 | { 65 | var str = writer.ToString()!; 66 | Padding.Apply(output, str, _token.Alignment); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/MessageTemplateOutputTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Serilog.Events; 18 | using Serilog.Parsing; 19 | using Serilog.Sinks.SystemConsole.Formatting; 20 | using Serilog.Sinks.SystemConsole.Rendering; 21 | using Serilog.Sinks.SystemConsole.Themes; 22 | 23 | namespace Serilog.Sinks.SystemConsole.Output; 24 | 25 | class MessageTemplateOutputTokenRenderer : OutputTemplateTokenRenderer 26 | { 27 | readonly ConsoleTheme _theme; 28 | readonly PropertyToken _token; 29 | readonly ThemedMessageTemplateRenderer _renderer; 30 | 31 | public MessageTemplateOutputTokenRenderer(ConsoleTheme theme, PropertyToken token, IFormatProvider? formatProvider) 32 | { 33 | _theme = theme ?? throw new ArgumentNullException(nameof(theme)); 34 | _token = token ?? throw new ArgumentNullException(nameof(token)); 35 | 36 | bool isLiteral = false, isJson = false; 37 | 38 | if (token.Format != null) 39 | { 40 | for (var i = 0; i < token.Format.Length; ++i) 41 | { 42 | if (token.Format[i] == 'l') 43 | isLiteral = true; 44 | else if (token.Format[i] == 'j') 45 | isJson = true; 46 | } 47 | } 48 | 49 | var valueFormatter = isJson 50 | ? (ThemedValueFormatter)new ThemedJsonValueFormatter(theme, formatProvider) 51 | : new ThemedDisplayValueFormatter(theme, formatProvider); 52 | 53 | _renderer = new ThemedMessageTemplateRenderer(theme, valueFormatter, isLiteral); 54 | } 55 | 56 | public override void Render(LogEvent logEvent, TextWriter output) 57 | { 58 | if (_token.Alignment is null || !_theme.CanBuffer) 59 | { 60 | _renderer.Render(logEvent.MessageTemplate, logEvent.Properties, output); 61 | return; 62 | } 63 | 64 | var buffer = new StringWriter(); 65 | var invisible = _renderer.Render(logEvent.MessageTemplate, logEvent.Properties, buffer); 66 | var value = buffer.ToString(); 67 | Padding.Apply(output, value, _token.Alignment.Value.Widen(invisible)); 68 | } 69 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Serilog Console Sink 2 | 3 | First of all thanks for dropping by, feel free to checkout [Serilog core project's contributing page](https://github.com/serilog/serilog/blob/dev/CONTRIBUTING.md) which contains some key points about the organisation. 4 | 5 | ## Reporting an issue 6 | 7 | Bugs are tracked via [GitHub][issue_list] issues. Below are some notes to help create a new issue. The issue template will help you on the way. 8 | 9 | * Create an issue via the [issues list][create_issue]. 10 | * List the version of Serilog that is affected 11 | * List the target framework and operating system 12 | * If possible, provide a sample that reproduces the issue. 13 | 14 | ## Requesting a feature/enhancement 15 | 16 | Feature as also tracked via [GitHub][issue_list] issues. Below are some notes to help create an issue. The issue template will help you on the way 17 | 18 | * Create an issue via the [issues list][create_issue]. 19 | * List the version of Serilog that is affected 20 | * List the target framework and operating system 21 | * If possible, provide a sample that reproduces the issue. 22 | 23 | ## Making a PR 24 | 25 | * If an issue does not already exist please create one via the issues list. 26 | * Fork the repository and create a branch with a descriptive name. 27 | * Attempt to make commits of logical units. 28 | * When committing, please reference the issue the commit relates to. 29 | * Run the build and tests. 30 | * Windows platforms can use the `build.ps1` script. (This is the script used in AppVeyor builds) 31 | * nix/OSX platforms can use the `build.sh` script. (This is the script used in Travis builds) 32 | * Create the PR, the PR template will help provide a stub of what information is required including: 33 | * The issue this PR addresses 34 | * Unit Tests for the changes have been added. 35 | 36 | ## Questions? 37 | 38 | Serilog has an active and helpful community who are happy to help point you in the right direction or work through any issues you might encounter. You can get in touch via: 39 | 40 | * [Stack Overflow](http://stackoverflow.com/questions/tagged/serilog) - this is the best place to start if you have a question 41 | * Our [issue tracker](https://github.com/serilog/serilog/issues) here on GitHub 42 | * [Gitter chat](https://gitter.im/serilog/serilog) 43 | * The [#serilog tag on Twitter](https://twitter.com/search?q=%23serilog) 44 | 45 | Finally when contributing please keep in mind our [Code of Conduct][serilog_code_of_conduct]. 46 | 47 | [serilog]: https://github.com/serilog/serilog 48 | [sinks]: https://github.com/serilog/serilog/wiki/Provided-Sinks 49 | [community_projects]: https://github.com/serilog/serilog/wiki/Community-Projects 50 | [create_issue]: https://github.com/serilog/serilog-sinks-console/issues/new 51 | [issue_list]: https://github.com/serilog/serilog-sinks-console/issues/ 52 | [serilog_code_of_conduct]: https://github.com/serilog/serilog/blob/dev/CODE_OF_CONDUCT.md 53 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | Write-Output "build: Tool versions follow" 2 | 3 | dotnet --version 4 | dotnet --list-sdks 5 | 6 | Write-Output "build: Build started" 7 | 8 | Push-Location $PSScriptRoot 9 | try { 10 | if(Test-Path .\artifacts) { 11 | Write-Output "build: Cleaning ./artifacts" 12 | Remove-Item ./artifacts -Force -Recurse 13 | } 14 | 15 | & dotnet restore --no-cache 16 | 17 | $dbp = [Xml] (Get-Content .\Directory.Version.props) 18 | $versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix 19 | 20 | Write-Output "build: Package version prefix is $versionPrefix" 21 | 22 | $branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH]; 23 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER]; 24 | $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"] 25 | $commitHash = $(git rev-parse --short HEAD) 26 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] 27 | 28 | Write-Output "build: Package version suffix is $suffix" 29 | Write-Output "build: Build version suffix is $buildSuffix" 30 | 31 | & dotnet build -c Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true 32 | if($LASTEXITCODE -ne 0) { throw "Build failed" } 33 | 34 | foreach ($src in Get-ChildItem src/*) { 35 | Push-Location $src 36 | 37 | Write-Output "build: Packaging project in $src" 38 | 39 | if ($suffix) { 40 | & dotnet pack -c Release --no-build --no-restore -o ../../artifacts --version-suffix=$suffix 41 | } else { 42 | & dotnet pack -c Release --no-build --no-restore -o ../../artifacts 43 | } 44 | if($LASTEXITCODE -ne 0) { throw "Packaging failed" } 45 | 46 | Pop-Location 47 | } 48 | 49 | foreach ($test in Get-ChildItem test/*.Tests) { 50 | Push-Location $test 51 | 52 | Write-Output "build: Testing project in $test" 53 | 54 | & dotnet test -c Release --no-build --no-restore 55 | if($LASTEXITCODE -ne 0) { throw "Testing failed" } 56 | 57 | Pop-Location 58 | } 59 | 60 | if ($env:NUGET_API_KEY) { 61 | # GitHub Actions will only supply this to branch builds and not PRs. We publish 62 | # builds from any branch this action targets (i.e. main and dev). 63 | 64 | Write-Output "build: Publishing NuGet packages" 65 | 66 | foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) { 67 | & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg" 68 | if($LASTEXITCODE -ne 0) { throw "Publishing failed" } 69 | } 70 | 71 | if (!($suffix)) { 72 | Write-Output "build: Creating release for version $versionPrefix" 73 | 74 | iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)" 75 | } 76 | } 77 | } finally { 78 | Pop-Location 79 | } 80 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/SystemConsoleTheme.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using System.Linq; 19 | 20 | namespace Serilog.Sinks.SystemConsole.Themes; 21 | 22 | /// 23 | /// A console theme using the styling facilities of the class. Recommended 24 | /// for Windows versions prior to Windows 10. 25 | /// 26 | public class SystemConsoleTheme : ConsoleTheme 27 | { 28 | /// 29 | /// A theme using only gray, black and white. 30 | /// 31 | public static SystemConsoleTheme Grayscale { get; } = SystemConsoleThemes.Grayscale; 32 | 33 | /// 34 | /// A theme in the style of the original Serilog.Sinks.Literate. 35 | /// 36 | public static SystemConsoleTheme Literate { get; } = SystemConsoleThemes.Literate; 37 | 38 | /// 39 | /// A theme based on the original Serilog "colored console" sink. 40 | /// 41 | public static SystemConsoleTheme Colored { get; } = SystemConsoleThemes.Colored; 42 | 43 | /// 44 | /// Construct a theme given a set of styles. 45 | /// 46 | /// Styles to apply within the theme. 47 | /// When is null 48 | public SystemConsoleTheme(IReadOnlyDictionary styles) 49 | { 50 | if (styles is null) throw new ArgumentNullException(nameof(styles)); 51 | Styles = styles.ToDictionary(kv => kv.Key, kv => kv.Value); 52 | } 53 | 54 | /// 55 | public IReadOnlyDictionary Styles { get; } 56 | 57 | /// 58 | public override bool CanBuffer => false; 59 | 60 | /// 61 | protected override int ResetCharCount { get; } 62 | 63 | /// 64 | public override int Set(TextWriter output, ConsoleThemeStyle style) 65 | { 66 | if (Styles.TryGetValue(style, out var wcts)) 67 | { 68 | if (wcts.Foreground.HasValue) 69 | Console.ForegroundColor = wcts.Foreground.Value; 70 | if (wcts.Background.HasValue) 71 | Console.BackgroundColor = wcts.Background.Value; 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | /// 78 | public override void Reset(TextWriter output) 79 | { 80 | Console.ResetColor(); 81 | } 82 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/ConsoleThemeStyle.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.ComponentModel; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Themes; 19 | 20 | /// 21 | /// Elements styled by a console theme. 22 | /// 23 | public enum ConsoleThemeStyle 24 | { 25 | /// 26 | /// Prominent text, generally content within an event's message. 27 | /// 28 | Text, 29 | 30 | /// 31 | /// Boilerplate text, for example items specified in an output template. 32 | /// 33 | SecondaryText, 34 | 35 | /// 36 | /// De-emphasized text, for example literal text in output templates and 37 | /// punctuation used when writing structured data. 38 | /// 39 | TertiaryText, 40 | 41 | /// 42 | /// Output demonstrating some kind of configuration issue, e.g. an invalid 43 | /// message template token. 44 | /// 45 | Invalid, 46 | 47 | /// 48 | /// The built-in value. 49 | /// 50 | Null, 51 | 52 | /// 53 | /// Property and type names. 54 | /// 55 | Name, 56 | 57 | /// 58 | /// Strings. 59 | /// 60 | String, 61 | 62 | /// 63 | /// Numbers. 64 | /// 65 | Number, 66 | 67 | /// 68 | /// values. 69 | /// 70 | Boolean, 71 | 72 | /// 73 | /// All other scalar values, e.g. instances. 74 | /// 75 | Scalar, 76 | 77 | /// 78 | /// Unrecognized literal values, e.g. instances. 79 | /// 80 | [Obsolete("Use ConsoleThemeStyle.Scalar instead")] 81 | [EditorBrowsable(EditorBrowsableState.Never)] 82 | Object = Scalar, 83 | 84 | /// 85 | /// Level indicator. 86 | /// 87 | LevelVerbose, 88 | 89 | /// 90 | /// Level indicator. 91 | /// 92 | LevelDebug, 93 | 94 | /// 95 | /// Level indicator. 96 | /// 97 | LevelInformation, 98 | 99 | /// 100 | /// Level indicator. 101 | /// 102 | LevelWarning, 103 | 104 | /// 105 | /// Level indicator. 106 | /// 107 | LevelError, 108 | 109 | /// 110 | /// Level indicator. 111 | /// 112 | LevelFatal, 113 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/ConsoleSink.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Serilog.Core; 16 | using Serilog.Events; 17 | using Serilog.Formatting; 18 | using Serilog.Sinks.SystemConsole.Platform; 19 | using Serilog.Sinks.SystemConsole.Themes; 20 | using System; 21 | using System.IO; 22 | using System.Text; 23 | 24 | namespace Serilog.Sinks.SystemConsole; 25 | 26 | class ConsoleSink : ILogEventSink 27 | { 28 | readonly LogEventLevel? _standardErrorFromLevel; 29 | readonly ConsoleTheme _theme; 30 | readonly ITextFormatter _formatter; 31 | readonly object _syncRoot; 32 | 33 | const int DefaultWriteBufferCapacity = 256; 34 | 35 | static ConsoleSink() 36 | { 37 | WindowsConsole.EnableVirtualTerminalProcessing(); 38 | } 39 | 40 | public ConsoleSink( 41 | ConsoleTheme theme, 42 | ITextFormatter formatter, 43 | LogEventLevel? standardErrorFromLevel, 44 | object syncRoot) 45 | { 46 | _standardErrorFromLevel = standardErrorFromLevel; 47 | _theme = theme ?? throw new ArgumentNullException(nameof(theme)); 48 | _formatter = formatter; 49 | _syncRoot = syncRoot ?? throw new ArgumentNullException(nameof(syncRoot)); 50 | } 51 | 52 | public void Emit(LogEvent logEvent) 53 | { 54 | var output = SelectOutputStream(logEvent.Level); 55 | 56 | // ANSI escape codes can be pre-rendered into a buffer; however, if we're on Windows and 57 | // using its console coloring APIs, the color switches would happen during the off-screen 58 | // buffered write here and have no effect when the line is actually written out. 59 | if (_theme.CanBuffer) 60 | { 61 | var buffer = new StringWriter(new StringBuilder(DefaultWriteBufferCapacity)); 62 | _formatter.Format(logEvent, buffer); 63 | var formattedLogEventText = buffer.ToString(); 64 | lock (_syncRoot) 65 | { 66 | output.Write(formattedLogEventText); 67 | output.Flush(); 68 | } 69 | } 70 | else 71 | { 72 | lock (_syncRoot) 73 | { 74 | _formatter.Format(logEvent, output); 75 | output.Flush(); 76 | } 77 | } 78 | } 79 | 80 | TextWriter SelectOutputStream(LogEventLevel logEventLevel) 81 | { 82 | if (_standardErrorFromLevel is null) 83 | return Console.Out; 84 | 85 | return logEventLevel < _standardErrorFromLevel ? Console.Out : Console.Error; 86 | } 87 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/AnsiConsoleTheme.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using System.Linq; 19 | 20 | namespace Serilog.Sinks.SystemConsole.Themes; 21 | 22 | /// 23 | /// A console theme using the ANSI terminal escape sequences. Recommended 24 | /// for Linux and Windows 10+. 25 | /// 26 | public class AnsiConsoleTheme : ConsoleTheme 27 | { 28 | /// 29 | /// A 256-color theme along the lines of Visual Studio Code. 30 | /// 31 | public static AnsiConsoleTheme Code { get; } = AnsiConsoleThemes.Code; 32 | 33 | /// 34 | /// A theme using only gray, black and white. 35 | /// 36 | public static AnsiConsoleTheme Grayscale { get; } = AnsiConsoleThemes.Grayscale; 37 | 38 | /// 39 | /// A theme in the style of the original Serilog.Sinks.Literate. 40 | /// 41 | public static AnsiConsoleTheme Literate { get; } = AnsiConsoleThemes.Literate; 42 | 43 | /// 44 | /// A theme in the style of the original Serilog.Sinks.Literate using only standard 16 terminal colors that will work on light backgrounds. 45 | /// 46 | public static AnsiConsoleTheme Sixteen { get; } = AnsiConsoleThemes.Sixteen; 47 | 48 | readonly IReadOnlyDictionary _styles; 49 | const string AnsiStyleReset = "\x1b[0m"; 50 | 51 | /// 52 | /// Construct a theme given a set of styles. 53 | /// 54 | /// Styles to apply within the theme. 55 | /// When is null 56 | public AnsiConsoleTheme(IReadOnlyDictionary styles) 57 | { 58 | if (styles is null) throw new ArgumentNullException(nameof(styles)); 59 | _styles = styles.ToDictionary(kv => kv.Key, kv => kv.Value); 60 | } 61 | 62 | /// 63 | public override bool CanBuffer => true; 64 | 65 | /// 66 | protected override int ResetCharCount { get; } = AnsiStyleReset.Length; 67 | 68 | /// 69 | public override int Set(TextWriter output, ConsoleThemeStyle style) 70 | { 71 | if (_styles.TryGetValue(style, out var ansiStyle)) 72 | { 73 | output.Write(ansiStyle); 74 | return ansiStyle.Length; 75 | } 76 | return 0; 77 | } 78 | 79 | /// 80 | public override void Reset(TextWriter output) 81 | { 82 | output.Write(AnsiStyleReset); 83 | } 84 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/PropertiesTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using System.Linq; 18 | using System.Text; 19 | using Serilog.Events; 20 | using Serilog.Parsing; 21 | using Serilog.Sinks.SystemConsole.Formatting; 22 | using Serilog.Sinks.SystemConsole.Rendering; 23 | using Serilog.Sinks.SystemConsole.Themes; 24 | 25 | namespace Serilog.Sinks.SystemConsole.Output; 26 | 27 | class PropertiesTokenRenderer : OutputTemplateTokenRenderer 28 | { 29 | readonly MessageTemplate _outputTemplate; 30 | readonly ConsoleTheme _theme; 31 | readonly PropertyToken _token; 32 | readonly ThemedValueFormatter _valueFormatter; 33 | 34 | public PropertiesTokenRenderer(ConsoleTheme theme, PropertyToken token, MessageTemplate outputTemplate, IFormatProvider? formatProvider) 35 | { 36 | _outputTemplate = outputTemplate; 37 | _theme = theme ?? throw new ArgumentNullException(nameof(theme)); 38 | _token = token ?? throw new ArgumentNullException(nameof(token)); 39 | 40 | var isJson = false; 41 | 42 | if (token.Format != null) 43 | { 44 | for (var i = 0; i < token.Format.Length; ++i) 45 | { 46 | if (token.Format[i] == 'j') 47 | isJson = true; 48 | } 49 | } 50 | 51 | _valueFormatter = isJson 52 | ? (ThemedValueFormatter)new ThemedJsonValueFormatter(theme, formatProvider) 53 | : new ThemedDisplayValueFormatter(theme, formatProvider); 54 | } 55 | 56 | public override void Render(LogEvent logEvent, TextWriter output) 57 | { 58 | var included = logEvent.Properties 59 | .Where(p => !TemplateContainsPropertyName(logEvent.MessageTemplate, p.Key) && 60 | !TemplateContainsPropertyName(_outputTemplate, p.Key)) 61 | .Select(p => new LogEventProperty(p.Key, p.Value)); 62 | 63 | var value = new StructureValue(included); 64 | 65 | if (_token.Alignment is null || !_theme.CanBuffer) 66 | { 67 | _valueFormatter.Format(value, output, null); 68 | return; 69 | } 70 | 71 | var buffer = new StringWriter(new StringBuilder(value.Properties.Count * 16)); 72 | var invisible = _valueFormatter.Format(value, buffer, null); 73 | var str = buffer.ToString(); 74 | Padding.Apply(output, str, _token.Alignment.Value.Widen(invisible)); 75 | } 76 | 77 | static bool TemplateContainsPropertyName(MessageTemplate template, string propertyName) 78 | { 79 | foreach (var token in template.Tokens) 80 | { 81 | if (token is PropertyToken namedProperty && 82 | namedProperty.PropertyName == propertyName) 83 | { 84 | return true; 85 | } 86 | } 87 | 88 | return false; 89 | } 90 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using Serilog.Events; 19 | using Serilog.Formatting; 20 | using Serilog.Formatting.Display; 21 | using Serilog.Parsing; 22 | using Serilog.Sinks.SystemConsole.Themes; 23 | 24 | namespace Serilog.Sinks.SystemConsole.Output; 25 | 26 | class OutputTemplateRenderer : ITextFormatter 27 | { 28 | readonly OutputTemplateTokenRenderer[] _renderers; 29 | 30 | public OutputTemplateRenderer(ConsoleTheme theme, string outputTemplate, IFormatProvider? formatProvider) 31 | { 32 | if (outputTemplate is null) throw new ArgumentNullException(nameof(outputTemplate)); 33 | var template = new MessageTemplateParser().Parse(outputTemplate); 34 | 35 | var renderers = new List(); 36 | foreach (var token in template.Tokens) 37 | { 38 | if (token is TextToken tt) 39 | { 40 | renderers.Add(new TextTokenRenderer(theme, tt.Text)); 41 | continue; 42 | } 43 | 44 | var pt = (PropertyToken)token; 45 | if (pt.PropertyName == OutputProperties.LevelPropertyName) 46 | { 47 | renderers.Add(new LevelTokenRenderer(theme, pt)); 48 | } 49 | else if (pt.PropertyName == OutputProperties.NewLinePropertyName) 50 | { 51 | renderers.Add(new NewLineTokenRenderer(pt.Alignment)); 52 | } 53 | else if (pt.PropertyName == OutputProperties.TraceIdPropertyName) 54 | { 55 | renderers.Add(new TraceIdTokenRenderer(theme, pt)); 56 | } 57 | else if (pt.PropertyName == OutputProperties.SpanIdPropertyName) 58 | { 59 | renderers.Add(new SpanIdTokenRenderer(theme, pt)); 60 | } 61 | else if (pt.PropertyName == OutputProperties.ExceptionPropertyName) 62 | { 63 | renderers.Add(new ExceptionTokenRenderer(theme)); 64 | } 65 | else if (pt.PropertyName == OutputProperties.MessagePropertyName) 66 | { 67 | renderers.Add(new MessageTemplateOutputTokenRenderer(theme, pt, formatProvider)); 68 | } 69 | else if (pt.PropertyName == OutputProperties.TimestampPropertyName) 70 | { 71 | renderers.Add(new TimestampTokenRenderer(theme, pt, formatProvider, convertToUtc: false)); 72 | } 73 | else if (pt.PropertyName == OutputProperties.UtcTimestampPropertyName) 74 | { 75 | renderers.Add(new TimestampTokenRenderer(theme, pt, formatProvider, convertToUtc: true)); 76 | } 77 | else if (pt.PropertyName == OutputProperties.PropertiesPropertyName) 78 | { 79 | renderers.Add(new PropertiesTokenRenderer(theme, pt, template, formatProvider)); 80 | } 81 | else 82 | { 83 | renderers.Add(new EventPropertyTokenRenderer(theme, pt, formatProvider)); 84 | } 85 | } 86 | 87 | _renderers = renderers.ToArray(); 88 | } 89 | 90 | public void Format(LogEvent logEvent, TextWriter output) 91 | { 92 | if (logEvent is null) throw new ArgumentNullException(nameof(logEvent)); 93 | if (output is null) throw new ArgumentNullException(nameof(output)); 94 | 95 | foreach (var renderer in _renderers) 96 | renderer.Render(logEvent, output); 97 | } 98 | } -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Formatting/ThemedJsonValueFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Serilog.Events; 5 | using Serilog.Sinks.SystemConsole.Formatting; 6 | using Serilog.Sinks.SystemConsole.Themes; 7 | using Xunit; 8 | 9 | namespace Serilog.Sinks.Console.Tests.Formatting; 10 | 11 | public class ThemedJsonValueFormatterTests 12 | { 13 | class TestThemedJsonValueFormatter : ThemedJsonValueFormatter 14 | { 15 | public TestThemedJsonValueFormatter() 16 | : base(ConsoleTheme.None, null) 17 | { 18 | } 19 | 20 | public string Format(object literal) 21 | { 22 | var output = new StringWriter(); 23 | Format(new SequenceValue(new[] { new ScalarValue(literal) }), output, null); 24 | var o = output.ToString(); 25 | return o.Substring(1, o.Length - 2); 26 | } 27 | } 28 | 29 | [Theory] 30 | [InlineData(123, "123")] 31 | [InlineData('c', "\"c\"")] 32 | [InlineData("Hello, world!", "\"Hello, world!\"")] 33 | [InlineData(true, "true")] 34 | [InlineData("\\\"\t\r\n\f", "\"\\\\\\\"\\t\\r\\n\\f\"")] 35 | [InlineData("\u0001", "\"\\u0001\"")] 36 | [InlineData("a\nb", "\"a\\nb\"")] 37 | [InlineData(null, "null")] 38 | public void JsonLiteralTypesAreFormatted(object value, string expectedJson) 39 | { 40 | var formatter = new TestThemedJsonValueFormatter(); 41 | Assert.Equal(expectedJson, formatter.Format(value)); 42 | } 43 | 44 | [Fact] 45 | public void DateTimesFormatAsIso8601() 46 | { 47 | JsonLiteralTypesAreFormatted(new DateTime(2016, 01, 01, 13, 13, 13, DateTimeKind.Utc), "\"2016-01-01T13:13:13.0000000Z\""); 48 | } 49 | 50 | [Fact] 51 | public void DoubleFormatsAsNumber() 52 | { 53 | JsonLiteralTypesAreFormatted(123.45, "123.45"); 54 | } 55 | 56 | [Fact] 57 | public void DoubleSpecialsFormatAsString() 58 | { 59 | JsonLiteralTypesAreFormatted(double.NaN, "\"NaN\""); 60 | JsonLiteralTypesAreFormatted(double.PositiveInfinity, "\"Infinity\""); 61 | JsonLiteralTypesAreFormatted(double.NegativeInfinity, "\"-Infinity\""); 62 | } 63 | 64 | [Fact] 65 | public void FloatFormatsAsNumber() 66 | { 67 | JsonLiteralTypesAreFormatted(123.45f, "123.45"); 68 | } 69 | 70 | [Fact] 71 | public void FloatSpecialsFormatAsString() 72 | { 73 | JsonLiteralTypesAreFormatted(float.NaN, "\"NaN\""); 74 | JsonLiteralTypesAreFormatted(float.PositiveInfinity, "\"Infinity\""); 75 | JsonLiteralTypesAreFormatted(float.NegativeInfinity, "\"-Infinity\""); 76 | } 77 | 78 | [Fact] 79 | public void DecimalFormatsAsNumber() 80 | { 81 | JsonLiteralTypesAreFormatted(123.45m, "123.45"); 82 | } 83 | 84 | static string Format(LogEventPropertyValue value) 85 | { 86 | var formatter = new TestThemedJsonValueFormatter(); 87 | var output = new StringWriter(); 88 | formatter.Format(value, output, null); 89 | return output.ToString(); 90 | } 91 | 92 | [Fact] 93 | public void ScalarPropertiesFormatAsLiteralValues() 94 | { 95 | var f = Format(new ScalarValue(123)); 96 | Assert.Equal("123", f); 97 | } 98 | 99 | [Fact] 100 | public void SequencePropertiesFormatAsArrayValue() 101 | { 102 | var f = Format(new SequenceValue(new[] { new ScalarValue(123), new ScalarValue(456) })); 103 | Assert.Equal("[123, 456]", f); 104 | } 105 | 106 | [Fact] 107 | public void StructuresFormatAsAnObject() 108 | { 109 | var structure = new StructureValue(new[] { new LogEventProperty("A", new ScalarValue(123)) }, "T"); 110 | var f = Format(structure); 111 | Assert.Equal("{\"A\": 123, \"$type\": \"T\"}", f); 112 | } 113 | 114 | [Fact] 115 | public void DictionaryWithScalarKeyFormatsAsAnObject() 116 | { 117 | var dict = new DictionaryValue(new Dictionary 118 | { 119 | { new ScalarValue(12), new ScalarValue(345) }, 120 | }); 121 | 122 | var f = Format(dict); 123 | Assert.Equal("{\"12\": 345}", f); 124 | } 125 | 126 | [Fact] 127 | public void SequencesOfSequencesAreFormatted() 128 | { 129 | var s = new SequenceValue(new[] { new SequenceValue(new[] { new ScalarValue("Hello") }) }); 130 | 131 | var f = Format(s); 132 | Assert.Equal("[[\"Hello\"]]", f); 133 | } 134 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelOutputFormat.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using Serilog.Events; 17 | using Serilog.Sinks.SystemConsole.Rendering; 18 | 19 | namespace Serilog.Sinks.SystemConsole.Output; 20 | 21 | /// 22 | /// Implements the {Level} element. 23 | /// can now have a fixed width applied to it, as well as casing rules. 24 | /// Width is set through formats like "u3" (uppercase three chars), 25 | /// "w1" (one lowercase char), or "t4" (title case four chars). 26 | /// 27 | static class LevelOutputFormat 28 | { 29 | static readonly string[][] TitleCaseLevelMap = { 30 | new []{ "V", "Vb", "Vrb", "Verb", "Verbo", "Verbos", "Verbose" }, 31 | new []{ "D", "De", "Dbg", "Dbug", "Debug" }, 32 | new []{ "I", "In", "Inf", "Info", "Infor", "Inform", "Informa", "Informat", "Informati", "Informatio", "Information" }, 33 | new []{ "W", "Wn", "Wrn", "Warn", "Warni", "Warnin", "Warning" }, 34 | new []{ "E", "Er", "Err", "Eror", "Error" }, 35 | new []{ "F", "Fa", "Ftl", "Fatl", "Fatal" } 36 | }; 37 | 38 | static readonly string[][] LowerCaseLevelMap = { 39 | new []{ "v", "vb", "vrb", "verb", "verbo", "verbos", "verbose" }, 40 | new []{ "d", "de", "dbg", "dbug", "debug" }, 41 | new []{ "i", "in", "inf", "info", "infor", "inform", "informa", "informat", "informati", "informatio", "information" }, 42 | new []{ "w", "wn", "wrn", "warn", "warni", "warnin", "warning" }, 43 | new []{ "e", "er", "err", "eror", "error" }, 44 | new []{ "f", "fa", "ftl", "fatl", "fatal" } 45 | }; 46 | 47 | static readonly string[][] UpperCaseLevelMap = { 48 | new []{ "V", "VB", "VRB", "VERB", "VERBO", "VERBOS", "VERBOSE" }, 49 | new []{ "D", "DE", "DBG", "DBUG", "DEBUG" }, 50 | new []{ "I", "IN", "INF", "INFO", "INFOR", "INFORM", "INFORMA", "INFORMAT", "INFORMATI", "INFORMATIO", "INFORMATION" }, 51 | new []{ "W", "WN", "WRN", "WARN", "WARNI", "WARNIN", "WARNING" }, 52 | new []{ "E", "ER", "ERR", "EROR", "ERROR" }, 53 | new []{ "F", "FA", "FTL", "FATL", "FATAL" } 54 | }; 55 | 56 | public static string GetLevelMoniker(LogEventLevel value, string? format = null) 57 | { 58 | var index = (int)value; 59 | if (index is < 0 or > (int)LogEventLevel.Fatal) 60 | return Casing.Format(value.ToString(), format); 61 | 62 | if (format == null || format.Length != 2 && format.Length != 3) 63 | return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format); 64 | 65 | // Using int.Parse() here requires allocating a string to exclude the first character prefix. 66 | // Junk like "wxy" will be accepted but produce benign results. 67 | var width = format[1] - '0'; 68 | if (format.Length == 3) 69 | { 70 | width *= 10; 71 | width += format[2] - '0'; 72 | } 73 | 74 | if (width < 1) 75 | return string.Empty; 76 | 77 | switch (format[0]) 78 | { 79 | case 'w': 80 | return GetLevelMoniker(LowerCaseLevelMap, index, width); 81 | case 'u': 82 | return GetLevelMoniker(UpperCaseLevelMap, index, width); 83 | case 't': 84 | return GetLevelMoniker(TitleCaseLevelMap, index, width); 85 | default: 86 | return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format); 87 | } 88 | } 89 | 90 | static string GetLevelMoniker(string[][] caseLevelMap, int index, int width) 91 | { 92 | var caseLevel = caseLevelMap[index]; 93 | return caseLevel[Math.Min(width, caseLevel.Length) - 1]; 94 | } 95 | 96 | static string GetLevelMoniker(string[][] caseLevelMap, int index) 97 | { 98 | var caseLevel = caseLevelMap[index]; 99 | return caseLevel[caseLevel.Length - 1]; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /serilog-sinks-console.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sln", "sln", "{E9D1B5E1-DEB9-4A04-8BAB-24EC7240ADAF}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitattributes = .gitattributes 11 | .gitignore = .gitignore 12 | Build.ps1 = Build.ps1 13 | CHANGES.md = CHANGES.md 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | assets\Serilog.snk = assets\Serilog.snk 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7D0692CD-F95D-4BF9-8C63-B4A1C078DF23}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Console", "src\Serilog.Sinks.Console\Serilog.Sinks.Console.csproj", "{866A028E-27DB-49A0-AC78-E5FEF247C099}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serilog.Sinks.Console.Tests", "test\Serilog.Sinks.Console.Tests\Serilog.Sinks.Console.Tests.csproj", "{1D56534C-4009-42C2-A573-789CAE6B8AA9}" 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{CF817664-4CEC-4B6A-9C57-A0D687757D82}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleDemo", "sample\ConsoleDemo\ConsoleDemo.csproj", "{DBF4907A-63A2-4895-8DEF-59F90C20380B}" 28 | EndProject 29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyncWritesDemo", "sample\SyncWritesDemo\SyncWritesDemo.csproj", "{633AE0AD-C9D4-440D-874A-C0F4632DB75F}" 30 | EndProject 31 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{91B268E6-3BCF-49B4-A04D-8467AE23410A}" 32 | ProjectSection(SolutionItems) = preProject 33 | .github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md 34 | .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md 35 | EndProjectSection 36 | EndProject 37 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{F4C015DE-80BA-4D63-9D3D-D687999B4960}" 38 | ProjectSection(SolutionItems) = preProject 39 | .github\workflows\ci.yml = .github\workflows\ci.yml 40 | EndProjectSection 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Release|Any CPU = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 48 | {866A028E-27DB-49A0-AC78-E5FEF247C099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {866A028E-27DB-49A0-AC78-E5FEF247C099}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {866A028E-27DB-49A0-AC78-E5FEF247C099}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {866A028E-27DB-49A0-AC78-E5FEF247C099}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {1D56534C-4009-42C2-A573-789CAE6B8AA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {1D56534C-4009-42C2-A573-789CAE6B8AA9}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {1D56534C-4009-42C2-A573-789CAE6B8AA9}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {1D56534C-4009-42C2-A573-789CAE6B8AA9}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {DBF4907A-63A2-4895-8DEF-59F90C20380B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {DBF4907A-63A2-4895-8DEF-59F90C20380B}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {DBF4907A-63A2-4895-8DEF-59F90C20380B}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {DBF4907A-63A2-4895-8DEF-59F90C20380B}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {633AE0AD-C9D4-440D-874A-C0F4632DB75F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {633AE0AD-C9D4-440D-874A-C0F4632DB75F}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {633AE0AD-C9D4-440D-874A-C0F4632DB75F}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {633AE0AD-C9D4-440D-874A-C0F4632DB75F}.Release|Any CPU.Build.0 = Release|Any CPU 64 | EndGlobalSection 65 | GlobalSection(SolutionProperties) = preSolution 66 | HideSolutionNode = FALSE 67 | EndGlobalSection 68 | GlobalSection(NestedProjects) = preSolution 69 | {866A028E-27DB-49A0-AC78-E5FEF247C099} = {037440DE-440B-4129-9F7A-09B42D00397E} 70 | {1D56534C-4009-42C2-A573-789CAE6B8AA9} = {7D0692CD-F95D-4BF9-8C63-B4A1C078DF23} 71 | {DBF4907A-63A2-4895-8DEF-59F90C20380B} = {CF817664-4CEC-4B6A-9C57-A0D687757D82} 72 | {633AE0AD-C9D4-440D-874A-C0F4632DB75F} = {CF817664-4CEC-4B6A-9C57-A0D687757D82} 73 | {F4C015DE-80BA-4D63-9D3D-D687999B4960} = {91B268E6-3BCF-49B4-A04D-8467AE23410A} 74 | EndGlobalSection 75 | GlobalSection(ExtensibilityGlobals) = postSolution 76 | SolutionGuid = {43C32ED4-D39A-4E27-AE99-7BB8C883833C} 77 | EndGlobalSection 78 | EndGlobal 79 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TimestampTokenRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Globalization; 17 | using System.IO; 18 | using Serilog.Events; 19 | using Serilog.Parsing; 20 | using Serilog.Sinks.SystemConsole.Rendering; 21 | using Serilog.Sinks.SystemConsole.Themes; 22 | 23 | namespace Serilog.Sinks.SystemConsole.Output; 24 | 25 | class TimestampTokenRenderer : OutputTemplateTokenRenderer 26 | { 27 | readonly ConsoleTheme _theme; 28 | readonly PropertyToken _token; 29 | readonly string? _format; 30 | readonly IFormatProvider? _formatProvider; 31 | readonly bool _convertToUtc; 32 | 33 | public TimestampTokenRenderer(ConsoleTheme theme, PropertyToken token, IFormatProvider? formatProvider, bool convertToUtc) 34 | { 35 | _theme = theme; 36 | _token = token; 37 | _format = token.Format; 38 | _formatProvider = formatProvider; 39 | _convertToUtc = convertToUtc; 40 | } 41 | 42 | public override void Render(LogEvent logEvent, TextWriter output) 43 | { 44 | var _ = 0; 45 | using (_theme.Apply(output, ConsoleThemeStyle.SecondaryText, ref _)) 46 | { 47 | if (_token.Alignment is null) 48 | { 49 | Render(output, logEvent.Timestamp); 50 | } 51 | else 52 | { 53 | var buffer = new StringWriter(); 54 | Render(buffer, logEvent.Timestamp); 55 | var str = buffer.ToString(); 56 | Padding.Apply(output, str, _token.Alignment); 57 | } 58 | } 59 | } 60 | 61 | private void Render(TextWriter output, DateTimeOffset timestamp) 62 | { 63 | // When a DateTimeOffset is converted to a string, the default format automatically adds the "+00:00" explicit offset to the output string. 64 | // As the TimestampTokenRenderer is also used for rendering the UtcTimestamp which is always in UTC by definition, the +00:00 suffix should be avoided. 65 | // This is done using the same approach as Serilog's MessageTemplateTextFormatter. In case output should be converted to UTC, in order to avoid a zone specifier, 66 | // the DateTimeOffset is converted to a DateTime which then renders as expected. 67 | 68 | var custom = (ICustomFormatter?)_formatProvider?.GetFormat(typeof(ICustomFormatter)); 69 | if (custom != null) 70 | { 71 | output.Write(custom.Format(_format, _convertToUtc ? timestamp.UtcDateTime : timestamp, _formatProvider)); 72 | return; 73 | } 74 | 75 | if (_convertToUtc) 76 | { 77 | RenderDateTime(output, timestamp.UtcDateTime); 78 | } 79 | else 80 | { 81 | RenderDateTimeOffset(output, timestamp); 82 | } 83 | } 84 | 85 | private void RenderDateTimeOffset(TextWriter output, DateTimeOffset timestamp) 86 | { 87 | #if FEATURE_SPAN 88 | Span buffer = stackalloc char[32]; 89 | if (timestamp.TryFormat(buffer, out int written, _format, _formatProvider ?? CultureInfo.InvariantCulture)) 90 | output.Write(buffer.Slice(0, written)); 91 | else 92 | output.Write(timestamp.ToString(_format, _formatProvider ?? CultureInfo.InvariantCulture)); 93 | #else 94 | output.Write(timestamp.ToString(_format, _formatProvider ?? CultureInfo.InvariantCulture)); 95 | #endif 96 | } 97 | 98 | private void RenderDateTime(TextWriter output, DateTime utcTimestamp) 99 | { 100 | #if FEATURE_SPAN 101 | Span buffer = stackalloc char[32]; 102 | if (utcTimestamp.TryFormat(buffer, out int written, _format, _formatProvider ?? CultureInfo.InvariantCulture)) 103 | output.Write(buffer.Slice(0, written)); 104 | else 105 | output.Write(utcTimestamp.ToString(_format, _formatProvider ?? CultureInfo.InvariantCulture)); 106 | #else 107 | output.Write(utcTimestamp.ToString(_format, _formatProvider ?? CultureInfo.InvariantCulture)); 108 | #endif 109 | } 110 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | .DS_Store/ 239 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Approval/Serilog.Sinks.Console.approved.txt: -------------------------------------------------------------------------------- 1 | namespace Serilog 2 | { 3 | public static class ConsoleAuditLoggerConfigurationExtensions 4 | { 5 | public static Serilog.LoggerConfiguration Console(this Serilog.Configuration.LoggerAuditSinkConfiguration sinkConfiguration, Serilog.Formatting.ITextFormatter formatter, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null, Serilog.Events.LogEventLevel? standardErrorFromLevel = default, object? syncRoot = null) { } 6 | public static Serilog.LoggerConfiguration Console(this Serilog.Configuration.LoggerAuditSinkConfiguration sinkConfiguration, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, string outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}", System.IFormatProvider? formatProvider = null, Serilog.Core.LoggingLevelSwitch? levelSwitch = null, Serilog.Events.LogEventLevel? standardErrorFromLevel = default, Serilog.Sinks.SystemConsole.Themes.ConsoleTheme? theme = null, bool applyThemeToRedirectedOutput = false, object? syncRoot = null) { } 7 | } 8 | public static class ConsoleLoggerConfigurationExtensions 9 | { 10 | public static Serilog.LoggerConfiguration Console(this Serilog.Configuration.LoggerSinkConfiguration sinkConfiguration, Serilog.Formatting.ITextFormatter formatter, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null, Serilog.Events.LogEventLevel? standardErrorFromLevel = default, object? syncRoot = null) { } 11 | public static Serilog.LoggerConfiguration Console(this Serilog.Configuration.LoggerSinkConfiguration sinkConfiguration, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, string outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}", System.IFormatProvider? formatProvider = null, Serilog.Core.LoggingLevelSwitch? levelSwitch = null, Serilog.Events.LogEventLevel? standardErrorFromLevel = default, Serilog.Sinks.SystemConsole.Themes.ConsoleTheme? theme = null, bool applyThemeToRedirectedOutput = false, object? syncRoot = null) { } 12 | } 13 | } 14 | namespace Serilog.Sinks.SystemConsole.Themes 15 | { 16 | public class AnsiConsoleTheme : Serilog.Sinks.SystemConsole.Themes.ConsoleTheme 17 | { 18 | public AnsiConsoleTheme(System.Collections.Generic.IReadOnlyDictionary styles) { } 19 | public override bool CanBuffer { get; } 20 | protected override int ResetCharCount { get; } 21 | public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Code { get; } 22 | public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Grayscale { get; } 23 | public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Literate { get; } 24 | public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Sixteen { get; } 25 | public override void Reset(System.IO.TextWriter output) { } 26 | public override int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style) { } 27 | } 28 | public abstract class ConsoleTheme 29 | { 30 | protected ConsoleTheme() { } 31 | public abstract bool CanBuffer { get; } 32 | protected abstract int ResetCharCount { get; } 33 | public static Serilog.Sinks.SystemConsole.Themes.ConsoleTheme None { get; } 34 | public abstract void Reset(System.IO.TextWriter output); 35 | public abstract int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style); 36 | } 37 | public enum ConsoleThemeStyle 38 | { 39 | Text = 0, 40 | SecondaryText = 1, 41 | TertiaryText = 2, 42 | Invalid = 3, 43 | Null = 4, 44 | Name = 5, 45 | String = 6, 46 | Number = 7, 47 | Boolean = 8, 48 | Scalar = 9, 49 | [System.Obsolete("Use ConsoleThemeStyle.Scalar instead")] 50 | Object = 9, 51 | LevelVerbose = 10, 52 | LevelDebug = 11, 53 | LevelInformation = 12, 54 | LevelWarning = 13, 55 | LevelError = 14, 56 | LevelFatal = 15, 57 | } 58 | public class SystemConsoleTheme : Serilog.Sinks.SystemConsole.Themes.ConsoleTheme 59 | { 60 | public SystemConsoleTheme(System.Collections.Generic.IReadOnlyDictionary styles) { } 61 | public override bool CanBuffer { get; } 62 | protected override int ResetCharCount { get; } 63 | public System.Collections.Generic.IReadOnlyDictionary Styles { get; } 64 | public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Colored { get; } 65 | public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Grayscale { get; } 66 | public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Literate { get; } 67 | public override void Reset(System.IO.TextWriter output) { } 68 | public override int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style) { } 69 | } 70 | public struct SystemConsoleThemeStyle 71 | { 72 | public System.ConsoleColor? Background; 73 | public System.ConsoleColor? Foreground; 74 | } 75 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/AnsiConsoleThemes.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Collections.Generic; 16 | 17 | namespace Serilog.Sinks.SystemConsole.Themes; 18 | 19 | static class AnsiConsoleThemes 20 | { 21 | public static AnsiConsoleTheme Literate { get; } = new AnsiConsoleTheme( 22 | new Dictionary 23 | { 24 | [ConsoleThemeStyle.Text] = "\x1b[38;5;0015m", 25 | [ConsoleThemeStyle.SecondaryText] = "\x1b[38;5;0007m", 26 | [ConsoleThemeStyle.TertiaryText] = "\x1b[38;5;0008m", 27 | [ConsoleThemeStyle.Invalid] = "\x1b[38;5;0011m", 28 | [ConsoleThemeStyle.Null] = "\x1b[38;5;0027m", 29 | [ConsoleThemeStyle.Name] = "\x1b[38;5;0007m", 30 | [ConsoleThemeStyle.String] = "\x1b[38;5;0045m", 31 | [ConsoleThemeStyle.Number] = "\x1b[38;5;0200m", 32 | [ConsoleThemeStyle.Boolean] = "\x1b[38;5;0027m", 33 | [ConsoleThemeStyle.Scalar] = "\x1b[38;5;0085m", 34 | [ConsoleThemeStyle.LevelVerbose] = "\x1b[38;5;0007m", 35 | [ConsoleThemeStyle.LevelDebug] = "\x1b[38;5;0007m", 36 | [ConsoleThemeStyle.LevelInformation] = "\x1b[38;5;0015m", 37 | [ConsoleThemeStyle.LevelWarning] = "\x1b[38;5;0011m", 38 | [ConsoleThemeStyle.LevelError] = "\x1b[38;5;0015m\x1b[48;5;0196m", 39 | [ConsoleThemeStyle.LevelFatal] = "\x1b[38;5;0015m\x1b[48;5;0196m", 40 | }); 41 | 42 | public static AnsiConsoleTheme Grayscale { get; } = new AnsiConsoleTheme( 43 | new Dictionary 44 | { 45 | [ConsoleThemeStyle.Text] = "\x1b[37;1m", 46 | [ConsoleThemeStyle.SecondaryText] = "\x1b[37m", 47 | [ConsoleThemeStyle.TertiaryText] = "\x1b[30;1m", 48 | [ConsoleThemeStyle.Invalid] = "\x1b[37;1m\x1b[47m", 49 | [ConsoleThemeStyle.Null] = "\x1b[1m\x1b[37;1m", 50 | [ConsoleThemeStyle.Name] = "\x1b[37m", 51 | [ConsoleThemeStyle.String] = "\x1b[1m\x1b[37;1m", 52 | [ConsoleThemeStyle.Number] = "\x1b[1m\x1b[37;1m", 53 | [ConsoleThemeStyle.Boolean] = "\x1b[1m\x1b[37;1m", 54 | [ConsoleThemeStyle.Scalar] = "\x1b[1m\x1b[37;1m", 55 | [ConsoleThemeStyle.LevelVerbose] = "\x1b[30;1m", 56 | [ConsoleThemeStyle.LevelDebug] = "\x1b[30;1m", 57 | [ConsoleThemeStyle.LevelInformation] = "\x1b[37;1m", 58 | [ConsoleThemeStyle.LevelWarning] = "\x1b[37;1m\x1b[47m", 59 | [ConsoleThemeStyle.LevelError] = "\x1b[30m\x1b[47m", 60 | [ConsoleThemeStyle.LevelFatal] = "\x1b[30m\x1b[47m", 61 | }); 62 | 63 | public static AnsiConsoleTheme Code { get; } = new AnsiConsoleTheme( 64 | new Dictionary 65 | { 66 | [ConsoleThemeStyle.Text] = "\x1b[38;5;0253m", 67 | [ConsoleThemeStyle.SecondaryText] = "\x1b[38;5;0246m", 68 | [ConsoleThemeStyle.TertiaryText] = "\x1b[38;5;0242m", 69 | [ConsoleThemeStyle.Invalid] = "\x1b[33;1m", 70 | [ConsoleThemeStyle.Null] = "\x1b[38;5;0038m", 71 | [ConsoleThemeStyle.Name] = "\x1b[38;5;0081m", 72 | [ConsoleThemeStyle.String] = "\x1b[38;5;0216m", 73 | [ConsoleThemeStyle.Number] = "\x1b[38;5;151m", 74 | [ConsoleThemeStyle.Boolean] = "\x1b[38;5;0038m", 75 | [ConsoleThemeStyle.Scalar] = "\x1b[38;5;0079m", 76 | [ConsoleThemeStyle.LevelVerbose] = "\x1b[37m", 77 | [ConsoleThemeStyle.LevelDebug] = "\x1b[37m", 78 | [ConsoleThemeStyle.LevelInformation] = "\x1b[37;1m", 79 | [ConsoleThemeStyle.LevelWarning] = "\x1b[38;5;0229m", 80 | [ConsoleThemeStyle.LevelError] = "\x1b[38;5;0197m\x1b[48;5;0238m", 81 | [ConsoleThemeStyle.LevelFatal] = "\x1b[38;5;0197m\x1b[48;5;0238m", 82 | }); 83 | 84 | public static AnsiConsoleTheme Sixteen { get; } = new AnsiConsoleTheme( 85 | new Dictionary 86 | { 87 | [ConsoleThemeStyle.Text] = AnsiEscapeSequence.Unthemed, 88 | [ConsoleThemeStyle.SecondaryText] = AnsiEscapeSequence.Unthemed, 89 | [ConsoleThemeStyle.TertiaryText] = AnsiEscapeSequence.Unthemed, 90 | [ConsoleThemeStyle.Invalid] = AnsiEscapeSequence.Yellow, 91 | [ConsoleThemeStyle.Null] = AnsiEscapeSequence.Blue, 92 | [ConsoleThemeStyle.Name] = AnsiEscapeSequence.Unthemed, 93 | [ConsoleThemeStyle.String] = AnsiEscapeSequence.Cyan, 94 | [ConsoleThemeStyle.Number] = AnsiEscapeSequence.Magenta, 95 | [ConsoleThemeStyle.Boolean] = AnsiEscapeSequence.Blue, 96 | [ConsoleThemeStyle.Scalar] = AnsiEscapeSequence.Green, 97 | [ConsoleThemeStyle.LevelVerbose] = AnsiEscapeSequence.Unthemed, 98 | [ConsoleThemeStyle.LevelDebug] = AnsiEscapeSequence.Bold, 99 | [ConsoleThemeStyle.LevelInformation] = AnsiEscapeSequence.BrightCyan, 100 | [ConsoleThemeStyle.LevelWarning] = AnsiEscapeSequence.BrightYellow, 101 | [ConsoleThemeStyle.LevelError] = AnsiEscapeSequence.BrightRed, 102 | [ConsoleThemeStyle.LevelFatal] = AnsiEscapeSequence.BrightRed, 103 | }); 104 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Rendering/ThemedMessageTemplateRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using Serilog.Events; 19 | using Serilog.Parsing; 20 | using Serilog.Sinks.SystemConsole.Formatting; 21 | using Serilog.Sinks.SystemConsole.Themes; 22 | 23 | namespace Serilog.Sinks.SystemConsole.Rendering; 24 | 25 | class ThemedMessageTemplateRenderer 26 | { 27 | readonly ConsoleTheme _theme; 28 | readonly ThemedValueFormatter _valueFormatter; 29 | readonly bool _isLiteral; 30 | static readonly ConsoleTheme NoTheme = new EmptyConsoleTheme(); 31 | readonly ThemedValueFormatter _unthemedValueFormatter; 32 | 33 | public ThemedMessageTemplateRenderer(ConsoleTheme theme, ThemedValueFormatter valueFormatter, bool isLiteral) 34 | { 35 | _theme = theme ?? throw new ArgumentNullException(nameof(theme)); 36 | _valueFormatter = valueFormatter; 37 | _isLiteral = isLiteral; 38 | _unthemedValueFormatter = valueFormatter.SwitchTheme(NoTheme); 39 | } 40 | 41 | public int Render(MessageTemplate template, IReadOnlyDictionary properties, TextWriter output) 42 | { 43 | var count = 0; 44 | foreach (var token in template.Tokens) 45 | { 46 | if (token is TextToken tt) 47 | { 48 | count += RenderTextToken(tt, output); 49 | } 50 | else 51 | { 52 | var pt = (PropertyToken)token; 53 | count += RenderPropertyToken(pt, properties, output); 54 | } 55 | } 56 | return count; 57 | } 58 | 59 | int RenderTextToken(TextToken tt, TextWriter output) 60 | { 61 | var count = 0; 62 | using (_theme.Apply(output, ConsoleThemeStyle.Text, ref count)) 63 | output.Write(tt.Text); 64 | return count; 65 | } 66 | 67 | int RenderPropertyToken(PropertyToken pt, IReadOnlyDictionary properties, TextWriter output) 68 | { 69 | if (!properties.TryGetValue(pt.PropertyName, out var propertyValue)) 70 | { 71 | var count = 0; 72 | using (_theme.Apply(output, ConsoleThemeStyle.Invalid, ref count)) 73 | output.Write(pt.ToString()); 74 | return count; 75 | } 76 | 77 | if (!pt.Alignment.HasValue) 78 | { 79 | return RenderValue(_theme, _valueFormatter, propertyValue, output, pt.Format); 80 | } 81 | 82 | var valueOutput = new StringWriter(); 83 | 84 | if (!_theme.CanBuffer) 85 | return RenderAlignedPropertyTokenUnbuffered(pt, output, propertyValue); 86 | 87 | var invisibleCount = RenderValue(_theme, _valueFormatter, propertyValue, valueOutput, pt.Format); 88 | 89 | var value = valueOutput.ToString(); 90 | 91 | if (value.Length - invisibleCount >= pt.Alignment.Value.Width) 92 | { 93 | output.Write(value); 94 | } 95 | else 96 | { 97 | Padding.Apply(output, value, pt.Alignment.Value.Widen(invisibleCount)); 98 | } 99 | 100 | return invisibleCount; 101 | } 102 | 103 | int RenderAlignedPropertyTokenUnbuffered(PropertyToken pt, TextWriter output, LogEventPropertyValue propertyValue) 104 | { 105 | if (pt.Alignment == null) throw new ArgumentException("The PropertyToken should have a non-null Alignment.", nameof(pt)); 106 | 107 | var valueOutput = new StringWriter(); 108 | RenderValue(NoTheme, _unthemedValueFormatter, propertyValue, valueOutput, pt.Format); 109 | 110 | var valueLength = valueOutput.ToString().Length; 111 | if (valueLength >= pt.Alignment.Value.Width) 112 | { 113 | return RenderValue(_theme, _valueFormatter, propertyValue, output, pt.Format); 114 | } 115 | 116 | if (pt.Alignment.Value.Direction == AlignmentDirection.Left) 117 | { 118 | var invisible = RenderValue(_theme, _valueFormatter, propertyValue, output, pt.Format); 119 | Padding.Apply(output, string.Empty, pt.Alignment.Value.Widen(-valueLength)); 120 | return invisible; 121 | } 122 | 123 | Padding.Apply(output, string.Empty, pt.Alignment.Value.Widen(-valueLength)); 124 | return RenderValue(_theme, _valueFormatter, propertyValue, output, pt.Format); 125 | } 126 | 127 | int RenderValue(ConsoleTheme theme, ThemedValueFormatter valueFormatter, LogEventPropertyValue propertyValue, TextWriter output, string? format) 128 | { 129 | if (_isLiteral && propertyValue is ScalarValue sv && sv.Value is string) 130 | { 131 | var count = 0; 132 | using (theme.Apply(output, ConsoleThemeStyle.String, ref count)) 133 | output.Write(sv.Value); 134 | return count; 135 | } 136 | 137 | return valueFormatter.Format(propertyValue, output, format, _isLiteral); 138 | } 139 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/ConsoleAuditLoggerConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Serilog.Configuration; 16 | using Serilog.Core; 17 | using Serilog.Events; 18 | using Serilog.Formatting; 19 | using Serilog.Sinks.SystemConsole; 20 | using Serilog.Sinks.SystemConsole.Output; 21 | using Serilog.Sinks.SystemConsole.Themes; 22 | using System; 23 | 24 | namespace Serilog; 25 | 26 | /// 27 | /// Adds the AuditTo.Console() extension method to . 28 | /// 29 | public static class ConsoleAuditLoggerConfigurationExtensions 30 | { 31 | /// 32 | /// Writes log events to . 33 | /// 34 | /// Logger sink configuration. 35 | /// The minimum level for 36 | /// events passed through the sink. Ignored when is specified. 37 | /// A message template describing the format used to write to the sink. 38 | /// The default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 39 | /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you 40 | /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while 41 | /// the lock is held. 42 | /// Supplies culture-specific formatting information, or null. 43 | /// A switch allowing the pass-through minimum level 44 | /// to be changed at runtime. 45 | /// Specifies the level at which events will be written to standard error. 46 | /// The theme to apply to the styled output. If not specified, 47 | /// uses . 48 | /// Applies the selected or default theme even when output redirection is detected. 49 | /// Configuration object allowing method chaining. 50 | /// When is null 51 | /// When is null 52 | public static LoggerConfiguration Console( 53 | this LoggerAuditSinkConfiguration sinkConfiguration, 54 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 55 | string outputTemplate = ConsoleLoggerConfigurationExtensions.DefaultConsoleOutputTemplate, 56 | IFormatProvider? formatProvider = null, 57 | LoggingLevelSwitch? levelSwitch = null, 58 | LogEventLevel? standardErrorFromLevel = null, 59 | ConsoleTheme? theme = null, 60 | bool applyThemeToRedirectedOutput = false, 61 | object? syncRoot = null) 62 | { 63 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 64 | if (outputTemplate is null) throw new ArgumentNullException(nameof(outputTemplate)); 65 | 66 | // see https://no-color.org/ 67 | var appliedTheme = !applyThemeToRedirectedOutput && (System.Console.IsOutputRedirected || System.Console.IsErrorRedirected) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NO_COLOR")) ? 68 | ConsoleTheme.None : 69 | theme ?? SystemConsoleThemes.Literate; 70 | 71 | syncRoot ??= ConsoleLoggerConfigurationExtensions.DefaultSyncRoot; 72 | 73 | var formatter = new OutputTemplateRenderer(appliedTheme, outputTemplate, formatProvider); 74 | return sinkConfiguration.Sink(new ConsoleSink(appliedTheme, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch); 75 | } 76 | 77 | /// 78 | /// Writes log events to . 79 | /// 80 | /// Logger sink configuration. 81 | /// Controls the rendering of log events into text, for example to log JSON. To 82 | /// control plain text formatting, use the overload that accepts an output template. 83 | /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you 84 | /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while 85 | /// the lock is held. 86 | /// The minimum level for 87 | /// events passed through the sink. Ignored when is specified. 88 | /// A switch allowing the pass-through minimum level 89 | /// to be changed at runtime. 90 | /// Specifies the level at which events will be written to standard error. 91 | /// Configuration object allowing method chaining. 92 | /// When is null 93 | /// When is null 94 | public static LoggerConfiguration Console( 95 | this LoggerAuditSinkConfiguration sinkConfiguration, 96 | ITextFormatter formatter, 97 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 98 | LoggingLevelSwitch? levelSwitch = null, 99 | LogEventLevel? standardErrorFromLevel = null, 100 | object? syncRoot = null) 101 | { 102 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 103 | if (formatter is null) throw new ArgumentNullException(nameof(formatter)); 104 | 105 | syncRoot ??= ConsoleLoggerConfigurationExtensions.DefaultSyncRoot; 106 | 107 | return sinkConfiguration.Sink(new ConsoleSink(ConsoleTheme.None, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch); 108 | } 109 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/ConsoleLoggerConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Serilog.Configuration; 16 | using Serilog.Core; 17 | using Serilog.Events; 18 | using Serilog.Formatting; 19 | using Serilog.Sinks.SystemConsole; 20 | using Serilog.Sinks.SystemConsole.Output; 21 | using Serilog.Sinks.SystemConsole.Themes; 22 | using System; 23 | 24 | namespace Serilog; 25 | 26 | /// 27 | /// Adds the WriteTo.Console() extension method to . 28 | /// 29 | public static class ConsoleLoggerConfigurationExtensions 30 | { 31 | internal static readonly object DefaultSyncRoot = new object(); 32 | internal const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"; 33 | 34 | /// 35 | /// Writes log events to . 36 | /// 37 | /// Logger sink configuration. 38 | /// The minimum level for 39 | /// events passed through the sink. Ignored when is specified. 40 | /// A message template describing the format used to write to the sink. 41 | /// The default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}". 42 | /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you 43 | /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while 44 | /// the lock is held. 45 | /// Supplies culture-specific formatting information, or null. 46 | /// A switch allowing the pass-through minimum level 47 | /// to be changed at runtime. 48 | /// Specifies the level at which events will be written to standard error. 49 | /// The theme to apply to the styled output. If not specified, 50 | /// uses . 51 | /// Applies the selected or default theme even when output redirection is detected. 52 | /// Configuration object allowing method chaining. 53 | /// When is null 54 | /// When is null 55 | public static LoggerConfiguration Console( 56 | this LoggerSinkConfiguration sinkConfiguration, 57 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 58 | string outputTemplate = DefaultConsoleOutputTemplate, 59 | IFormatProvider? formatProvider = null, 60 | LoggingLevelSwitch? levelSwitch = null, 61 | LogEventLevel? standardErrorFromLevel = null, 62 | ConsoleTheme? theme = null, 63 | bool applyThemeToRedirectedOutput = false, 64 | object? syncRoot = null) 65 | { 66 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 67 | if (outputTemplate is null) throw new ArgumentNullException(nameof(outputTemplate)); 68 | 69 | // see https://no-color.org/ 70 | var appliedTheme = !applyThemeToRedirectedOutput && (System.Console.IsOutputRedirected || System.Console.IsErrorRedirected) || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NO_COLOR")) ? 71 | ConsoleTheme.None : 72 | theme ?? SystemConsoleThemes.Literate; 73 | 74 | syncRoot ??= DefaultSyncRoot; 75 | 76 | var formatter = new OutputTemplateRenderer(appliedTheme, outputTemplate, formatProvider); 77 | return sinkConfiguration.Sink(new ConsoleSink(appliedTheme, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch); 78 | } 79 | 80 | /// 81 | /// Writes log events to . 82 | /// 83 | /// Logger sink configuration. 84 | /// Controls the rendering of log events into text, for example to log JSON. To 85 | /// control plain text formatting, use the overload that accepts an output template. 86 | /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you 87 | /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while 88 | /// the lock is held. 89 | /// The minimum level for 90 | /// events passed through the sink. Ignored when is specified. 91 | /// A switch allowing the pass-through minimum level 92 | /// to be changed at runtime. 93 | /// Specifies the level at which events will be written to standard error. 94 | /// Configuration object allowing method chaining. 95 | /// When is null 96 | /// When is null 97 | public static LoggerConfiguration Console( 98 | this LoggerSinkConfiguration sinkConfiguration, 99 | ITextFormatter formatter, 100 | LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, 101 | LoggingLevelSwitch? levelSwitch = null, 102 | LogEventLevel? standardErrorFromLevel = null, 103 | object? syncRoot = null) 104 | { 105 | if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration)); 106 | if (formatter is null) throw new ArgumentNullException(nameof(formatter)); 107 | 108 | syncRoot ??= DefaultSyncRoot; 109 | 110 | return sinkConfiguration.Sink(new ConsoleSink(ConsoleTheme.None, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch); 111 | } 112 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/SystemConsoleThemes.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | 18 | namespace Serilog.Sinks.SystemConsole.Themes; 19 | 20 | static class SystemConsoleThemes 21 | { 22 | public static SystemConsoleTheme Literate { get; } = new SystemConsoleTheme( 23 | new Dictionary 24 | { 25 | [ConsoleThemeStyle.Text] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 26 | [ConsoleThemeStyle.SecondaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 27 | [ConsoleThemeStyle.TertiaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 28 | [ConsoleThemeStyle.Invalid] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Yellow }, 29 | [ConsoleThemeStyle.Null] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Blue }, 30 | [ConsoleThemeStyle.Name] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 31 | [ConsoleThemeStyle.String] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Cyan }, 32 | [ConsoleThemeStyle.Number] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Magenta }, 33 | [ConsoleThemeStyle.Boolean] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Blue }, 34 | [ConsoleThemeStyle.Scalar] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, 35 | [ConsoleThemeStyle.LevelVerbose] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 36 | [ConsoleThemeStyle.LevelDebug] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 37 | [ConsoleThemeStyle.LevelInformation] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 38 | [ConsoleThemeStyle.LevelWarning] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Yellow }, 39 | [ConsoleThemeStyle.LevelError] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.Red }, 40 | [ConsoleThemeStyle.LevelFatal] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.Red }, 41 | }); 42 | 43 | public static SystemConsoleTheme Grayscale { get; } = new SystemConsoleTheme( 44 | new Dictionary 45 | { 46 | [ConsoleThemeStyle.Text] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 47 | [ConsoleThemeStyle.SecondaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 48 | [ConsoleThemeStyle.TertiaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 49 | [ConsoleThemeStyle.Invalid] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.DarkGray }, 50 | [ConsoleThemeStyle.Null] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 51 | [ConsoleThemeStyle.Name] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 52 | [ConsoleThemeStyle.String] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 53 | [ConsoleThemeStyle.Number] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 54 | [ConsoleThemeStyle.Boolean] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 55 | [ConsoleThemeStyle.Scalar] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 56 | [ConsoleThemeStyle.LevelVerbose] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 57 | [ConsoleThemeStyle.LevelDebug] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 58 | [ConsoleThemeStyle.LevelInformation] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 59 | [ConsoleThemeStyle.LevelWarning] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.DarkGray }, 60 | [ConsoleThemeStyle.LevelError] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Black, Background = ConsoleColor.White }, 61 | [ConsoleThemeStyle.LevelFatal] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Black, Background = ConsoleColor.White }, 62 | }); 63 | 64 | public static SystemConsoleTheme Colored { get; } = new SystemConsoleTheme( 65 | new Dictionary 66 | { 67 | [ConsoleThemeStyle.Text] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray }, 68 | [ConsoleThemeStyle.SecondaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 69 | [ConsoleThemeStyle.TertiaryText] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray }, 70 | [ConsoleThemeStyle.Invalid] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Yellow }, 71 | [ConsoleThemeStyle.Null] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 72 | [ConsoleThemeStyle.Name] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 73 | [ConsoleThemeStyle.String] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 74 | [ConsoleThemeStyle.Number] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 75 | [ConsoleThemeStyle.Boolean] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 76 | [ConsoleThemeStyle.Scalar] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White }, 77 | [ConsoleThemeStyle.LevelVerbose] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.Gray, Background = ConsoleColor.DarkGray }, 78 | [ConsoleThemeStyle.LevelDebug] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.DarkGray }, 79 | [ConsoleThemeStyle.LevelInformation] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.Blue }, 80 | [ConsoleThemeStyle.LevelWarning] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.DarkGray, Background = ConsoleColor.Yellow }, 81 | [ConsoleThemeStyle.LevelError] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.Red }, 82 | [ConsoleThemeStyle.LevelFatal] = new SystemConsoleThemeStyle { Foreground = ConsoleColor.White, Background = ConsoleColor.Red }, 83 | }); 84 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serilog.Sinks.Console [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.Console.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.Console/) [![Documentation](https://img.shields.io/badge/docs-wiki-yellow.svg)](https://github.com/serilog/serilog/wiki) [![Help](https://img.shields.io/badge/stackoverflow-serilog-orange.svg)](http://stackoverflow.com/questions/tagged/serilog) 2 | 3 | A Serilog sink that writes log events to the Windows Console or an ANSI terminal via standard output. Coloring and custom themes are supported, including ANSI 256-color themes on macOS, Linux and Windows 10+. The default output is plain text; JSON formatting can be plugged in using [_Serilog.Formatting.Compact_](https://github.com/serilog/serilog-formatting-compact) or the [fully-customizable](https://nblumhardt.com/2021/06/customize-serilog-json-output/) [_Serilog.Expressions_](https://github.com/serilog/serilog-expressions). 4 | 5 | ### Getting started 6 | 7 | To use the console sink, first install the [NuGet package](https://nuget.org/packages/serilog.sinks.console): 8 | 9 | ```shell 10 | dotnet add package Serilog.Sinks.Console 11 | ``` 12 | 13 | Then enable the sink using `WriteTo.Console()`: 14 | 15 | ```csharp 16 | Log.Logger = new LoggerConfiguration() 17 | .WriteTo.Console() 18 | .CreateLogger(); 19 | 20 | Log.Information("Hello, world!"); 21 | ``` 22 | 23 | Log events will be printed to `STDOUT`: 24 | 25 | ``` 26 | [12:50:51 INF] Hello, world! 27 | ``` 28 | 29 | ### Themes 30 | 31 | The sink will colorize output by default: 32 | 33 | ![Colorized Console](https://raw.githubusercontent.com/serilog/serilog-sinks-console/dev/assets/Screenshot.png) 34 | 35 | Themes can be specified when configuring the sink: 36 | 37 | ```csharp 38 | .WriteTo.Console(theme: AnsiConsoleTheme.Code) 39 | ``` 40 | 41 | The following built-in themes are available: 42 | 43 | * `ConsoleTheme.None` - no styling 44 | * `SystemConsoleTheme.Literate` - styled to replicate _Serilog.Sinks.Literate_, using the `System.Console` coloring modes supported on all Windows/.NET targets; **this is the default when no theme is specified** 45 | * `SystemConsoleTheme.Grayscale` - a theme using only shades of gray, white, and black 46 | * `AnsiConsoleTheme.Literate` - an ANSI 256-color version of the "literate" theme 47 | * `AnsiConsoleTheme.Grayscale` - an ANSI 256-color version of the "grayscale" theme 48 | * `AnsiConsoleTheme.Code` - an ANSI 256-color Visual Studio Code-inspired theme 49 | * `AnsiConsoleTheme.Sixteen` - an ANSI 16-color theme that works well with both light and dark backgrounds 50 | 51 | Adding a new theme is straightforward; examples can be found in the [`SystemConsoleThemes`](https://github.com/serilog/serilog-sinks-console/blob/dev/src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/SystemConsoleThemes.cs) and [`AnsiConsoleThemes`](https://github.com/serilog/serilog-sinks-console/blob/dev/src/Serilog.Sinks.Console/Sinks/SystemConsole/Themes/AnsiConsoleThemes.cs) classes. 52 | 53 | ### Output templates 54 | 55 | The format of events to the console can be modified using the `outputTemplate` configuration parameter: 56 | 57 | ```csharp 58 | .WriteTo.Console( 59 | outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") 60 | ``` 61 | 62 | The default template, shown in the example above, uses built-in properties like `Timestamp` and `Level`. Properties from events, including those attached using [enrichers](https://github.com/serilog/serilog/wiki/Enrichment), can also appear in the output template. 63 | 64 | ### JSON output 65 | 66 | The sink can write JSON output instead of plain text. `CompactJsonFormatter` or `RenderedCompactJsonFormatter` from [Serilog.Formatting.Compact](https://github.com/serilog/serilog-formatting-compact) is recommended: 67 | 68 | ```shell 69 | dotnet add package Serilog.Formatting.Compact 70 | ``` 71 | 72 | Pass a formatter to the `Console()` configuration method: 73 | 74 | ```csharp 75 | .WriteTo.Console(new RenderedCompactJsonFormatter()) 76 | ``` 77 | 78 | Output theming is not available when custom formatters are used. 79 | 80 | ### XML `` configuration 81 | 82 | To use the console sink with the [Serilog.Settings.AppSettings](https://github.com/serilog/serilog-settings-appsettings) package, first install that package if you haven't already done so: 83 | 84 | ```shell 85 | dotnet add package Serilog.Settings.AppSettings 86 | ``` 87 | 88 | Instead of configuring the logger in code, call `ReadFrom.AppSettings()`: 89 | 90 | ```csharp 91 | var log = new LoggerConfiguration() 92 | .ReadFrom.AppSettings() 93 | .CreateLogger(); 94 | ``` 95 | 96 | In your application's `App.config` or `Web.config` file, specify the console sink assembly under the `` node: 97 | 98 | ```xml 99 | 100 | 101 | 102 | 103 | ``` 104 | 105 | To configure the console sink with a different theme and include the `SourceContext` in the output, change your `App.config`/`Web.config` to: 106 | ```xml 107 | 108 | 109 | 110 | 111 | 112 | ``` 113 | 114 | ### JSON `appsettings.json` configuration 115 | 116 | To use the console sink with _Microsoft.Extensions.Configuration_, for example with ASP.NET Core or .NET Core, use the [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) package. First install that package if you have not already done so: 117 | 118 | ```shell 119 | dotnet add package Serilog.Settings.Configuration 120 | ``` 121 | 122 | Instead of configuring the sink directly in code, call `ReadFrom.Configuration()`: 123 | 124 | ```csharp 125 | var configuration = new ConfigurationBuilder() 126 | .AddJsonFile("appsettings.json") 127 | .Build(); 128 | 129 | var logger = new LoggerConfiguration() 130 | .ReadFrom.Configuration(configuration) 131 | .CreateLogger(); 132 | ``` 133 | 134 | In your `appsettings.json` file, under the `Serilog` node, : 135 | ```json 136 | { 137 | "Serilog": { 138 | "WriteTo": [{"Name": "Console"}] 139 | } 140 | } 141 | ``` 142 | 143 | To configure the console sink with a different theme and include the `SourceContext` in the output, change your `appsettings.json` to: 144 | ```json 145 | { 146 | "Serilog": { 147 | "WriteTo": [ 148 | { 149 | "Name": "Console", 150 | "Args": { 151 | "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console", 152 | "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {NewLine}{Exception}" 153 | } 154 | } 155 | ] 156 | } 157 | } 158 | ``` 159 | 160 | ### Performance 161 | 162 | Console logging is synchronous and this can cause bottlenecks in some deployment scenarios. For high-volume console logging, consider using [_Serilog.Sinks.Async_](https://github.com/serilog/serilog-sinks-async) to move console writes to a background thread: 163 | 164 | ```csharp 165 | // dotnet add package serilog.sinks.async 166 | 167 | Log.Logger = new LoggerConfiguration() 168 | .WriteTo.Async(wt => wt.Console()) 169 | .CreateLogger(); 170 | ``` 171 | 172 | ### Contributing 173 | 174 | Would you like to help make the Serilog console sink even better? We keep a list of issues that are approachable for newcomers under the [up-for-grabs](https://github.com/serilog/serilog-sinks-console/issues?labels=up-for-grabs&state=open) label. Before starting work on a pull request, we suggest commenting on, or raising, an issue on the issue tracker so that we can help and coordinate efforts. For more details check out our [contributing guide](CONTRIBUTING.md). 175 | 176 | When contributing please keep in mind our [Code of Conduct](CODE_OF_CONDUCT.md). 177 | 178 | _Copyright © Serilog Contributors - Provided under the [Apache License, Version 2.0](http://apache.org/licenses/LICENSE-2.0.html)._ 179 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Rendering/ThemedMessageTemplateRendererTests.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Sinks.SystemConsole.Formatting; 2 | using Serilog.Sinks.SystemConsole.Rendering; 3 | using Serilog.Sinks.SystemConsole.Themes; 4 | using System; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace Serilog.Sinks.Console.Tests.Rendering; 12 | 13 | public class ThemedMessageTemplateRendererTests 14 | { 15 | class Chair 16 | { 17 | // ReSharper disable UnusedMember.Local 18 | public string Back => "straight"; 19 | 20 | public int[] Legs => new[] { 1, 2, 3, 4 }; 21 | 22 | // ReSharper restore UnusedMember.Local 23 | public override string ToString() => "a chair"; 24 | } 25 | 26 | class Receipt 27 | { 28 | // ReSharper disable UnusedMember.Local 29 | public decimal Sum => 12.345m; 30 | 31 | public DateTime When => new DateTime(2013, 5, 20, 16, 39, 0); 32 | 33 | // ReSharper restore UnusedMember.Local 34 | public override string ToString() => "a receipt"; 35 | } 36 | 37 | [Fact] 38 | public void AnObjectIsRenderedInSimpleNotation() 39 | { 40 | var m = Render("I sat at {@Chair}", new Chair()); 41 | Assert.Equal("I sat at Chair {Back=\"straight\", Legs=[1, 2, 3, 4]}", m); 42 | } 43 | 44 | [Fact] 45 | public void AnObjectIsRenderedInSimpleNotationUsingFormatProvider() 46 | { 47 | var m = Render(new CultureInfo("fr-FR"), "I received {@Receipt}", new Receipt()); 48 | Assert.Equal("I received Receipt {Sum=12,345, When=20/05/2013 16:39:00}", m); 49 | } 50 | 51 | [Fact] 52 | public void AnAnonymousObjectIsRenderedInSimpleNotationWithoutType() 53 | { 54 | var m = Render("I sat at {@Chair}", new { Back = "straight", Legs = new[] { 1, 2, 3, 4 } }); 55 | Assert.Equal("I sat at {Back=\"straight\", Legs=[1, 2, 3, 4]}", m); 56 | } 57 | 58 | [Fact] 59 | public void AnAnonymousObjectIsRenderedInSimpleNotationWithoutTypeUsingFormatProvider() 60 | { 61 | var m = Render(new CultureInfo("fr-FR"), "I received {@Receipt}", new { Sum = 12.345, When = new DateTime(2013, 5, 20, 16, 39, 0) }); 62 | Assert.Equal("I received {Sum=12,345, When=20/05/2013 16:39:00}", m); 63 | } 64 | 65 | [Fact] 66 | public void AnObjectWithDefaultDestructuringIsRenderedAsAStringLiteral() 67 | { 68 | var m = Render("I sat at {Chair}", new Chair()); 69 | Assert.Equal("I sat at \"a chair\"", m); 70 | } 71 | 72 | [Fact] 73 | public void AnObjectWithStringifyDestructuringIsRenderedAsAString() 74 | { 75 | var m = Render("I sat at {$Chair}", new Chair()); 76 | Assert.Equal("I sat at \"a chair\"", m); 77 | } 78 | 79 | [Fact] 80 | public void MultiplePropertiesAreRenderedInOrder() 81 | { 82 | var m = Render("Just biting {Fruit} number {Count}", "Apple", 12); 83 | Assert.Equal("Just biting \"Apple\" number 12", m); 84 | } 85 | 86 | [Fact] 87 | public void MultiplePropertiesUseFormatProvider() 88 | { 89 | var m = Render(new CultureInfo("fr-FR"), "Income was {Income} at {Date:d}", 1234.567, new DateTime(2013, 5, 20)); 90 | Assert.Equal("Income was 1234,567 at 20/05/2013", m); 91 | } 92 | 93 | [Fact] 94 | public void FormatStringsArePropagated() 95 | { 96 | var m = Render("Welcome, customer {CustomerId:0000}", 12); 97 | Assert.Equal("Welcome, customer 0012", m); 98 | } 99 | 100 | [Theory] 101 | [InlineData("Welcome, customer #{CustomerId,-10}, pleasure to see you", "Welcome, customer #1234 , pleasure to see you")] 102 | [InlineData("Welcome, customer #{CustomerId,-10:000000}, pleasure to see you", "Welcome, customer #001234 , pleasure to see you")] 103 | [InlineData("Welcome, customer #{CustomerId,10}, pleasure to see you", "Welcome, customer # 1234, pleasure to see you")] 104 | [InlineData("Welcome, customer #{CustomerId,10:000000}, pleasure to see you", "Welcome, customer # 001234, pleasure to see you")] 105 | [InlineData("Welcome, customer #{CustomerId,10:0,0}, pleasure to see you", "Welcome, customer # 1,234, pleasure to see you")] 106 | [InlineData("Welcome, customer #{CustomerId:0,0}, pleasure to see you", "Welcome, customer #1,234, pleasure to see you")] 107 | public void AlignmentStringsArePropagated(string value, string expected) 108 | { 109 | Assert.Equal(expected, Render(value, 1234)); 110 | } 111 | 112 | [Fact] 113 | public void FormatProviderIsUsed() 114 | { 115 | var m = Render(new CultureInfo("fr-FR"), "Please pay {Sum}", 12.345); 116 | Assert.Equal("Please pay 12,345", m); 117 | } 118 | 119 | static string Render(string messageTemplate, params object[] properties) 120 | { 121 | return Render(null, messageTemplate, properties); 122 | } 123 | 124 | static string Render(IFormatProvider? formatProvider, string messageTemplate, params object[] properties) 125 | { 126 | var binder = new LoggerConfiguration().CreateLogger(); 127 | if (binder.BindMessageTemplate(messageTemplate, properties, out var mt, out var props) == false) 128 | throw new InvalidOperationException(); 129 | 130 | var output = new StringBuilder(); 131 | var writer = new StringWriter(output); 132 | var renderer = new ThemedMessageTemplateRenderer(ConsoleTheme.None, 133 | new ThemedDisplayValueFormatter(ConsoleTheme.None, formatProvider), false); 134 | renderer.Render(mt, props.ToDictionary(p => p.Name, p => p.Value), writer); 135 | writer.Flush(); 136 | return output.ToString(); 137 | } 138 | 139 | [Fact] 140 | public void ATemplateWithOnlyPositionalPropertiesIsAnalyzedAndRenderedPositionally() 141 | { 142 | var m = Render("{1}, {0}", "world", "Hello"); 143 | Assert.Equal("\"Hello\", \"world\"", m); 144 | } 145 | 146 | [Fact] 147 | public void ATemplateWithOnlyPositionalPropertiesUsesFormatProvider() 148 | { 149 | var m = Render(new CultureInfo("fr-FR"), "{1}, {0}", 12.345, "Hello"); 150 | Assert.Equal("\"Hello\", 12,345", m); 151 | } 152 | 153 | // Debatable what the behavior should be, here. 154 | [Fact] 155 | public void ATemplateWithNamesAndPositionalsUsesNamesForAllValues() 156 | { 157 | var m = Render("{1}, {Place}", "world", "Hello"); 158 | Assert.Equal("\"world\", \"Hello\"", m); 159 | } 160 | 161 | [Fact] 162 | public void MissingPositionalParametersRenderAsTextLikeStandardFormats() 163 | { 164 | var m = Render("{1}, {0}", "world"); 165 | Assert.Equal("{1}, \"world\"", m); 166 | } 167 | 168 | [Fact] 169 | public void AnonymousTypeShouldBeRendered() 170 | { 171 | var anonymous = new { Test = 3M }; 172 | var m = Render("Anonymous type {value}", anonymous); 173 | Assert.Equal("Anonymous type \"{ Test = 3 }\"", m); 174 | } 175 | 176 | [Fact] 177 | public void EnumerableOfAnonymousTypeShouldBeRendered() 178 | { 179 | var anonymous = new { Foo = 4M, Bar = "Baz" }; 180 | var enumerable = Enumerable.Repeat("MyKey", 1).Select(v => anonymous); 181 | var m = Render("Enumerable with anonymous type {enumerable}", enumerable); 182 | Assert.Equal("Enumerable with anonymous type [\"{ Foo = 4, Bar = Baz }\"]", m); 183 | } 184 | 185 | [Fact] 186 | public void DictionaryOfAnonymousTypeAsValueShouldBeRendered() 187 | { 188 | var anonymous = new { Test = 5M }; 189 | var dictionary = Enumerable.Repeat("MyKey", 1).ToDictionary(v => v, v => anonymous); 190 | var m = Render("Dictionary with anonymous type value {dictionary}", dictionary); 191 | Assert.Equal("Dictionary with anonymous type value {[\"MyKey\"]=\"{ Test = 5 }\"}", m); 192 | } 193 | 194 | [Fact] 195 | public void DictionaryOfAnonymousTypeAsKeyShouldBeRendered() 196 | { 197 | var anonymous = new { Bar = 6M, Baz = 4M }; 198 | var dictionary = Enumerable.Repeat("MyValue", 1).ToDictionary(v => anonymous, v => v); 199 | var m = Render("Dictionary with anonymous type key {dictionary}", dictionary); 200 | Assert.Equal("Dictionary with anonymous type key [\"[{ Bar = 6, Baz = 4 }, MyValue]\"]", m); 201 | } 202 | } -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Formatting/ThemedDisplayValueFormatter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | using Serilog.Events; 18 | using Serilog.Formatting.Json; 19 | using Serilog.Sinks.SystemConsole.Themes; 20 | 21 | namespace Serilog.Sinks.SystemConsole.Formatting; 22 | 23 | class ThemedDisplayValueFormatter : ThemedValueFormatter 24 | { 25 | readonly IFormatProvider? _formatProvider; 26 | 27 | public ThemedDisplayValueFormatter(ConsoleTheme theme, IFormatProvider? formatProvider) 28 | : base(theme) 29 | { 30 | _formatProvider = formatProvider; 31 | } 32 | 33 | public override ThemedValueFormatter SwitchTheme(ConsoleTheme theme) 34 | { 35 | return new ThemedDisplayValueFormatter(theme, _formatProvider); 36 | } 37 | 38 | protected override int VisitScalarValue(ThemedValueFormatterState state, ScalarValue scalar) 39 | { 40 | if (scalar is null) 41 | throw new ArgumentNullException(nameof(scalar)); 42 | return FormatLiteralValue(scalar, state.Output, state.Format); 43 | } 44 | 45 | protected override int VisitSequenceValue(ThemedValueFormatterState state, SequenceValue sequence) 46 | { 47 | if (sequence is null) 48 | throw new ArgumentNullException(nameof(sequence)); 49 | 50 | var count = 0; 51 | 52 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 53 | state.Output.Write('['); 54 | 55 | var delim = string.Empty; 56 | for (var index = 0; index < sequence.Elements.Count; ++index) 57 | { 58 | if (delim.Length != 0) 59 | { 60 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 61 | state.Output.Write(delim); 62 | } 63 | 64 | delim = ", "; 65 | Visit(state, sequence.Elements[index]); 66 | } 67 | 68 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 69 | state.Output.Write(']'); 70 | 71 | return count; 72 | } 73 | 74 | protected override int VisitStructureValue(ThemedValueFormatterState state, StructureValue structure) 75 | { 76 | var count = 0; 77 | 78 | if (structure.TypeTag != null) 79 | { 80 | using (ApplyStyle(state.Output, ConsoleThemeStyle.Name, ref count)) 81 | state.Output.Write(structure.TypeTag); 82 | 83 | state.Output.Write(' '); 84 | } 85 | 86 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 87 | state.Output.Write('{'); 88 | 89 | var delim = string.Empty; 90 | for (var index = 0; index < structure.Properties.Count; ++index) 91 | { 92 | if (delim.Length != 0) 93 | { 94 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 95 | state.Output.Write(delim); 96 | } 97 | 98 | delim = ", "; 99 | 100 | var property = structure.Properties[index]; 101 | 102 | using (ApplyStyle(state.Output, ConsoleThemeStyle.Name, ref count)) 103 | state.Output.Write(property.Name); 104 | 105 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 106 | state.Output.Write('='); 107 | 108 | count += Visit(state.Nest(), property.Value); 109 | } 110 | 111 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 112 | state.Output.Write('}'); 113 | 114 | return count; 115 | } 116 | 117 | protected override int VisitDictionaryValue(ThemedValueFormatterState state, DictionaryValue dictionary) 118 | { 119 | var count = 0; 120 | 121 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 122 | state.Output.Write('{'); 123 | 124 | var delim = string.Empty; 125 | foreach (var element in dictionary.Elements) 126 | { 127 | if (delim.Length != 0) 128 | { 129 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 130 | state.Output.Write(delim); 131 | } 132 | 133 | delim = ", "; 134 | 135 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 136 | state.Output.Write('['); 137 | 138 | using (ApplyStyle(state.Output, ConsoleThemeStyle.String, ref count)) 139 | count += Visit(state.Nest(), element.Key); 140 | 141 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 142 | state.Output.Write("]="); 143 | 144 | count += Visit(state.Nest(), element.Value); 145 | } 146 | 147 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 148 | state.Output.Write('}'); 149 | 150 | return count; 151 | } 152 | 153 | public int FormatLiteralValue(ScalarValue scalar, TextWriter output, string? format) 154 | { 155 | var value = scalar.Value; 156 | var count = 0; 157 | 158 | if (value is null) 159 | { 160 | using (ApplyStyle(output, ConsoleThemeStyle.Null, ref count)) 161 | output.Write("null"); 162 | return count; 163 | } 164 | 165 | if (value is string str) 166 | { 167 | using (ApplyStyle(output, ConsoleThemeStyle.String, ref count)) 168 | { 169 | if (format != "l") 170 | JsonValueFormatter.WriteQuotedJsonString(str, output); 171 | else 172 | output.Write(str); 173 | } 174 | return count; 175 | } 176 | 177 | if (value is ValueType) 178 | { 179 | if (value is int || value is uint || value is long || value is ulong || 180 | value is decimal || value is byte || value is sbyte || value is short || 181 | value is ushort || value is float || value is double) 182 | { 183 | using (ApplyStyle(output, ConsoleThemeStyle.Number, ref count)) 184 | scalar.Render(output, format, _formatProvider); 185 | return count; 186 | } 187 | 188 | if (value is bool b) 189 | { 190 | using (ApplyStyle(output, ConsoleThemeStyle.Boolean, ref count)) 191 | output.Write(b); 192 | 193 | return count; 194 | } 195 | 196 | if (value is char ch) 197 | { 198 | using (ApplyStyle(output, ConsoleThemeStyle.Scalar, ref count)) 199 | { 200 | output.Write('\''); 201 | output.Write(ch); 202 | output.Write('\''); 203 | } 204 | return count; 205 | } 206 | } 207 | 208 | using (ApplyStyle(output, ConsoleThemeStyle.Scalar, ref count)) 209 | scalar.Render(output, format, _formatProvider); 210 | 211 | return count; 212 | } 213 | } -------------------------------------------------------------------------------- /assets/Serilog.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 44 | 55 | 56 | 57 | 70 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 110 | 117 | 123 | 128 | 131 | 134 | 137 | 140 | 143 | 146 | 149 | 156 | 163 | -------------------------------------------------------------------------------- /src/Serilog.Sinks.Console/Sinks/SystemConsole/Formatting/ThemedJsonValueFormatter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Serilog Contributors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Globalization; 17 | using System.IO; 18 | using Serilog.Events; 19 | using Serilog.Formatting.Json; 20 | using Serilog.Sinks.SystemConsole.Themes; 21 | 22 | namespace Serilog.Sinks.SystemConsole.Formatting; 23 | 24 | class ThemedJsonValueFormatter : ThemedValueFormatter 25 | { 26 | readonly ThemedDisplayValueFormatter _displayFormatter; 27 | readonly IFormatProvider? _formatProvider; 28 | 29 | public ThemedJsonValueFormatter(ConsoleTheme theme, IFormatProvider? formatProvider) 30 | : base(theme) 31 | { 32 | _displayFormatter = new ThemedDisplayValueFormatter(theme, formatProvider); 33 | _formatProvider = formatProvider; 34 | } 35 | 36 | public override ThemedValueFormatter SwitchTheme(ConsoleTheme theme) 37 | { 38 | return new ThemedJsonValueFormatter(theme, _formatProvider); 39 | } 40 | 41 | protected override int VisitScalarValue(ThemedValueFormatterState state, ScalarValue scalar) 42 | { 43 | if (scalar is null) 44 | throw new ArgumentNullException(nameof(scalar)); 45 | 46 | // At the top level, for scalar values, use "display" rendering. 47 | if (state.IsTopLevel) 48 | return _displayFormatter.FormatLiteralValue(scalar, state.Output, state.Format); 49 | 50 | return FormatLiteralValue(scalar, state.Output); 51 | } 52 | 53 | protected override int VisitSequenceValue(ThemedValueFormatterState state, SequenceValue sequence) 54 | { 55 | if (sequence == null) 56 | throw new ArgumentNullException(nameof(sequence)); 57 | 58 | var count = 0; 59 | 60 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 61 | state.Output.Write('['); 62 | 63 | var delim = string.Empty; 64 | for (var index = 0; index < sequence.Elements.Count; ++index) 65 | { 66 | if (delim.Length != 0) 67 | { 68 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 69 | state.Output.Write(delim); 70 | } 71 | 72 | delim = ", "; 73 | count += Visit(state.Nest(), sequence.Elements[index]); 74 | } 75 | 76 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 77 | state.Output.Write(']'); 78 | 79 | return count; 80 | } 81 | 82 | protected override int VisitStructureValue(ThemedValueFormatterState state, StructureValue structure) 83 | { 84 | var count = 0; 85 | 86 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 87 | state.Output.Write('{'); 88 | 89 | var delim = string.Empty; 90 | for (var index = 0; index < structure.Properties.Count; ++index) 91 | { 92 | if (delim.Length != 0) 93 | { 94 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 95 | state.Output.Write(delim); 96 | } 97 | 98 | delim = ", "; 99 | 100 | var property = structure.Properties[index]; 101 | 102 | using (ApplyStyle(state.Output, ConsoleThemeStyle.Name, ref count)) 103 | JsonValueFormatter.WriteQuotedJsonString(property.Name, state.Output); 104 | 105 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 106 | state.Output.Write(": "); 107 | 108 | count += Visit(state.Nest(), property.Value); 109 | } 110 | 111 | if (structure.TypeTag != null) 112 | { 113 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 114 | state.Output.Write(delim); 115 | 116 | using (ApplyStyle(state.Output, ConsoleThemeStyle.Name, ref count)) 117 | JsonValueFormatter.WriteQuotedJsonString("$type", state.Output); 118 | 119 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 120 | state.Output.Write(": "); 121 | 122 | using (ApplyStyle(state.Output, ConsoleThemeStyle.String, ref count)) 123 | JsonValueFormatter.WriteQuotedJsonString(structure.TypeTag, state.Output); 124 | } 125 | 126 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 127 | state.Output.Write('}'); 128 | 129 | return count; 130 | } 131 | 132 | protected override int VisitDictionaryValue(ThemedValueFormatterState state, DictionaryValue dictionary) 133 | { 134 | var count = 0; 135 | 136 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 137 | state.Output.Write('{'); 138 | 139 | var delim = string.Empty; 140 | foreach (var element in dictionary.Elements) 141 | { 142 | if (delim.Length != 0) 143 | { 144 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 145 | state.Output.Write(delim); 146 | } 147 | 148 | delim = ", "; 149 | 150 | var style = element.Key.Value == null 151 | ? ConsoleThemeStyle.Null 152 | : element.Key.Value is string 153 | ? ConsoleThemeStyle.String 154 | : ConsoleThemeStyle.Scalar; 155 | 156 | using (ApplyStyle(state.Output, style, ref count)) 157 | JsonValueFormatter.WriteQuotedJsonString((element.Key.Value ?? "null").ToString() ?? "", state.Output); 158 | 159 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 160 | state.Output.Write(": "); 161 | 162 | count += Visit(state.Nest(), element.Value); 163 | } 164 | 165 | using (ApplyStyle(state.Output, ConsoleThemeStyle.TertiaryText, ref count)) 166 | state.Output.Write('}'); 167 | 168 | return count; 169 | } 170 | 171 | int FormatLiteralValue(ScalarValue scalar, TextWriter output) 172 | { 173 | var value = scalar.Value; 174 | var count = 0; 175 | 176 | if (value == null) 177 | { 178 | using (ApplyStyle(output, ConsoleThemeStyle.Null, ref count)) 179 | output.Write("null"); 180 | return count; 181 | } 182 | 183 | if (value is string str) 184 | { 185 | using (ApplyStyle(output, ConsoleThemeStyle.String, ref count)) 186 | JsonValueFormatter.WriteQuotedJsonString(str, output); 187 | return count; 188 | } 189 | 190 | if (value is ValueType) 191 | { 192 | if (value is int || value is uint || value is long || value is ulong || value is decimal || value is byte || value is sbyte || value is short || value is ushort) 193 | { 194 | using (ApplyStyle(output, ConsoleThemeStyle.Number, ref count)) 195 | output.Write(((IFormattable)value).ToString(null, CultureInfo.InvariantCulture)); 196 | return count; 197 | } 198 | 199 | if (value is double d) 200 | { 201 | using (ApplyStyle(output, ConsoleThemeStyle.Number, ref count)) 202 | { 203 | if (double.IsNaN(d) || double.IsInfinity(d)) 204 | JsonValueFormatter.WriteQuotedJsonString(d.ToString(CultureInfo.InvariantCulture), output); 205 | else 206 | output.Write(d.ToString("R", CultureInfo.InvariantCulture)); 207 | } 208 | return count; 209 | } 210 | 211 | if (value is float f) 212 | { 213 | using (ApplyStyle(output, ConsoleThemeStyle.Number, ref count)) 214 | { 215 | if (double.IsNaN(f) || double.IsInfinity(f)) 216 | JsonValueFormatter.WriteQuotedJsonString(f.ToString(CultureInfo.InvariantCulture), output); 217 | else 218 | output.Write(f.ToString("R", CultureInfo.InvariantCulture)); 219 | } 220 | return count; 221 | } 222 | 223 | if (value is bool b) 224 | { 225 | using (ApplyStyle(output, ConsoleThemeStyle.Boolean, ref count)) 226 | output.Write(b ? "true" : "false"); 227 | 228 | return count; 229 | } 230 | 231 | if (value is char ch) 232 | { 233 | using (ApplyStyle(output, ConsoleThemeStyle.Scalar, ref count)) 234 | JsonValueFormatter.WriteQuotedJsonString(ch.ToString(), output); 235 | return count; 236 | } 237 | 238 | if (value is DateTime || value is DateTimeOffset) 239 | { 240 | using (ApplyStyle(output, ConsoleThemeStyle.Scalar, ref count)) 241 | { 242 | output.Write('"'); 243 | output.Write(((IFormattable)value).ToString("O", CultureInfo.InvariantCulture)); 244 | output.Write('"'); 245 | } 246 | return count; 247 | } 248 | } 249 | 250 | using (ApplyStyle(output, ConsoleThemeStyle.Scalar, ref count)) 251 | JsonValueFormatter.WriteQuotedJsonString(value.ToString() ?? "", output); 252 | 253 | return count; 254 | } 255 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using Serilog.Events; 7 | using Serilog.Parsing; 8 | using Serilog.Sinks.Console.Tests.Support; 9 | using Serilog.Sinks.SystemConsole.Output; 10 | using Serilog.Sinks.SystemConsole.Themes; 11 | using Xunit; 12 | 13 | namespace Serilog.Sinks.Console.Tests.Output; 14 | 15 | public class OutputTemplateRendererTests 16 | { 17 | [Fact] 18 | public void UsesFormatProvider() 19 | { 20 | var french = new CultureInfo("fr-FR"); 21 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message}", french); 22 | var evt = DelegatingSink.GetLogEvent(l => l.Information("{0}", 12.345)); 23 | var sw = new StringWriter(); 24 | formatter.Format(evt, sw); 25 | Assert.Equal("12,345", sw.ToString()); 26 | } 27 | 28 | [Fact] 29 | public void MessageTemplatesContainingFormatStringPropertiesRenderCorrectly() 30 | { 31 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message}", CultureInfo.InvariantCulture); 32 | var evt = DelegatingSink.GetLogEvent(l => l.Information("{Message}", "Hello, world!")); 33 | var sw = new StringWriter(); 34 | formatter.Format(evt, sw); 35 | Assert.Equal("\"Hello, world!\"", sw.ToString()); 36 | } 37 | 38 | [Fact] 39 | public void UppercaseFormatSpecifierIsSupportedForStrings() 40 | { 41 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Name:u}", CultureInfo.InvariantCulture); 42 | var evt = DelegatingSink.GetLogEvent(l => l.Information("{Name}", "Nick")); 43 | var sw = new StringWriter(); 44 | formatter.Format(evt, sw); 45 | Assert.Equal("NICK", sw.ToString()); 46 | } 47 | 48 | [Fact] 49 | public void LowercaseFormatSpecifierIsSupportedForStrings() 50 | { 51 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Name:w}", CultureInfo.InvariantCulture); 52 | var evt = DelegatingSink.GetLogEvent(l => l.Information("{Name}", "Nick")); 53 | var sw = new StringWriter(); 54 | formatter.Format(evt, sw); 55 | Assert.Equal("nick", sw.ToString()); 56 | } 57 | 58 | [Theory] 59 | [InlineData(LogEventLevel.Verbose, 1, "V")] 60 | [InlineData(LogEventLevel.Verbose, 2, "Vb")] 61 | [InlineData(LogEventLevel.Verbose, 3, "Vrb")] 62 | [InlineData(LogEventLevel.Verbose, 4, "Verb")] 63 | [InlineData(LogEventLevel.Verbose, 5, "Verbo")] 64 | [InlineData(LogEventLevel.Verbose, 6, "Verbos")] 65 | [InlineData(LogEventLevel.Verbose, 7, "Verbose")] 66 | [InlineData(LogEventLevel.Verbose, 8, "Verbose")] 67 | [InlineData(LogEventLevel.Debug, 1, "D")] 68 | [InlineData(LogEventLevel.Debug, 2, "De")] 69 | [InlineData(LogEventLevel.Debug, 3, "Dbg")] 70 | [InlineData(LogEventLevel.Debug, 4, "Dbug")] 71 | [InlineData(LogEventLevel.Debug, 5, "Debug")] 72 | [InlineData(LogEventLevel.Debug, 6, "Debug")] 73 | [InlineData(LogEventLevel.Information, 1, "I")] 74 | [InlineData(LogEventLevel.Information, 2, "In")] 75 | [InlineData(LogEventLevel.Information, 3, "Inf")] 76 | [InlineData(LogEventLevel.Information, 4, "Info")] 77 | [InlineData(LogEventLevel.Information, 5, "Infor")] 78 | [InlineData(LogEventLevel.Information, 6, "Inform")] 79 | [InlineData(LogEventLevel.Information, 7, "Informa")] 80 | [InlineData(LogEventLevel.Information, 8, "Informat")] 81 | [InlineData(LogEventLevel.Information, 9, "Informati")] 82 | [InlineData(LogEventLevel.Information, 10, "Informatio")] 83 | [InlineData(LogEventLevel.Information, 11, "Information")] 84 | [InlineData(LogEventLevel.Information, 12, "Information")] 85 | [InlineData(LogEventLevel.Error, 1, "E")] 86 | [InlineData(LogEventLevel.Error, 2, "Er")] 87 | [InlineData(LogEventLevel.Error, 3, "Err")] 88 | [InlineData(LogEventLevel.Error, 4, "Eror")] 89 | [InlineData(LogEventLevel.Error, 5, "Error")] 90 | [InlineData(LogEventLevel.Error, 6, "Error")] 91 | [InlineData(LogEventLevel.Fatal, 1, "F")] 92 | [InlineData(LogEventLevel.Fatal, 2, "Fa")] 93 | [InlineData(LogEventLevel.Fatal, 3, "Ftl")] 94 | [InlineData(LogEventLevel.Fatal, 4, "Fatl")] 95 | [InlineData(LogEventLevel.Fatal, 5, "Fatal")] 96 | [InlineData(LogEventLevel.Fatal, 6, "Fatal")] 97 | [InlineData(LogEventLevel.Warning, 1, "W")] 98 | [InlineData(LogEventLevel.Warning, 2, "Wn")] 99 | [InlineData(LogEventLevel.Warning, 3, "Wrn")] 100 | [InlineData(LogEventLevel.Warning, 4, "Warn")] 101 | [InlineData(LogEventLevel.Warning, 5, "Warni")] 102 | [InlineData(LogEventLevel.Warning, 6, "Warnin")] 103 | [InlineData(LogEventLevel.Warning, 7, "Warning")] 104 | [InlineData(LogEventLevel.Warning, 8, "Warning")] 105 | public void FixedLengthLevelIsSupported( 106 | LogEventLevel level, 107 | int width, 108 | string expected) 109 | { 110 | var formatter1 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture); 111 | var evt1 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello")); 112 | var sw1 = new StringWriter(); 113 | formatter1.Format(evt1, sw1); 114 | Assert.Equal(expected, sw1.ToString()); 115 | 116 | var formatter2 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:u{width}}}", CultureInfo.InvariantCulture); 117 | var evt2 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello")); 118 | var sw2 = new StringWriter(); 119 | formatter2.Format(evt2, sw2); 120 | Assert.Equal(expected.ToUpper(), sw2.ToString()); 121 | 122 | var formatter3 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:w{width}}}", CultureInfo.InvariantCulture); 123 | var evt3 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello")); 124 | var sw3 = new StringWriter(); 125 | formatter3.Format(evt3, sw3); 126 | Assert.Equal(expected.ToLower(), sw3.ToString()); 127 | } 128 | 129 | [Fact] 130 | public void FixedLengthLevelSupportsUpperCasing() 131 | { 132 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:u3}", CultureInfo.InvariantCulture); 133 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 134 | var sw = new StringWriter(); 135 | formatter.Format(evt, sw); 136 | Assert.Equal("INF", sw.ToString()); 137 | } 138 | 139 | [Fact] 140 | public void FixedLengthLevelSupportsLowerCasing() 141 | { 142 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:w3}", CultureInfo.InvariantCulture); 143 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 144 | var sw = new StringWriter(); 145 | formatter.Format(evt, sw); 146 | Assert.Equal("inf", sw.ToString()); 147 | } 148 | 149 | [Fact] 150 | public void FixedLengthLevelSupportsCasingForWideNames() 151 | { 152 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:w6}", CultureInfo.InvariantCulture); 153 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 154 | var sw = new StringWriter(); 155 | formatter.Format(evt, sw); 156 | Assert.Equal("inform", sw.ToString()); 157 | } 158 | 159 | [Fact] 160 | public void DefaultLevelLengthIsFullText() 161 | { 162 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level}", CultureInfo.InvariantCulture); 163 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 164 | var sw = new StringWriter(); 165 | formatter.Format(evt, sw); 166 | Assert.Equal("Information", sw.ToString()); 167 | } 168 | 169 | [Fact] 170 | public void AlignmentAndWidthCanBeCombined() 171 | { 172 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level,5:w3}", CultureInfo.InvariantCulture); 173 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 174 | var sw = new StringWriter(); 175 | formatter.Format(evt, sw); 176 | Assert.Equal(" inf", sw.ToString()); 177 | } 178 | 179 | enum Size 180 | { 181 | Large 182 | } 183 | 184 | class SizeFormatter : IFormatProvider, ICustomFormatter 185 | { 186 | readonly IFormatProvider _innerFormatProvider; 187 | 188 | public SizeFormatter(IFormatProvider innerFormatProvider) 189 | { 190 | _innerFormatProvider = innerFormatProvider; 191 | } 192 | 193 | public object GetFormat(Type? formatType) 194 | { 195 | return formatType == typeof(ICustomFormatter) ? this : _innerFormatProvider.GetFormat(formatType) ?? this; 196 | } 197 | 198 | public string Format(string? format, object? arg, IFormatProvider? formatProvider) 199 | { 200 | if (arg is Size size) 201 | return size == Size.Large ? "Huge" : size.ToString(); 202 | 203 | if (arg is IFormattable formattable) 204 | return formattable.ToString(format, _innerFormatProvider); 205 | 206 | return arg?.ToString() ?? ""; 207 | } 208 | } 209 | 210 | [Fact] 211 | public void AppliesCustomFormatterToEnums() 212 | { 213 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message}", new SizeFormatter(CultureInfo.InvariantCulture)); 214 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Size {Size}", Size.Large)); 215 | var sw = new StringWriter(); 216 | formatter.Format(evt, sw); 217 | Assert.Equal("Size Huge", sw.ToString()); 218 | } 219 | 220 | [Fact] 221 | public void NonMessagePropertiesAreRendered() 222 | { 223 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Properties}", CultureInfo.InvariantCulture); 224 | var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Foo", 42).Information("Hello from {Bar}!", "bar")); 225 | var sw = new StringWriter(); 226 | formatter.Format(evt, sw); 227 | Assert.Equal("{Foo=42}", sw.ToString()); 228 | } 229 | 230 | [Fact] 231 | public void NonMessagePositionalPropertiesAreRendered() 232 | { 233 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Properties}", CultureInfo.InvariantCulture); 234 | var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Foo", 42).Information("Hello from {0}!", "bar")); 235 | var sw = new StringWriter(); 236 | formatter.Format(evt, sw); 237 | Assert.Equal("{Foo=42}", sw.ToString()); 238 | } 239 | 240 | [Fact] 241 | public void DoNotDuplicatePropertiesAlreadyRenderedInOutputTemplate() 242 | { 243 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Foo} {Properties}", CultureInfo.InvariantCulture); 244 | var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Foo", 42).ForContext("Bar", 42).Information("Hello from bar!")); 245 | var sw = new StringWriter(); 246 | formatter.Format(evt, sw); 247 | Assert.Equal("42 {Bar=42}", sw.ToString()); 248 | } 249 | 250 | [Theory] 251 | [InlineData("", "Hello, \"World\"!")] 252 | [InlineData(":j", "Hello, \"World\"!")] 253 | [InlineData(":l", "Hello, World!")] 254 | [InlineData(":lj", "Hello, World!")] 255 | [InlineData(":jl", "Hello, World!")] 256 | public void AppliesLiteralFormattingToMessageStringsWhenSpecified(string format, string expected) 257 | { 258 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message" + format + "}", null); 259 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello, {Name}!", "World")); 260 | var sw = new StringWriter(); 261 | formatter.Format(evt, sw); 262 | Assert.Equal(expected, sw.ToString()); 263 | } 264 | 265 | [Theory] 266 | [InlineData("", "{Name=\"World\"}")] 267 | [InlineData(":j", "{\"Name\": \"World\"}")] 268 | [InlineData(":lj", "{\"Name\": \"World\"}")] 269 | [InlineData(":jl", "{\"Name\": \"World\"}")] 270 | public void AppliesJsonFormattingToMessageStructuresWhenSpecified(string format, string expected) 271 | { 272 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message" + format + "}", null); 273 | var evt = DelegatingSink.GetLogEvent(l => l.Information("{@Obj}", new { Name = "World" })); 274 | var sw = new StringWriter(); 275 | formatter.Format(evt, sw); 276 | Assert.Equal(expected, sw.ToString()); 277 | } 278 | 279 | [Theory] 280 | [InlineData("", "{Name=\"World\"}")] 281 | [InlineData(":j", "{\"Name\": \"World\"}")] 282 | [InlineData(":lj", "{\"Name\": \"World\"}")] 283 | [InlineData(":jl", "{\"Name\": \"World\"}")] 284 | public void AppliesJsonFormattingToPropertiesTokenWhenSpecified(string format, string expected) 285 | { 286 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Properties" + format + "}", null); 287 | var evt = DelegatingSink.GetLogEvent(l => l.ForContext("Name", "World").Information("Hello")); 288 | var sw = new StringWriter(); 289 | formatter.Format(evt, sw); 290 | Assert.Equal(expected, sw.ToString()); 291 | } 292 | 293 | [Fact] 294 | public void AnEmptyPropertiesTokenIsAnEmptyStructureValue() 295 | { 296 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Properties}", null); 297 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello")); 298 | var sw = new StringWriter(); 299 | formatter.Format(evt, sw); 300 | 301 | // /!\ different behavior from Serilog Core : https://github.com/serilog/serilog/blob/5c3a7821aa0f654e551dc21e8e19089f6767666b/test/Serilog.Tests/Formatting/Display/MessageTemplateTextFormatterTests.cs#L268-L278 302 | // 303 | // var expected = new StructureValue(Enumerable.Empty()).ToString(); 304 | // // expected == "{ }" 305 | // Assert.Equal(expected, sw.ToString()); 306 | // 307 | Assert.Equal("{}", sw.ToString()); 308 | } 309 | 310 | [Theory] 311 | [InlineData("", true)] 312 | [InlineData(":lj", true)] 313 | [InlineData(":jl", true)] 314 | [InlineData(":j", false)] 315 | [InlineData(":l", true)] 316 | public void FormatProviderWithScalarProperties(string format, bool shouldUseCustomFormatter) 317 | { 318 | var frenchFormatProvider = new CultureInfo("fr-FR"); 319 | var defaultFormatProvider = CultureInfo.InvariantCulture; 320 | 321 | var date = new DateTime(2018, 01, 01); 322 | var number = 12.345; 323 | 324 | var expectedFormattedDate = shouldUseCustomFormatter 325 | ? date.ToString(frenchFormatProvider) 326 | : date.ToString("O", defaultFormatProvider); 327 | var expectedFormattedNumber = shouldUseCustomFormatter 328 | ? number.ToString(frenchFormatProvider) 329 | : number.ToString(defaultFormatProvider); 330 | 331 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message" + format + "}", frenchFormatProvider); 332 | var evt = DelegatingSink.GetLogEvent(l => 333 | { 334 | l.Information("{MyDate}{MyNumber}", date, number); 335 | }); 336 | var sw = new StringWriter(); 337 | formatter.Format(evt, sw); 338 | 339 | Assert.Contains(expectedFormattedDate, sw.ToString()); 340 | Assert.Contains(expectedFormattedNumber, sw.ToString()); 341 | } 342 | 343 | [Theory] 344 | [InlineData("", true)] 345 | [InlineData(":lj", false)] 346 | [InlineData(":jl", false)] 347 | [InlineData(":j", false)] 348 | [InlineData(":l", true)] 349 | public void FormatProviderWithDestructuredProperties(string format, bool shouldUseCustomFormatter) 350 | { 351 | var frenchFormatProvider = new CultureInfo("fr-FR"); 352 | var defaultFormatProvider = CultureInfo.InvariantCulture; 353 | 354 | var date = new DateTime(2018, 01, 01); 355 | var number = 12.345; 356 | 357 | var expectedFormattedDate = shouldUseCustomFormatter 358 | ? date.ToString(frenchFormatProvider) 359 | : date.ToString("O", defaultFormatProvider); 360 | var expectedFormattedNumber = shouldUseCustomFormatter 361 | ? number.ToString(frenchFormatProvider) 362 | : number.ToString(defaultFormatProvider); 363 | 364 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Message" + format + "}", frenchFormatProvider); 365 | var evt = DelegatingSink.GetLogEvent(l => 366 | { 367 | l.Information("{@Item}", new 368 | { 369 | MyDate = date, 370 | MyNumber = number, 371 | }); 372 | }); 373 | var sw = new StringWriter(); 374 | formatter.Format(evt, sw); 375 | 376 | Assert.Contains(expectedFormattedDate, sw.ToString()); 377 | Assert.Contains(expectedFormattedNumber, sw.ToString()); 378 | } 379 | 380 | [Fact] 381 | public void TraceAndSpanAreEmptyWhenAbsent() 382 | { 383 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{TraceId}/{SpanId}", CultureInfo.InvariantCulture); 384 | var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello, world")); 385 | var sw = new StringWriter(); 386 | formatter.Format(evt, sw); 387 | Assert.Equal("/", sw.ToString()); 388 | } 389 | 390 | [Fact] 391 | public void TraceAndSpanAreIncludedWhenPresent() 392 | { 393 | var traceId = ActivityTraceId.CreateRandom(); 394 | var spanId = ActivitySpanId.CreateRandom(); 395 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{TraceId}/{SpanId}", CultureInfo.InvariantCulture); 396 | var evt = new LogEvent(DateTimeOffset.Now, LogEventLevel.Debug, null, 397 | new MessageTemplate(Enumerable.Empty()), Enumerable.Empty(), 398 | traceId, spanId); 399 | var sw = new StringWriter(); 400 | formatter.Format(evt, sw); 401 | Assert.Equal($"{traceId}/{spanId}", sw.ToString()); 402 | } 403 | 404 | [Theory] 405 | [InlineData("{Timestamp}", "09/03/2024 14:15:16 +02:00")] // Default Format 406 | [InlineData("{Timestamp:o}", "2024-09-03T14:15:16.0790000+02:00")] // Round-trip Standard Format String 407 | [InlineData("{Timestamp:yyyy-MM-dd HH:mm:ss}", "2024-09-03 14:15:16")] // Custom Format String 408 | public void TimestampTokenRendersLocalTime(string actualToken, string expectedOutput) 409 | { 410 | var logTimestampWithTimeZoneOffset = DateTimeOffset.Parse("2024-09-03T14:15:16.079+02:00", CultureInfo.InvariantCulture); 411 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, actualToken, CultureInfo.InvariantCulture); 412 | var evt = new LogEvent(logTimestampWithTimeZoneOffset, LogEventLevel.Debug, null, 413 | new MessageTemplate(Enumerable.Empty()), Enumerable.Empty()); 414 | var sw = new StringWriter(); 415 | formatter.Format(evt, sw); 416 | // expect time in local time, unchanged from the input, the +02:00 offset should not affect the output 417 | Assert.Equal(expectedOutput, sw.ToString()); 418 | } 419 | 420 | [Theory] 421 | [InlineData("{UtcTimestamp}", "09/03/2024 12:15:16")] // Default Format 422 | [InlineData("{UtcTimestamp:o}", "2024-09-03T12:15:16.0790000Z")] // Round-trip Standard Format String 423 | [InlineData("{UtcTimestamp:yyyy-MM-dd HH:mm:ss}", "2024-09-03 12:15:16")] // Custom Format String 424 | public void UtcTimestampTokenRendersUtcTime(string actualToken, string expectedOutput) 425 | { 426 | var logTimestampWithTimeZoneOffset = DateTimeOffset.Parse("2024-09-03T14:15:16.079+02:00", CultureInfo.InvariantCulture); 427 | var formatter = new OutputTemplateRenderer(ConsoleTheme.None, actualToken, CultureInfo.InvariantCulture); 428 | var evt = new LogEvent(logTimestampWithTimeZoneOffset, LogEventLevel.Debug, null, 429 | new MessageTemplate(Enumerable.Empty()), Enumerable.Empty()); 430 | var sw = new StringWriter(); 431 | formatter.Format(evt, sw); 432 | // expect time in UTC, the +02:00 offset must be applied to adjust the hour 433 | Assert.Equal(expectedOutput, sw.ToString()); 434 | } 435 | } --------------------------------------------------------------------------------