Compare commits

...

5 commits

Author SHA1 Message Date
Ell 5ca1073125 1.4.1 2023-12-01 13:19:14 +01:00
Ell 8e5bd41a47 allow using ANSI escape sequences for the console sink 2023-12-01 13:17:46 +01:00
Ell 034497201e 1.4.0 2023-12-01 10:54:36 +01:00
Ell cb824c73fc added WriterSink 2023-12-01 10:54:00 +01:00
Ell 096bc98a05 more explicitly implement LogWriter 2023-12-01 10:49:04 +01:00
6 changed files with 326 additions and 10 deletions

View file

@ -5,6 +5,7 @@ using System.IO;
namespace ExtremelySimpleLogger {
/// <summary>
/// A <see cref="Sink"/> that writes log output to <see cref="Console.Out"/> or <see cref="Console.Error"/>.
/// This class is a variation of the <see cref="WriterSink"/>.
/// </summary>
public class ConsoleSink : Sink {
@ -17,15 +18,17 @@ namespace ExtremelySimpleLogger {
{LogLevel.Error, ConsoleColor.DarkRed},
{LogLevel.Fatal, ConsoleColor.DarkRed}
};
private readonly object locker = new object();
private readonly TextWriter console;
private readonly bool useAnsiCodes;
/// <summary>
/// Creates a new console sink with the given settings.
/// </summary>
/// <param name="error">Whether to log to <see cref="Console.Error"/> instead of <see cref="Console.Out"/>.</param>
public ConsoleSink(bool error = false) {
/// <param name="useAnsiCodes">Whether to wrap log output text in ANSI escape codes using <see cref="Extensions.WrapAnsiCode"/> instead of using the <see cref="Console.ForegroundColor"/> and <see cref="Console.ResetColor"/>. This may work better on some terminals.</param>
public ConsoleSink(bool error = false, bool useAnsiCodes = false) {
this.console = error ? Console.Error : Console.Out;
this.useAnsiCodes = useAnsiCodes;
}
/// <summary>
@ -63,12 +66,17 @@ namespace ExtremelySimpleLogger {
/// <param name="level">The importance level of this message</param>
/// <param name="s">The message to log</param>
protected override void Log(Logger logger, LogLevel level, string s) {
lock (this.locker) {
lock (this.console) {
var color = this.GetColor(level);
if (color.HasValue)
Console.ForegroundColor = color.Value;
if (color.HasValue) {
if (this.useAnsiCodes) {
s = s.WrapAnsiCode(color.Value);
} else {
Console.ForegroundColor = color.Value;
}
}
this.console.WriteLine(s);
if (color.HasValue)
if (color.HasValue && !this.useAnsiCodes)
Console.ResetColor();
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Linq;
namespace ExtremelySimpleLogger {
/// <summary>
/// A set of extension methods for logging-related activities, like converting <see cref="ConsoleColor"/> to ANSI color codes.
/// </summary>
public static class Extensions {
private static readonly int[] AnsiCodes = new[] {
ConsoleColor.Black, ConsoleColor.DarkRed, ConsoleColor.DarkGreen, ConsoleColor.DarkYellow,
ConsoleColor.DarkBlue, ConsoleColor.DarkMagenta, ConsoleColor.DarkCyan, ConsoleColor.Gray,
ConsoleColor.DarkGray, ConsoleColor.Red, ConsoleColor.Green, ConsoleColor.Yellow,
ConsoleColor.Blue, ConsoleColor.Magenta, ConsoleColor.Cyan, ConsoleColor.White
}.Select((s, i) => (int) s).ToArray();
/// <summary>
/// Converts the given <see cref="ConsoleColor"/> to its ANSI escape sequence representation. If the supplied <paramref name="color"/> is <see langword="null"/>, the reset escape sequence for the given color type will be returned.
/// </summary>
/// <param name="color">The color. If <see langword="null"/>, the reset escape sequence for the given color type will be returned.</param>
/// <param name="background">Whether to return a background color. If this is <see langword="false"/>, a foreground color is returned instead.</param>
/// <returns>The ANSI escape sequence representation of the given <paramref name="color"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">If the <paramref name="color"/> is not in defined range.</exception>
public static string ToAnsiCode(this ConsoleColor? color, bool background = false) {
if (color.HasValue) {
if (color < 0 || (int) color >= Extensions.AnsiCodes.Length)
throw new ArgumentOutOfRangeException(nameof(color), color, null);
return $"\x1B[{(background ? 48 : 38)};5;{Extensions.AnsiCodes[(int) color]}m";
}
return $"\x1B[{(background ? 49 : 39)}m";
}
/// <summary>
/// Wraps the given string in the ANSI escape sequence representation of the given <paramref name="color"/> and the appropriate reset escape sequence using <see cref="ToAnsiCode"/>.
/// </summary>
/// <param name="s">The string to wrap.</param>
/// <param name="color">The color.</param>
/// <param name="background">Whether to use <paramref name="color"/> as a background color. If this is <see langword="false"/>, a foreground color is used instead.</param>
/// <returns>The given string, wrapped in ANSI color codes.</returns>
public static string WrapAnsiCode(this string s, ConsoleColor color, bool background = false) {
return Extensions.ToAnsiCode(color, background) + s + Extensions.ToAnsiCode(null, background);
}
}
}

View file

@ -14,7 +14,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>Logo.png</PackageIcon>
<VersionPrefix>1.3.3</VersionPrefix>
<VersionPrefix>1.4.1</VersionPrefix>
</PropertyGroup>
<ItemGroup>

View file

@ -1,11 +1,11 @@
using System;
using System.IO;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace ExtremelySimpleLogger {
/// <summary>
/// Implementation of a <see cref="TextWriter"/> that writes to a <see cref="Logger"/>.
/// The log writer constructs a message based on calls to <see cref="Write(string)"/> and its variations and then submits the message to the underlying <see cref="Logger"/> when <see cref="WriteLine()"/> or <see cref="Flush"/> is called. <see cref="WriteLine(string)"/> and its variations submit the message immediately.
/// </summary>
public class LogWriter : TextWriter {
@ -59,17 +59,237 @@ namespace ExtremelySimpleLogger {
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(char[] buffer) {
lock (this.logger)
this.line.Append(buffer);
}
/// <inheritdoc />
public override void Write(bool value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(int value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(uint value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(long value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(ulong value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(float value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(double value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(decimal value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(object value) {
lock (this.logger)
this.line.Append(value);
}
/// <inheritdoc />
public override void Write(string format, object arg0) {
lock (this.logger)
this.line.AppendFormat(this.FormatProvider, format, arg0);
}
/// <inheritdoc />
public override void Write(string format, object arg0, object arg1) {
lock (this.logger)
this.line.AppendFormat(this.FormatProvider, format, arg0, arg1);
}
/// <inheritdoc />
public override void Write(string format, object arg0, object arg1, object arg2) {
lock (this.logger)
this.line.AppendFormat(this.FormatProvider, format, arg0, arg1, arg2);
}
/// <inheritdoc />
public override void Write(string format, params object[] arg) {
lock (this.logger)
this.line.AppendFormat(this.FormatProvider, format, arg);
}
/// <inheritdoc />
public override void WriteLine(string value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(char value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(char[] buffer) {
this.Write(buffer);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(char[] buffer, int index, int count) {
this.Write(buffer, index, count);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(bool value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(int value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(uint value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(long value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(ulong value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(float value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(double value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(decimal value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(object value) {
this.Write(value);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(string format, object arg0) {
this.Write(format, arg0);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(string format, object arg0, object arg1) {
this.Write(format, arg0, arg1);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(string format, object arg0, object arg1, object arg2) {
this.Write(format, arg0, arg1, arg2);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine(string format, params object[] arg) {
this.Write(format, arg);
this.WriteLine();
}
/// <inheritdoc />
public override void WriteLine() {
this.Flush();
}
/// <inheritdoc />
public override Task WriteAsync(char value) {
return Task.Run(() => this.Write(value));
}
/// <inheritdoc />
public override Task WriteAsync(string value) {
return Task.Run(() => this.Write(value));
}
/// <inheritdoc />
public override Task WriteAsync(char[] buffer, int index, int count) {
return Task.Run(() => this.Write(buffer, index, count));
}
/// <inheritdoc />
public override Task WriteLineAsync(char value) {
return Task.Run(() => this.WriteLine(value));
}
/// <inheritdoc />
public override Task WriteLineAsync(string value) {
return Task.Run(() => this.WriteLine(value));
}
/// <inheritdoc />
public override Task WriteLineAsync(char[] buffer, int index, int count) {
return Task.Run(() => this.WriteLine(buffer, index, count));
}
/// <inheritdoc />
public override Task WriteLineAsync() {
return Task.Run(this.WriteLine);
}
/// <inheritdoc />
public override Task FlushAsync() {
return Task.Run(this.Flush);
}
/// <inheritdoc />
public override void Flush() {
lock (this.logger) {

View file

@ -0,0 +1,43 @@
using System.IO;
namespace ExtremelySimpleLogger {
/// <summary>
/// A <see cref="Sink"/> that writes log output to an underlying <see cref="TextWriter"/>.
/// Note that <see cref="ConsoleSink"/> is a variation of this sink that additionally includes console colors.
/// </summary>
public class WriterSink : Sink {
private readonly TextWriter writer;
private readonly bool autoClose;
/// <summary>
/// Creates a new writer sink with the given settings.
/// </summary>
/// <param name="writer">The writer to write to.</param>
/// <param name="autoClose">Whether the underlying <paramref name="writer"/> should be closed automatically when this sink is disposed in <see cref="Dispose"/>.</param>
public WriterSink(TextWriter writer, bool autoClose = false) {
this.writer = writer;
this.autoClose = autoClose;
}
/// <summary>
/// Logs the given message, which has already been formatted using <see cref="Sink.Formatter"/> or <see cref="Logger.DefaultFormatter"/>.
/// </summary>
/// <param name="logger">The logger that the message was passed to</param>
/// <param name="level">The importance level of this message</param>
/// <param name="s">The message to log</param>
protected override void Log(Logger logger, LogLevel level, string s) {
lock (this.writer)
this.writer.WriteLine(s);
}
/// <inheritdoc />
public override void Dispose() {
if (this.autoClose) {
lock (this.writer)
this.writer.Dispose();
}
}
}
}

View file

@ -12,7 +12,7 @@ namespace Sample {
var sinks = new List<Sink> {
new FileSink("Log.txt", true),
// We only want to log messages with a higher importance in the console
new ConsoleSink {MinimumLevel = LogLevel.Info},
new ConsoleSink() {MinimumLevel = LogLevel.Info},
// we allow a total of 5 files in our directory sink before old ones start being deleted
new DirectorySink("AllLogs", 5)
};