diff --git a/appveyor.yml b/appveyor.yml
index 397acc7..c6dd7f8 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -9,7 +9,7 @@ artifacts:
deploy:
- provider: NuGet
api_key:
- secure: oemq1E4zMR+LKQyrR83ZLcugPpZtl5OMKjtpMy/mbPEwuFGS+Oe46427D9KoHYD8
+ secure: dX4ewxGxhiNURkqJPuZQ8GQNjLvb8oZrHBThVotn+9GSMyQzeKXFpHkN04ykOfgc
skip_symbols: true
on:
branch: /^(main|dev)$/
diff --git a/sample/ConsoleDemo/ConsoleDemo.csproj b/sample/ConsoleDemo/ConsoleDemo.csproj
index 81b1a1f..de9024a 100644
--- a/sample/ConsoleDemo/ConsoleDemo.csproj
+++ b/sample/ConsoleDemo/ConsoleDemo.csproj
@@ -2,7 +2,7 @@
Exe
- netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1;net452;net462;net472;net48;net5.0
+ net7.0
diff --git a/sample/SyncWritesDemo/SyncWritesDemo.csproj b/sample/SyncWritesDemo/SyncWritesDemo.csproj
index a72595e..de9024a 100644
--- a/sample/SyncWritesDemo/SyncWritesDemo.csproj
+++ b/sample/SyncWritesDemo/SyncWritesDemo.csproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ net7.0
diff --git a/src/Serilog.Sinks.Console/ConsoleAuditLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.Console/ConsoleAuditLoggerConfigurationExtensions.cs
new file mode 100644
index 0000000..6073c37
--- /dev/null
+++ b/src/Serilog.Sinks.Console/ConsoleAuditLoggerConfigurationExtensions.cs
@@ -0,0 +1,109 @@
+// Copyright 2017 Serilog Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Serilog.Configuration;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Sinks.SystemConsole;
+using Serilog.Sinks.SystemConsole.Output;
+using Serilog.Sinks.SystemConsole.Themes;
+using System;
+
+namespace Serilog
+{
+ ///
+ /// Adds the AuditTo.Console() extension method to .
+ ///
+ public static class ConsoleAuditLoggerConfigurationExtensions
+ {
+ ///
+ /// Writes log events to .
+ ///
+ /// Logger sink configuration.
+ /// The minimum level for
+ /// events passed through the sink. Ignored when is specified.
+ /// A message template describing the format used to write to the sink.
+ /// The default is "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
.
+ /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you
+ /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while
+ /// the lock is held.
+ /// Supplies culture-specific formatting information, or null.
+ /// A switch allowing the pass-through minimum level
+ /// to be changed at runtime.
+ /// Specifies the level at which events will be written to standard error.
+ /// The theme to apply to the styled output. If not specified,
+ /// uses .
+ /// Applies the selected or default theme even when output redirection is detected.
+ /// Configuration object allowing method chaining.
+ /// When is null
+ /// When is null
+ public static LoggerConfiguration Console(
+ this LoggerAuditSinkConfiguration sinkConfiguration,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ string outputTemplate = ConsoleLoggerConfigurationExtensions.DefaultConsoleOutputTemplate,
+ IFormatProvider? formatProvider = null,
+ LoggingLevelSwitch? levelSwitch = null,
+ LogEventLevel? standardErrorFromLevel = null,
+ ConsoleTheme? theme = null,
+ bool applyThemeToRedirectedOutput = false,
+ object? syncRoot = null)
+ {
+ if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration));
+ if (outputTemplate is null) throw new ArgumentNullException(nameof(outputTemplate));
+
+ var appliedTheme = !applyThemeToRedirectedOutput && (System.Console.IsOutputRedirected || System.Console.IsErrorRedirected) ?
+ ConsoleTheme.None :
+ theme ?? SystemConsoleThemes.Literate;
+
+ syncRoot ??= ConsoleLoggerConfigurationExtensions.DefaultSyncRoot;
+
+ var formatter = new OutputTemplateRenderer(appliedTheme, outputTemplate, formatProvider);
+ return sinkConfiguration.Sink(new ConsoleSink(appliedTheme, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch);
+ }
+
+ ///
+ /// Writes log events to .
+ ///
+ /// Logger sink configuration.
+ /// Controls the rendering of log events into text, for example to log JSON. To
+ /// control plain text formatting, use the overload that accepts an output template.
+ /// An object that will be used to `lock` (sync) access to the console output. If you specify this, you
+ /// will have the ability to lock on this object, and guarantee that the console sink will not be about to output anything while
+ /// the lock is held.
+ /// The minimum level for
+ /// events passed through the sink. Ignored when is specified.
+ /// A switch allowing the pass-through minimum level
+ /// to be changed at runtime.
+ /// Specifies the level at which events will be written to standard error.
+ /// Configuration object allowing method chaining.
+ /// When is null
+ /// When is null
+ public static LoggerConfiguration Console(
+ this LoggerAuditSinkConfiguration sinkConfiguration,
+ ITextFormatter formatter,
+ LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
+ LoggingLevelSwitch? levelSwitch = null,
+ LogEventLevel? standardErrorFromLevel = null,
+ object? syncRoot = null)
+ {
+ if (sinkConfiguration is null) throw new ArgumentNullException(nameof(sinkConfiguration));
+ if (formatter is null) throw new ArgumentNullException(nameof(formatter));
+
+ syncRoot ??= ConsoleLoggerConfigurationExtensions.DefaultSyncRoot;
+
+ return sinkConfiguration.Sink(new ConsoleSink(ConsoleTheme.None, formatter, standardErrorFromLevel, syncRoot), restrictedToMinimumLevel, levelSwitch);
+ }
+ }
+}
diff --git a/src/Serilog.Sinks.Console/ConsoleLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.Console/ConsoleLoggerConfigurationExtensions.cs
index e55f7a2..cca312a 100644
--- a/src/Serilog.Sinks.Console/ConsoleLoggerConfigurationExtensions.cs
+++ b/src/Serilog.Sinks.Console/ConsoleLoggerConfigurationExtensions.cs
@@ -28,8 +28,8 @@ namespace Serilog
///
public static class ConsoleLoggerConfigurationExtensions
{
- static readonly object DefaultSyncRoot = new object();
- const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
+ internal static readonly object DefaultSyncRoot = new object();
+ internal const string DefaultConsoleOutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}";
///
/// Writes log events to .
diff --git a/src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj b/src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj
index 06abb29..c1aeaf1 100644
--- a/src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj
+++ b/src/Serilog.Sinks.Console/Serilog.Sinks.Console.csproj
@@ -1,37 +1,50 @@
+
+ A Serilog sink that writes log events to the console/terminal.
+ 5.0.0
+ Serilog Contributors
+ net462;net471
+ $(TargetFrameworks);netstandard2.1;netstandard2.0;net5.0;net6.0;net7.0
+ enable
+ ../../assets/Serilog.snk
+ true
+ true
+ serilog;console;terminal
+ icon.png
+ https://github.com/serilog/serilog-sinks-console
+ Apache-2.0
+ https://github.com/serilog/serilog-sinks-console
+ git
+ true
+ True
+ Serilog
+ latest
+ README.md
+
-
- A Serilog sink that writes log events to the console/terminal.
- 4.1.0
- Serilog Contributors
- net45;netstandard1.3;netstandard2.0;net5.0
- 8.0
- enable
- ../../assets/Serilog.snk
- true
- true
- serilog;console;terminal
- icon.png
- https://github.com/serilog/serilog-sinks-console
- Apache-2.0
- https://github.com/serilog/serilog-sinks-console
- git
- true
- True
-
- Serilog
-
+
+ $(DefineConstants);RUNTIME_INFORMATION
+
-
- $(DefineConstants);RUNTIME_INFORMATION
-
-
-
-
-
-
+
+ $(DefineConstants);FEATURE_SPAN
+
+
+
+ $(DefineConstants);FEATURE_SPAN
+
+
+
+ $(DefineConstants);FEATURE_SPAN
+
+
+
+
+
+
-
-
-
+
+
+
+
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelOutputFormat.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelOutputFormat.cs
index d703bb8..a0c996f 100644
--- a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelOutputFormat.cs
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/LevelOutputFormat.cs
@@ -1,4 +1,4 @@
-// Copyright 2017 Serilog Contributors
+// Copyright 2023 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,89 +12,90 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using System;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Rendering;
-namespace Serilog.Sinks.SystemConsole.Output
-{
- ///
- /// Implements the {Level} element.
- /// can now have a fixed width applied to it, as well as casing rules.
- /// Width is set through formats like "u3" (uppercase three chars),
- /// "w1" (one lowercase char), or "t4" (title case four chars).
- ///
- static class LevelOutputFormat
- {
- static readonly string[][] TitleCaseLevelMap =
- {
- new[] { "V", "Vb", "Vrb", "Verb" },
- new[] { "D", "De", "Dbg", "Dbug" },
- new[] { "I", "In", "Inf", "Info" },
- new[] { "W", "Wn", "Wrn", "Warn" },
- new[] { "E", "Er", "Err", "Eror" },
- new[] { "F", "Fa", "Ftl", "Fatl" },
- };
+namespace Serilog.Sinks.SystemConsole.Output;
- static readonly string[][] LowercaseLevelMap =
- {
- new[] { "v", "vb", "vrb", "verb" },
- new[] { "d", "de", "dbg", "dbug" },
- new[] { "i", "in", "inf", "info" },
- new[] { "w", "wn", "wrn", "warn" },
- new[] { "e", "er", "err", "eror" },
- new[] { "f", "fa", "ftl", "fatl" },
- };
+///
+/// Implements the {Level} element.
+/// can now have a fixed width applied to it, as well as casing rules.
+/// Width is set through formats like "u3" (uppercase three chars),
+/// "w1" (one lowercase char), or "t4" (title case four chars).
+///
+static class LevelOutputFormat
+{
+ static readonly string[][] TitleCaseLevelMap = {
+ new []{ "V", "Vb", "Vrb", "Verb", "Verbo", "Verbos", "Verbose" },
+ new []{ "D", "De", "Dbg", "Dbug", "Debug" },
+ new []{ "I", "In", "Inf", "Info", "Infor", "Inform", "Informa", "Informat", "Informati", "Informatio", "Information" },
+ new []{ "W", "Wn", "Wrn", "Warn", "Warni", "Warnin", "Warning" },
+ new []{ "E", "Er", "Err", "Eror", "Error" },
+ new []{ "F", "Fa", "Ftl", "Fatl", "Fatal" }
+ };
- static readonly string[][] UppercaseLevelMap =
- {
- new[] { "V", "VB", "VRB", "VERB" },
- new[] { "D", "DE", "DBG", "DBUG" },
- new[] { "I", "IN", "INF", "INFO" },
- new[] { "W", "WN", "WRN", "WARN" },
- new[] { "E", "ER", "ERR", "EROR" },
- new[] { "F", "FA", "FTL", "FATL" },
- };
+ static readonly string[][] LowerCaseLevelMap = {
+ new []{ "v", "vb", "vrb", "verb", "verbo", "verbos", "verbose" },
+ new []{ "d", "de", "dbg", "dbug", "debug" },
+ new []{ "i", "in", "inf", "info", "infor", "inform", "informa", "informat", "informati", "informatio", "information" },
+ new []{ "w", "wn", "wrn", "warn", "warni", "warnin", "warning" },
+ new []{ "e", "er", "err", "eror", "error" },
+ new []{ "f", "fa", "ftl", "fatl", "fatal" }
+ };
- public static string GetLevelMoniker(LogEventLevel value, string? format = null)
- {
- if (format is null || format.Length != 2 && format.Length != 3)
- return Casing.Format(value.ToString(), format);
+ static readonly string[][] UpperCaseLevelMap = {
+ new []{ "V", "VB", "VRB", "VERB", "VERBO", "VERBOS", "VERBOSE" },
+ new []{ "D", "DE", "DBG", "DBUG", "DEBUG" },
+ new []{ "I", "IN", "INF", "INFO", "INFOR", "INFORM", "INFORMA", "INFORMAT", "INFORMATI", "INFORMATIO", "INFORMATION" },
+ new []{ "W", "WN", "WRN", "WARN", "WARNI", "WARNIN", "WARNING" },
+ new []{ "E", "ER", "ERR", "EROR", "ERROR" },
+ new []{ "F", "FA", "FTL", "FATL", "FATAL" }
+ };
- // Using int.Parse() here requires allocating a string to exclude the first character prefix.
- // Junk like "wxy" will be accepted but produce benign results.
- var width = format[1] - '0';
- if (format.Length == 3)
- {
- width *= 10;
- width += format[2] - '0';
- }
+ public static string GetLevelMoniker(LogEventLevel value, string? format = null)
+ {
+ var index = (int)value;
+ if (index is < 0 or > (int)LogEventLevel.Fatal)
+ return Casing.Format(value.ToString(), format);
- if (width < 1)
- return string.Empty;
+ if (format == null || format.Length != 2 && format.Length != 3)
+ return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);
- if (width > 4)
- {
- var stringValue = value.ToString();
- if (stringValue.Length > width)
- stringValue = stringValue.Substring(0, width);
- return Casing.Format(stringValue);
- }
+ // Using int.Parse() here requires allocating a string to exclude the first character prefix.
+ // Junk like "wxy" will be accepted but produce benign results.
+ var width = format[1] - '0';
+ if (format.Length == 3)
+ {
+ width *= 10;
+ width += format[2] - '0';
+ }
- var index = (int)value;
- if (index >= 0 && index <= (int)LogEventLevel.Fatal)
- {
- switch (format[0])
- {
- case 'w':
- return LowercaseLevelMap[index][width - 1];
- case 'u':
- return UppercaseLevelMap[index][width - 1];
- case 't':
- return TitleCaseLevelMap[index][width - 1];
- }
- }
+ if (width < 1)
+ return string.Empty;
- return Casing.Format(value.ToString(), format);
+ switch (format[0])
+ {
+ case 'w':
+ return GetLevelMoniker(LowerCaseLevelMap, index, width);
+ case 'u':
+ return GetLevelMoniker(UpperCaseLevelMap, index, width);
+ case 't':
+ return GetLevelMoniker(TitleCaseLevelMap, index, width);
+ default:
+ return Casing.Format(GetLevelMoniker(TitleCaseLevelMap, index), format);
}
}
-}
\ No newline at end of file
+
+ static string GetLevelMoniker(string[][] caseLevelMap, int index, int width)
+ {
+ var caseLevel = caseLevelMap[index];
+ return caseLevel[Math.Min(width, caseLevel.Length) - 1];
+ }
+
+ static string GetLevelMoniker(string[][] caseLevelMap, int index)
+ {
+ var caseLevel = caseLevelMap[index];
+ return caseLevel[caseLevel.Length - 1];
+ }
+}
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/NewLineTokenRenderer.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/NewLineTokenRenderer.cs
index c7174e8..4b072c3 100644
--- a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/NewLineTokenRenderer.cs
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/NewLineTokenRenderer.cs
@@ -37,4 +37,4 @@ public override void Render(LogEvent logEvent, TextWriter output)
output.WriteLine();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateRenderer.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateRenderer.cs
index c62ab67..f922290 100644
--- a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateRenderer.cs
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/OutputTemplateRenderer.cs
@@ -50,6 +50,14 @@ public OutputTemplateRenderer(ConsoleTheme theme, string outputTemplate, IFormat
{
renderers.Add(new NewLineTokenRenderer(pt.Alignment));
}
+ else if (pt.PropertyName == OutputProperties.TraceIdPropertyName)
+ {
+ renderers.Add(new TraceIdTokenRenderer(theme, pt));
+ }
+ else if (pt.PropertyName == OutputProperties.SpanIdPropertyName)
+ {
+ renderers.Add(new SpanIdTokenRenderer(theme, pt));
+ }
else if (pt.PropertyName == OutputProperties.ExceptionPropertyName)
{
renderers.Add(new ExceptionTokenRenderer(theme, pt));
@@ -62,7 +70,7 @@ public OutputTemplateRenderer(ConsoleTheme theme, string outputTemplate, IFormat
{
renderers.Add(new TimestampTokenRenderer(theme, pt, formatProvider));
}
- else if (pt.PropertyName == "Properties")
+ else if (pt.PropertyName == OutputProperties.PropertiesPropertyName)
{
renderers.Add(new PropertiesTokenRenderer(theme, pt, template, formatProvider));
}
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/SpanIdTokenRenderer.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/SpanIdTokenRenderer.cs
new file mode 100644
index 0000000..394a7da
--- /dev/null
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/SpanIdTokenRenderer.cs
@@ -0,0 +1,34 @@
+using System.IO;
+using Serilog.Events;
+using Serilog.Parsing;
+using Serilog.Sinks.SystemConsole.Rendering;
+using Serilog.Sinks.SystemConsole.Themes;
+
+namespace Serilog.Sinks.SystemConsole.Output;
+
+class SpanIdTokenRenderer : OutputTemplateTokenRenderer
+{
+ readonly ConsoleTheme _theme;
+ readonly Alignment? _alignment;
+
+ public SpanIdTokenRenderer(ConsoleTheme theme, PropertyToken spanIdToken)
+ {
+ _theme = theme;
+ _alignment = spanIdToken.Alignment;
+ }
+
+ public override void Render(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.SpanId is not { } spanId)
+ return;
+
+ var _ = 0;
+ using (_theme.Apply(output, ConsoleThemeStyle.Text, ref _))
+ {
+ if (_alignment is {} alignment)
+ Padding.Apply(output, spanId.ToString(), alignment);
+ else
+ output.Write(spanId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TimestampTokenRenderer.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TimestampTokenRenderer.cs
index c763279..bc5f000 100644
--- a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TimestampTokenRenderer.cs
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TimestampTokenRenderer.cs
@@ -13,6 +13,7 @@
// limitations under the License.
using System;
+using System.Globalization;
using System.IO;
using Serilog.Events;
using Serilog.Parsing;
@@ -36,9 +37,7 @@ public TimestampTokenRenderer(ConsoleTheme theme, PropertyToken token, IFormatPr
public override void Render(LogEvent logEvent, TextWriter output)
{
- // We need access to ScalarValue.Render() to avoid this alloc; just ensures
- // that custom format providers are supported properly.
- var sv = new ScalarValue(logEvent.Timestamp);
+ var sv = new DateTimeOffsetValue(logEvent.Timestamp);
var _ = 0;
using (_theme.Apply(output, ConsoleThemeStyle.SecondaryText, ref _))
@@ -56,5 +55,35 @@ public override void Render(LogEvent logEvent, TextWriter output)
}
}
}
+
+ readonly struct DateTimeOffsetValue
+ {
+ public DateTimeOffsetValue(DateTimeOffset value)
+ {
+ Value = value;
+ }
+
+ public DateTimeOffset Value { get; }
+
+ public void Render(TextWriter output, string? format = null, IFormatProvider? formatProvider = null)
+ {
+ var custom = (ICustomFormatter?)formatProvider?.GetFormat(typeof(ICustomFormatter));
+ if (custom != null)
+ {
+ output.Write(custom.Format(format, Value, formatProvider));
+ return;
+ }
+
+#if FEATURE_SPAN
+ Span buffer = stackalloc char[32];
+ if (Value.TryFormat(buffer, out int written, format, formatProvider ?? CultureInfo.InvariantCulture))
+ output.Write(buffer.Slice(0, written));
+ else
+ output.Write(Value.ToString(format, formatProvider ?? CultureInfo.InvariantCulture));
+#else
+ output.Write(Value.ToString(format, formatProvider ?? CultureInfo.InvariantCulture));
+#endif
+ }
+ }
}
}
diff --git a/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TraceIdTokenRenderer.cs b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TraceIdTokenRenderer.cs
new file mode 100644
index 0000000..c2be4d0
--- /dev/null
+++ b/src/Serilog.Sinks.Console/Sinks/SystemConsole/Output/TraceIdTokenRenderer.cs
@@ -0,0 +1,34 @@
+using System.IO;
+using Serilog.Events;
+using Serilog.Parsing;
+using Serilog.Sinks.SystemConsole.Rendering;
+using Serilog.Sinks.SystemConsole.Themes;
+
+namespace Serilog.Sinks.SystemConsole.Output;
+
+class TraceIdTokenRenderer : OutputTemplateTokenRenderer
+{
+ readonly ConsoleTheme _theme;
+ readonly Alignment? _alignment;
+
+ public TraceIdTokenRenderer(ConsoleTheme theme, PropertyToken traceIdToken)
+ {
+ _theme = theme;
+ _alignment = traceIdToken.Alignment;
+ }
+
+ public override void Render(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent.TraceId is not { } traceId)
+ return;
+
+ var _ = 0;
+ using (_theme.Apply(output, ConsoleThemeStyle.Text, ref _))
+ {
+ if (_alignment is {} alignment)
+ Padding.Apply(output, traceId.ToString(), alignment);
+ else
+ output.Write(traceId);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Console.Tests/Approval/ApiApprovalTests.cs b/test/Serilog.Sinks.Console.Tests/Approval/ApiApprovalTests.cs
new file mode 100644
index 0000000..46b1c72
--- /dev/null
+++ b/test/Serilog.Sinks.Console.Tests/Approval/ApiApprovalTests.cs
@@ -0,0 +1,27 @@
+#if NET5_0
+
+using PublicApiGenerator;
+using Shouldly;
+using Xunit;
+
+namespace Serilog.Sinks.Console.Tests.Approval
+{
+ public class ApiApprovalTests
+ {
+ [Fact]
+ public void PublicApi_Should_Not_Change_Unintentionally()
+ {
+ var assembly = typeof(ConsoleLoggerConfigurationExtensions).Assembly;
+ var publicApi = assembly.GeneratePublicApi(
+ new ApiGeneratorOptions()
+ {
+ IncludeAssemblyAttributes = false,
+ ExcludeAttributes = new[] { "System.Diagnostics.DebuggerDisplayAttribute" },
+ });
+
+ publicApi.ShouldMatchApproved(options => options.WithFilenameGenerator((_, __, fileType, fileExtension) => $"{assembly.GetName().Name!}.{fileType}.{fileExtension}"));
+ }
+ }
+}
+
+#endif
diff --git a/test/Serilog.Sinks.Console.Tests/Approval/Serilog.Sinks.Console.approved.txt b/test/Serilog.Sinks.Console.Tests/Approval/Serilog.Sinks.Console.approved.txt
new file mode 100644
index 0000000..a6239fb
--- /dev/null
+++ b/test/Serilog.Sinks.Console.Tests/Approval/Serilog.Sinks.Console.approved.txt
@@ -0,0 +1,75 @@
+namespace Serilog
+{
+ public static class ConsoleAuditLoggerConfigurationExtensions
+ {
+ 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) { }
+ 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) { }
+ }
+ public static class ConsoleLoggerConfigurationExtensions
+ {
+ 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) { }
+ 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) { }
+ }
+}
+namespace Serilog.Sinks.SystemConsole.Themes
+{
+ public class AnsiConsoleTheme : Serilog.Sinks.SystemConsole.Themes.ConsoleTheme
+ {
+ public AnsiConsoleTheme(System.Collections.Generic.IReadOnlyDictionary styles) { }
+ public override bool CanBuffer { get; }
+ protected override int ResetCharCount { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Code { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Grayscale { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Literate { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme Sixteen { get; }
+ public override void Reset(System.IO.TextWriter output) { }
+ public override int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style) { }
+ }
+ public abstract class ConsoleTheme
+ {
+ protected ConsoleTheme() { }
+ public abstract bool CanBuffer { get; }
+ protected abstract int ResetCharCount { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.ConsoleTheme None { get; }
+ public abstract void Reset(System.IO.TextWriter output);
+ public abstract int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style);
+ }
+ public enum ConsoleThemeStyle
+ {
+ Text = 0,
+ SecondaryText = 1,
+ TertiaryText = 2,
+ Invalid = 3,
+ Null = 4,
+ Name = 5,
+ String = 6,
+ Number = 7,
+ Boolean = 8,
+ Scalar = 9,
+ [System.Obsolete("Use ConsoleThemeStyle.Scalar instead")]
+ Object = 9,
+ LevelVerbose = 10,
+ LevelDebug = 11,
+ LevelInformation = 12,
+ LevelWarning = 13,
+ LevelError = 14,
+ LevelFatal = 15,
+ }
+ public class SystemConsoleTheme : Serilog.Sinks.SystemConsole.Themes.ConsoleTheme
+ {
+ public SystemConsoleTheme(System.Collections.Generic.IReadOnlyDictionary styles) { }
+ public override bool CanBuffer { get; }
+ protected override int ResetCharCount { get; }
+ public System.Collections.Generic.IReadOnlyDictionary Styles { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Colored { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Grayscale { get; }
+ public static Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme Literate { get; }
+ public override void Reset(System.IO.TextWriter output) { }
+ public override int Set(System.IO.TextWriter output, Serilog.Sinks.SystemConsole.Themes.ConsoleThemeStyle style) { }
+ }
+ public struct SystemConsoleThemeStyle
+ {
+ public System.ConsoleColor? Background;
+ public System.ConsoleColor? Foreground;
+ }
+}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleAuditLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleAuditLoggerConfigurationExtensionsTests.cs
new file mode 100644
index 0000000..1398128
--- /dev/null
+++ b/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleAuditLoggerConfigurationExtensionsTests.cs
@@ -0,0 +1,64 @@
+using System;
+using System.IO;
+using System.Linq;
+using Xunit;
+using Serilog.Sinks.SystemConsole.Themes;
+
+namespace Serilog.Sinks.Console.Tests.Configuration
+{
+ [Collection("ConsoleSequentialTests")]
+ public class ConsoleAuditLoggerConfigurationExtensionsTests
+ {
+ [Fact]
+ public void OutputFormattingIsIgnored()
+ {
+ using (var stream = new MemoryStream())
+ {
+ var sw = new StreamWriter(stream);
+
+ System.Console.SetOut(sw);
+ var config = new LoggerConfiguration()
+ .AuditTo.Console(theme: AnsiConsoleTheme.Literate,
+ applyThemeToRedirectedOutput: false);
+
+ var logger = config.CreateLogger();
+
+ logger.Error("test");
+ stream.Position = 0;
+
+ using (var streamReader = new StreamReader(stream))
+ {
+ var result = streamReader.ReadToEnd();
+ var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c));
+ Assert.Equal(0, controlCharacterCount);
+ }
+ }
+ }
+
+ [Fact]
+ public void OutputFormattingIsPresent()
+ {
+ using (var stream = new MemoryStream())
+ {
+ var sw = new StreamWriter(stream);
+
+ System.Console.SetOut(sw);
+ var config = new LoggerConfiguration()
+ .AuditTo.Console(theme: AnsiConsoleTheme.Literate,
+ applyThemeToRedirectedOutput: true);
+
+ var logger = config.CreateLogger();
+
+ logger.Error("test");
+ stream.Position = 0;
+
+ using (var streamReader = new StreamReader(stream))
+ {
+ var result = streamReader.ReadToEnd();
+ var controlCharacterCount = result.Count(c => Char.IsControl(c) && !Char.IsWhiteSpace(c));
+ Assert.NotEqual(0, controlCharacterCount);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleLoggerConfigurationExtensionsTests.cs
index 74596c5..a66c82f 100644
--- a/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleLoggerConfigurationExtensionsTests.cs
+++ b/test/Serilog.Sinks.Console.Tests/Configuration/ConsoleLoggerConfigurationExtensionsTests.cs
@@ -6,6 +6,7 @@
namespace Serilog.Sinks.Console.Tests.Configuration
{
+ [Collection("ConsoleSequentialTests")]
public class ConsoleLoggerConfigurationExtensionsTests
{
[Fact]
diff --git a/test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs b/test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs
index 1a71415..59bbedc 100644
--- a/test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs
+++ b/test/Serilog.Sinks.Console.Tests/Output/OutputTemplateRendererTests.cs
@@ -1,8 +1,10 @@
using System;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using Serilog.Events;
+using Serilog.Parsing;
using Serilog.Sinks.Console.Tests.Support;
using Serilog.Sinks.SystemConsole.Output;
using Serilog.Sinks.SystemConsole.Themes;
@@ -105,11 +107,23 @@ public void FixedLengthLevelIsSupported(
int width,
string expected)
{
- var formatter = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
- var evt = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
- var sw = new StringWriter();
- formatter.Format(evt, sw);
- Assert.Equal(expected, sw.ToString());
+ var formatter1 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:t{width}}}", CultureInfo.InvariantCulture);
+ var evt1 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
+ var sw1 = new StringWriter();
+ formatter1.Format(evt1, sw1);
+ Assert.Equal(expected, sw1.ToString());
+
+ var formatter2 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:u{width}}}", CultureInfo.InvariantCulture);
+ var evt2 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
+ var sw2 = new StringWriter();
+ formatter2.Format(evt2, sw2);
+ Assert.Equal(expected.ToUpper(), sw2.ToString());
+
+ var formatter3 = new OutputTemplateRenderer(ConsoleTheme.None, $"{{Level:w{width}}}", CultureInfo.InvariantCulture);
+ var evt3 = DelegatingSink.GetLogEvent(l => l.Write(level, "Hello"));
+ var sw3 = new StringWriter();
+ formatter3.Format(evt3, sw3);
+ Assert.Equal(expected.ToLower(), sw3.ToString());
}
[Fact]
@@ -131,6 +145,16 @@ public void FixedLengthLevelSupportsLowerCasing()
formatter.Format(evt, sw);
Assert.Equal("inf", sw.ToString());
}
+
+ [Fact]
+ public void FixedLengthLevelSupportsCasingForWideNames()
+ {
+ var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{Level:w6}", CultureInfo.InvariantCulture);
+ var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello"));
+ var sw = new StringWriter();
+ formatter.Format(evt, sw);
+ Assert.Equal("inform", sw.ToString());
+ }
[Fact]
public void DefaultLevelLengthIsFullText()
@@ -166,7 +190,7 @@ public SizeFormatter(IFormatProvider innerFormatProvider)
_innerFormatProvider = innerFormatProvider;
}
- public object? GetFormat(Type? formatType)
+ public object GetFormat(Type? formatType)
{
return formatType == typeof(ICustomFormatter) ? this : _innerFormatProvider.GetFormat(formatType) ?? this;
}
@@ -352,5 +376,29 @@ public void FormatProviderWithDestructuredProperties(string format, bool shouldU
Assert.Contains(expectedFormattedDate, sw.ToString());
Assert.Contains(expectedFormattedNumber, sw.ToString());
}
+
+ [Fact]
+ public void TraceAndSpanAreEmptyWhenAbsent()
+ {
+ var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{TraceId}/{SpanId}", CultureInfo.InvariantCulture);
+ var evt = DelegatingSink.GetLogEvent(l => l.Information("Hello, world"));
+ var sw = new StringWriter();
+ formatter.Format(evt, sw);
+ Assert.Equal("/", sw.ToString());
+ }
+
+ [Fact]
+ public void TraceAndSpanAreIncludedWhenPresent()
+ {
+ var traceId = ActivityTraceId.CreateRandom();
+ var spanId = ActivitySpanId.CreateRandom();
+ var formatter = new OutputTemplateRenderer(ConsoleTheme.None, "{TraceId}/{SpanId}", CultureInfo.InvariantCulture);
+ var evt = new LogEvent(DateTimeOffset.Now, LogEventLevel.Debug, null,
+ new MessageTemplate(Enumerable.Empty()), Enumerable.Empty(),
+ traceId, spanId);
+ var sw = new StringWriter();
+ formatter.Format(evt, sw);
+ Assert.Equal($"{traceId}/{spanId}", sw.ToString());
+ }
}
}
diff --git a/test/Serilog.Sinks.Console.Tests/Serilog.Sinks.Console.Tests.csproj b/test/Serilog.Sinks.Console.Tests/Serilog.Sinks.Console.Tests.csproj
index 5e141e3..472965f 100644
--- a/test/Serilog.Sinks.Console.Tests/Serilog.Sinks.Console.Tests.csproj
+++ b/test/Serilog.Sinks.Console.Tests/Serilog.Sinks.Console.Tests.csproj
@@ -1,12 +1,11 @@
-
+
- netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1;net452;net462;net472;net48;net5.0
+ net7.0
true
../../assets/Serilog.snk
true
true
- 8.0
enable
@@ -15,10 +14,12 @@
-
+
+
+