Skip to content

Opinionated logging interface and implementations for C# applications and libraries.

License

Notifications You must be signed in to change notification settings

chickensoft-games/Log

Repository files navigation

πŸͺ΅ Log

Chickensoft Badge Discord Read the docs line coverage branch coverage

Opinionated logging interface and implementations for C# applications and libraries.


Chickensoft.Log

πŸ“¦ Installation

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

🌱 Usage

Essentials

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 ILogWriters, each responsible for directing log messages to one output.

Setting up a Log

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());
}

Logging

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.

Formatting

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 LogFormatters:

LogFormatter.DefaultMessagePrefix = "INFO";
LogFormatter.DefaultWarningPrefix = "WARN";
LogFormatter.DefaultErrorPrefix = "ERROR";

Warning

Changing the default values of the level labels will affect newly-created LogFormatters, but will not affect ones that already exist.

βœ’οΈ Writer Types

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's Trace 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 individual FileWriters 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).

Using TraceWriter

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.

Using FileWriter

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 FileWriters:

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 FileWriters, but will not affect ones that already exist.

Using TestWriter

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.

πŸ’ Getting Help

Having issues? We'll be happy to help you in the Chickensoft Discord server.


🐣 Package generated from a 🐀 Chickensoft Template β€” https://chickensoft.games

About

Opinionated logging interface and implementations for C# applications and libraries.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published