0
\$\begingroup\$

I've been using logging libraries recently and somebody told me to try and write my own. I've kept it minimal but the only drawback is I can't log in static classes, due to TClass.


Follow-up


I'm looking for constructive criticism on how to improve it.

How do I declare a logger variable?

private static readonly Logger<CatalogController> Logger = new Logger<CatalogController>();

I guess another drawback is that its pretty long, including the class name (twice) and then all the access modifiers and class names of the logger, it an get pretty ugly.

public class Logger <TClass>
{
    private readonly Type _className;

    public Logger()
    {
        _className = typeof(TClass);
    }

    public void Trace(string message, bool log = false)
    {
        Log(message, ConsoleColor.White, log);
    }

    public void Warn(string message, bool log = false)
    {
        Log(message, ConsoleColor.Yellow, log);
    }

    public void Debug(string message, bool log = false)
    {
        Log(message, ConsoleColor.Cyan, log);
    }

    public void Success(string message, bool log = false)
    {
        Log(message, ConsoleColor.Green, log);
    }

    public void Error(string message, bool log = false)
    {
        Log(message, ConsoleColor.Red, log);
    }

    public void Error(Exception e)
    {
        Log("An error occurred: " + Environment.NewLine + e, ConsoleColor.Red);
    }

    private void Log(string message, ConsoleColor color, bool log = false)
    {
        var oldColor = Console.ForegroundColor;
        Console.ForegroundColor = color;
        Console.WriteLine($"[{DateTime.Now:MM/dd HH:mm:ss}] " + message);
        Console.ForegroundColor = oldColor;

        string logFile;

        switch (color)
        {
            case ConsoleColor.Yellow:
                logFile = "error.log";
                break;
            case ConsoleColor.Cyan:
                logFile = "debug.log";
                break;
            case ConsoleColor.Red:
                logFile = "error.log";
                break;
            default:
                logFile = "trace.log";
                break;
        }

        LogToFile(logFile, $"Occurred at [{DateTime.Now:MM/dd HH:mm:ss}] in [{_className.FullName}]: " + message);
    }

    private static void LogToFile(string file, string content)
    {
        var executionPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        var fileWriter = new StreamWriter(executionPath + "/resources/logging/" + file, true);

        fileWriter.WriteLine(content);
        fileWriter.Close();
    }
}
\$\endgroup\$
  • \$\begingroup\$ I would advise you check a few of the existing ones to get some ideas. If you're going to reinvent the wheel, at least look at the existing ones for some tips. \$\endgroup\$ – Nkosi May 27 '18 at 23:43
  • \$\begingroup\$ You've already posted a follow-up - in this case, it'd be nice if you could accept the answer that helped you most. \$\endgroup\$ – t3chb0t May 28 '18 at 6:52
3
\$\begingroup\$

I have two notes to make here,

  1. Extract a public interface from this code, like ILogger<T>, and store (or inject) an ILogger<T> instead of a Logger<T>

This will allow you to create different implementations of your logger type and pass it to the consumers of your logger without them knowing/caring whether it's logging to a console, to a file, or to a typewriter.

An important use case for that is creating an ILogger implementation for unit tests. You probably don't want your unit tests to have any side effects, so you could create a TestLogger class that implements ILogger and records all the logs to a list instead of a to a file or console.

  1. Handle write access in a safer way

Currently you are not disposing of your StreamWriter instance. Even though you are calling Close you really should be either using a using statement or having a single instance of the stream writer that is accessed through an actor or other thread safe method and is disposed when your logger is disposed.

(Less important, but I would also store the execution path in a static readonly

private static readonly string executionPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location + "/resources/logging/" + file;

private static void LogToFile(string file, string content)
{
    using (var fileWriter = new StreamWriter(executionPath, true))
    {
        fileWriter.WriteLine(content);
        fileWriter.Close();
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ Note that it may not be desirable to reopen a stream for every log message that is written. It depends on the log, an exception log that is going to rarely be written to shouldn't be kept open; but a trace log that receives a massive load (e.g. during a processing job) will benefit from reusing the same stream. You may want to make this behavior configurable so you can use either. \$\endgroup\$ – Flater May 29 '18 at 8:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.