Opinionated logging interface and implementations for C# applications and libraries.
Tip
For logging in Godot, see Chickensoft.Log.Godot.
Install the latest version of the Chickensoft.Log package from nuget:
dotnet add package Chickensoft.Log
In Chickensoft.Log, messages are logged through the ILog
interface. Each ILog
has a name (often the name of the class using the ILog
), an ILogFormatter
for
formatting messages, and a collection of ILogWriter
s, each responsible for
directing log messages to one output.
The package includes a standard implementation of ILog
, named Log
. To create
a log, instantiate a Log
and give it a name and at least one writer:
public class MyClass
{
// Create a log with the name of MyClass, outputting to stdout/stderr
private ILog _log = new Log(nameof(MyClass), new ConsoleWriter());
}
You can give the log multiple writers to obtain multiple outputs:
public class MyClass
{
// Create a log with the name of MyClass, outputting to stdout/stderr and
// .NET's Trace system
private ILog _log = new Log(nameof(MyClass), new ConsoleWriter(),
new TraceWriter());
}
To log messages, use ILog
's methods Print()
, Warn()
, and Err()
.
Example:
public class MyClass
{
public void MyMethod()
{
// Outputs "Info (MyClass): A log message"
_log.Print("A log message");
// Outputs "Warn (MyClass): A warning message"
_log.Warn("A warning message");
// Outputs "Error (MyClass): An error occurred"
_log.Err("An error occurred");
try
{
SomethingThatThrows();
}
catch (Exception e)
{
// Outputs the value of e.ToString(), prefixed by a line labeling it an exception,
// as an error
_log.Print(e);
}
// Outputs the current stack trace as a standard log message
_log.Print(new System.Diagnostics.StackTrace());
}
}
Tip
Some writers may have separate channels for warnings and errors, while others
may not. For instance, the TraceWriter
has separate channels for regular log
messages, warnings, and errors. The FileWriter
has only one channel, the
file it's writing to. Warnings and errors can still be distinguished by the
label the formatter gives them, even if directed to the same channel as regular
log messages.
Optionally, when constructing a log, you can provide an ILogFormatter
that the
log will use to format each message. (The formatted message should include
the log's name, the level of the message, and the message itself.)
public class MyClass
{
private ILog _log = new Log(nameof(MyClass), new ConsoleWriter())
{
Formatter = new MyFormatter()
};
}
By default, Log
will use the included LogFormatter
class implementing
ILogFormatter
.
Messages are formatted with one of three level labels, depending which log method
you call. By default, the included LogFormatter
uses the labels "Info"
,
"Warn"
, and "Error"
. You can change these labels for an individual
LogFormatter
:
var formatter = new LogFormatter()
{
MessagePrefix = "INFO";
WarningPrefix = "WARN";
ErrorPrefix = "ERROR";
};
You can also change the default values of these labels for all LogFormatter
s:
LogFormatter.DefaultMessagePrefix = "INFO";
LogFormatter.DefaultWarningPrefix = "WARN";
LogFormatter.DefaultErrorPrefix = "ERROR";
Warning
Changing the default values of the level labels will affect newly-created
LogFormatter
s, but will not affect ones that already exist.
Log
accepts a parameter array of writers, which receive formatted messages
from the log and are responsible for handling the output of the messages. The
Log package provides three writer types for use in applications or libraries:
ConsoleWriter
: Outputs log messages to stdout and stderr.TraceWriter
: Outputs log messages to .NET'sTrace
system. This is useful for seeing log output in Visual Studio's "Output" tab while debugging.FileWriter
: Outputs log messages to file. By default,FileWriter
will write to a file called "output.log" in the working directory, but you can either configure a different default, or configure individualFileWriter
s to write to particular files on creation. To avoid concurrency issues,FileWriter
is implemented as a pseudo-singleton with a single instance per file name; see below for details.
The package provides one additional writer type, TestWriter
, which may be
useful for testing your code without mocking ILog
(see below).
To see TraceWriter
's output from a Godot application in Visual Studio's
Output pane, add a DefaultTraceListener
to the system's list of Trace
listeners somewhere near your entry point:
using System.Diagnostics;
public class MyGodotApp : Node
{
public override void _Ready()
{
Trace.Listeners.Add(new DefaultTraceListener());
}
}
This step is necessary with GoDotTest test suites as well as games (or any other Godot-based applications).
Tip
This step is unnecessary if you are running or debugging in Visual Studio
Code, so you may want to guard adding DefaultTraceListener
with a
command-line flag.
FileWriter
provides two static Instance()
methods for obtaining references
to writers.
You can obtain a reference to a writer using the default file name "output.log"
:
public class MyClass
{
private ILog _log = new Log(nameof(MyClass), FileWriter.Instance());
}
You can obtain a writer that outputs messages to a custom file name:
public class MyClass
{
private ILog _log = new Log(nameof(MyClass),
FileWriter.Instance("CustomFileName.log"));
}
And you can change the default file name for FileWriter
s:
public class Entry
{
public static void Main()
{
// Change the default file name for FileWriter before any writers are created
FileWriter.DefaultFileName = "MyFileName.log";
// ...
}
}
public class MyClass
{
// Create a FileWriter that writes to the new default name
private ILog _log = new Log(nameof(MyClass), FileWriter.Instance());
}
Warning
Changing the default value for the log file name will affect newly-created
FileWriter
s, but will not affect ones that already exist.
When testing code that uses an ILog
, it may be cumbersome to mock ILog
's
methods. In that case, you may prefer to use the provided TestWriter
type,
which accumulates log messages for testing:
// Class under test
public class MyClass
{
public ILog Log { get; set; } = new Log(nameof(MyClass), new ConsoleWriter());
// Method that logs some information; we want to test the logged messages
public void MyMethod()
{
Log.Print("A normal log message");
Log.Err("An error message");
}
}
public class MyClassTest
{
[Fact]
public void MyMethodLogs()
{
// set up an instance of MyClass, but with a TestWriter instead of a ConsoleWriter
var testWriter = new TestWriter();
var obj = new MyClass() { Log = new Log(nameof(MyClass), testWriter) };
obj.MyMethod();
// use TestWriter to test the logging behavior of MyClass
testWriter.LoggedMessages
.ShouldBeEquivalentTo(new List<string>
{
"Info (MyClass): A normal log message",
"Error (MyClass): An error message"
});
}
}
Warning
TestWriter
is not thread-safe.
Having issues? We'll be happy to help you in the Chickensoft Discord server.
🐣 Package generated from a 🐤 Chickensoft Template — https://chickensoft.games