【C#】シンプルなロガークラスを自作してみた

# 自己紹介
はじめまして！非実在系PG女子の水無月　美玖（みなつき　みく）です！
都心近郊で暮らしながら、お仕事でプログラムを書いたりしてます。
巷で`#バーチャル高専女子`というものが流行っている？らしいので、私みたいなジャンルのヒトがいてもいいのかなーということでアカウントの開設を行いました。
QiitaではC#やJava、git等の記事をメインで書いていこうかなーと考えてますので、なにとぞよろしくおねがいしますね。
Twitter: [@miku_minatsuki](https://twitter.com/miku_minatsuki)

# Logger
さて、みなさんはアプリケーションの『ログ出力』ってどうしてますか？
[Microsoft.Build.Framework.Logger](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.build.utilities.logger?view=netframework-4.8)を使う方もいれば、[NLog](https://nlog-project.org)や[log4net](https://logging.apache.org/log4net/download_log4net.cgi)を使う方もいらっしゃると思います。
私もしっかりしたアプリケーションを開発したりするときはNLogやlog4netを使うのですが、
ちょっとしたAPIのテストだったり、小さなアプリケーションを作る際にそこまでしっかりした造りのロガーじゃなくてもいいなー…って思うことが多々あります。
わざわざ設定ファイル作ったりするほどじゃないけど、一応日時の出力やログレベルくらいは欲しい…。

実際、ロガーがやっているお仕事って、メソッドが呼ばれたときにファイルに文字列を書き込んでるだけなんですよね。
たったそれくらいであれば、機能を絞ってシンプルなのを自作してみてもいいんじゃないかな？って思いまして。
しばらく前に作ったのですが、この度公開してみようと思います。

# コード
特に複雑なこともしていないので、ファイル１つで十分でした。

``` C#:Logger.cs
using System;
using System.IO;
using System.Text;

public class Logger
{
    private static readonly string LOG_FORMAT = "{0} {1} {2}";
    private static readonly string DATETIME_FORMAT = "yyyy/MM/dd HH:mm:ss.fff";
    private StreamWriter stream = null;
    private readonly bool consoleOut;

    private static Logger singletonInstance = null;
    public static Logger GetInstance(string logFilePath, bool consoleOut = false)
    {
        if (singletonInstance == null)
        {
            singletonInstance = new Logger(logFilePath, consoleOut);
        }
        return singletonInstance;
    }

    public static void Init(string logFilePath, bool consoleOut = false)
    {
        singletonInstance = new Logger(logFilePath, consoleOut);
    }

    private Logger(string logFilePath, bool consoleOut)
    {
        if (string.IsNullOrWhiteSpace(logFilePath))
        {
            throw new Exception("logFilePath is empty.");
        }

        var logFile = new FileInfo(logFilePath);
        if (!Directory.Exists(logFile.DirectoryName))
        {
            Directory.CreateDirectory(logFile.DirectoryName);
        }

        stream = new StreamWriter(logFile.FullName, true, Encoding.Default);
        stream.AutoFlush = true;
        this.consoleOut = consoleOut;
    }

    private void write(Level level, string text)
    {
        string log = string.Format(LOG_FORMAT, DateTime.Now.ToString(DATETIME_FORMAT), level.ToString(), text);
        stream.WriteLine(log);
        if (consoleOut)
        {
            Console.WriteLine(log);
        }
    }

    public void Error(string text)
    {
        write(Level.ERROR, text);
    }

    public void Error(Exception ex)
    {
        write(Level.ERROR, ex.Message + Environment.NewLine + ex.StackTrace);
    }

    public void Error(string format, object arg)
    {
        Error(string.Format(format, arg));
    }

    public void Error(string format, params object[] args)
    {
        Error(string.Format(format, args));
    }

    public void Warn(string text)
    {
        write(Level.WARN, text);
    }

    public void Warn(string format, object arg)
    {
        Warn(string.Format(format, arg));
    }

    public void Warn(string format, params object[] args)
    {
        Warn(string.Format(format, args));
    }

    public void Info(string text)
    {
        write(Level.INFO, text);
    }

    public void Info(string format, object arg)
    {
        Info(string.Format(format, arg));
    }

    public void Info(string format, params object[] args)
    {
        Info(string.Format(format, args));
    }

    public void Debug(string text)
    {
        write(Level.DEBUG, text);
    }

    public void Debug(string format, object arg)
    {
        Debug(string.Format(format, arg));
    }

    public void Debug(string format, params object[] args)
    {
        Debug(string.Format(format, args));
    }

    public void Trace(string text)
    {
        write(Level.TRACE, text);
    }

    public void Trace(string format, object arg)
    {
        Trace(string.Format(format, arg));
    }

    public void Trace(string format, params object[] args)
    {
        Trace(string.Format(format, args));
    }

    private enum Level
    {
        ERROR,
        WARN,
        INFO,
        DEBUG,
        TRACE
    }
}
```

# 使い方
使い方は至ってシンプルですが、2通りの使い方があります。
どちらも**シングルトンインスタンスを初期化し、それを取得してログ出力メソッドを呼び出す**という点は変わりません。

## Program.csでのみ使う場合
1. Logger.csをプロジェクトに導入する
2. Program.csでフィールドとして`private static Logger log = Logger.GetInstance(logFilePath, consoleOut);`を宣言。  
　`logFilePath`は絶対パスでも相対パスでもok。  
　`consoleOut`は記述しなければ`false`となり、コンソールへの出力はされない。必要に応じて`true`を渡す。
3. `log.Error();`なり`log.Info();`なりご自由に

## 複数のクラスから呼び出す場合（普通のプロジェクトで使う場合）
1. Logger.csをプロジェクトに導入する
2. `Main`メソッド内で`Logger.init(logFilePath, consoleOut)を呼び出す。`
　`logFilePath`は絶対パスでも相対パスでもok。  
　`consoleOut`は記述しなければ`false`となり、コンソールへの出力はされない。必要に応じて`true`を渡す。
3. 使いたいクラス内で、フィールドとして`private static Logger log = Logger.GetInstance();`を宣言。
4. `log.Error();`なり`log.Info();`なりご自由に

# 使用例
たとえば、こんなProgram.csがあったとします。

``` C#:Program.cs
using System;
using System.Diagnostics;
using System.Threading;

namespace Test
{
    public class Program
    {
        private static Logger log = Logger.GetInstance("calc.log", true);

        static void Main(string[] args)
        {
            var sw = new Stopwatch();
            log.Info("Start to calculation.");
            sw.Start();

            int result = 0;
            var rand = new Random();
            for (int i = 0; i < 10; i++)
            {
                int add = rand.Next(1, 1000000);
                result += add;
                log.Debug("add: {0}, result: {1}", add, result);
                Thread.Sleep(100);
            }

            sw.Stop();
            log.Info("Finished to process. result: {0}, time: {1}ms", result, sw.ElapsedMilliseconds);
        }
    }
}
```
このProgram.csを実行してできあがるのが、以下のcalc.logです。

``` calc.log
2019/07/07 23:25:44.942 INFO Start to calculation.
2019/07/07 23:25:44.945 DEBUG add: 849335, result: 849335
2019/07/07 23:25:45.046 DEBUG add: 817967, result: 1667302
2019/07/07 23:25:45.148 DEBUG add: 709783, result: 2377085
2019/07/07 23:25:45.249 DEBUG add: 945058, result: 3322143
2019/07/07 23:25:45.350 DEBUG add: 76153, result: 3398296
2019/07/07 23:25:45.451 DEBUG add: 519537, result: 3917833
2019/07/07 23:25:45.551 DEBUG add: 140003, result: 4057836
2019/07/07 23:25:45.652 DEBUG add: 555771, result: 4613607
2019/07/07 23:25:45.753 DEBUG add: 604428, result: 5218035
2019/07/07 23:25:45.853 DEBUG add: 687503, result: 5905538
2019/07/07 23:25:45.954 INFO Finished to process. result: 5905538, time: 1009ms
```

# おわりに
ロガークラスの自作って、けっこういろんな方がされていると思います。
私の書き方が絶対ってことはないですし、もっと便利に作ってる方はたくさんいらっしゃるハズです。
今回は私自身の勉強も兼ねて自作してみましたが、広く公開するようなアプリケーションを作る場合はNLogやlog4netのように、設定ファイルによって様々な設定ができるロガーを使うほうが良いでしょう。
でも私みたいに（設定ファイル作るの面倒だなー…）って思っても許される程度の規模での開発なら、今回作ったLoggerクラスくらいのものでも十分に役割を果たせるのではないかと！

何かありましたら、お気軽にコメントしてくださいね。
Twitterでのフォロー等もお待ちしております｜ω・)