├── 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 [](https://www.nuget.org/packages/Serilog.Sinks.Console/) [](https://github.com/serilog/serilog/wiki) [](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 | 
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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------