26
25

More than 3 years have passed since last update.

[コピペでOK] C#でログを出力する

Last updated at Posted at 2020-04-08

はじめに

C#でログを出力するLoggerを作成しました。
C#では、便利なライブラリがありますが、
ここでは、コピペですぐに使えるものを目指しています。

機能としては下記の通りです。

  • ログレベル
  • ログローテート
  • 出力先指定

下記にC#でログ出力を行うライブラリを紹介しているリンクを記載します。

実行環境

今回実行した環境は下記の通りです。

実行環境
Microsoft .NET Framework 3.5

C#の古いバージョンでも動作するように実装しています。

使い方

使い方は下記の通りです。

Logger log = Logger.GetInstance();
log.Error("error log.");
log.Warn("warn log.");
log.Info("info log.");
log.Debug("debug log.");

出力されるログは下記の通りです。

[2020-04-08 22:38:56.081][1][DEBUG] debug log.
[2020-04-08 22:38:56.082][1][ERROR] error log.
[2020-04-08 22:38:56.082][1][WARN] warn log.
[2020-04-08 22:38:56.082][1][INFO] info log.

「1」この数字は、プロセスIDを表示しています。

Loggerの実装

configでLoggerに関する設定を行います。

app.config
<setting name="IS_LOGFILE" serializeAs="String">
    <value>True</value>
</setting>
<setting name="LOG_LEVEL" serializeAs="String">
    <value>3</value>
</setting>
<setting name="LOGDIR_PATH" serializeAs="String">
    <value>./logs/</value>
</setting>
<setting name="LOGFILE_NAME" serializeAs="String">
    <value>console</value>
</setting>
<setting name="LOGFILE_MAXSIZE" serializeAs="String">
    <value>10485760</value>
</setting>
<setting name="LOGFILE_PERIOD" serializeAs="String">
    <value>30</value>
</setting>
  • 「IS_LOGFILE」はログファイル出力フラグです。
  • 「LOG_LEVEL」はログレベルです。
  • 「LOGDIR_PATH」はログファイル出力ディレクトリです。
  • 「LOGFILE_NAME」はログファイル名です。
  • 「LOGFILE_MAXSIZE」はログファイル最大サイズ(Byte)です。
  • 「LOGFILE_PERIOD」はログ保存期間(日)です。

Loggerクラスの実装は下記の通りです。

Logger.cs
class Logger
{
    /// <summary>
    /// ログレベル
    /// </summary>
    private enum LogLevel
    {
        ERROR,
        WARN,
        INFO,
        DEBUG
    }

    private static Logger singleton = null;
    private readonly string logFilePath = null;
    private readonly object lockObj = new object();
    private StreamWriter stream = null;

    /// <summary>
    /// インスタンスを生成する
    /// </summary>
    public static Logger GetInstance()
    {
        if (singleton == null)
        {
            singleton = new Logger();
        }
        return singleton;
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    private Logger()
    {
        this.logFilePath = Settings.Default.LOGDIR_PATH + Settings.Default.LOGFILE_NAME + ".log";

        // ログファイルを生成する
        CreateLogfile(new FileInfo(logFilePath));
    }

    /// <summary>
    /// ERRORレベルのログを出力する
    /// </summary>
    /// <param name="msg">メッセージ</param>
    public void Error(string msg)
    {
        if ((int)LogLevel.ERROR <= Settings.Default.LOG_LEVEL)
        {
            Out(LogLevel.ERROR, msg);
        }
    }

    /// <summary>
    /// ERRORレベルのスタックトレースログを出力する
    /// </summary>
    /// <param name="ex">例外オブジェクト</param>
    public void Error(Exception ex)
    {
        if ((int)LogLevel.ERROR <= Settings.Default.LOG_LEVEL)
        {
            Out(LogLevel.ERROR, ex.Message + Environment.NewLine + ex.StackTrace);
        }
    }

    /// <summary>
    /// WARNレベルのログを出力する
    /// </summary>
    /// <param name="msg">メッセージ</param>
    public void Warn(string msg)
    {
        if ((int)LogLevel.WARN <= Settings.Default.LOG_LEVEL)
        {
            Out(LogLevel.WARN, msg);
        }
    }

    /// <summary>
    /// INFOレベルのログを出力する
    /// </summary>
    /// <param name="msg">メッセージ</param>
    public void Info(string msg)
    {
        if ((int)LogLevel.INFO <= Settings.Default.LOG_LEVEL)
        {
            Out(LogLevel.INFO, msg);
        }
    }

    /// <summary>
    /// DEBUGレベルのログを出力する
    /// </summary>
    /// <param name="msg">メッセージ</param>
    public void Debug(string msg)
    {
        if ((int)LogLevel.DEBUG <= Properties.Settings.Default.LOG_LEVEL)
        {
            Out(LogLevel.DEBUG, msg);
        }
    }

    /// <summary>
    /// ログを出力する
    /// </summary>
    /// <param name="level">ログレベル</param>
    /// <param name="msg">メッセージ</param>
    private void Out(LogLevel level, string msg)
    {
        if (Settings.Default.IS_LOGFILE)
        {
            int tid = System.Threading.Thread.CurrentThread.ManagedThreadId;
            string fullMsg = string.Format("[{0}][{1}][{2}] {3}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), tid, level.ToString(), msg);

            lock (this.lockObj)
            {
                this.stream.WriteLine(fullMsg);

                FileInfo logFile = new FileInfo(this.logFilePath);
                if (Settings.Default.LOGFILE_MAXSIZE < logFile.Length)
                {
                    // ログファイルを圧縮する
                    CompressLogFile();

                    // 古いログファイルを削除する
                    DeleteOldLogFile();
                }
            }
        }
    }

    /// <summary>
    /// ログファイルを生成する
    /// </summary>
    /// <param name="logFile">ファイル情報</param>
    private void CreateLogfile(FileInfo logFile)
    {
        if (!Directory.Exists(logFile.DirectoryName))
        {
            Directory.CreateDirectory(logFile.DirectoryName);
        }

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

    /// <summary>
    /// ログファイルを圧縮する
    /// </summary>
    private void CompressLogFile()
    {
        this.stream.Close();
        string oldFilePath = Settings.Default.LOGDIR_PATH + Settings.Default.LOGFILE_NAME + "_" + DateTime.Now.ToString("yyyyMMddHHmmss");
        File.Move(this.logFilePath, oldFilePath + ".log");

        FileStream inStream = new FileStream(oldFilePath + ".log", FileMode.Open, FileAccess.Read);
        FileStream outStream = new FileStream(oldFilePath + ".gz", FileMode.Create, FileAccess.Write);
        GZipStream gzStream = new GZipStream(outStream, CompressionMode.Compress);

        int size = 0;
        byte[] buffer = new byte[Settings.Default.LOGFILE_MAXSIZE + 1000];
        while (0 < (size = inStream.Read(buffer, 0, buffer.Length)))
        {
            gzStream.Write(buffer, 0, size);
        }

        inStream.Close();
        gzStream.Close();
        outStream.Close();

        File.Delete(oldFilePath + ".log");
        CreateLogfile(new FileInfo(this.logFilePath));
    }

    /// <summary>
    /// 古いログファイルを削除する
    /// </summary>
    private void DeleteOldLogFile()
    {
        Regex regex = new Regex(Settings.Default.LOGFILE_NAME + @"_(\d{14}).*\.gz");
        DateTime retentionDate = DateTime.Today.AddDays(-Settings.Default.LOGFILE_PERIOD);
        string[] filePathList = Directory.GetFiles(Settings.Default.LOGDIR_PATH, Settings.Default.LOGFILE_NAME + "_*.gz", SearchOption.TopDirectoryOnly);
        foreach (string filePath in filePathList)
        {
            Match match = regex.Match(filePath);
            if (match.Success)
            {
                DateTime logCreatedDate = DateTime.ParseExact(match.Groups[1].Value.ToString(), "yyyyMMddHHmmss", null);
                if (logCreatedDate < retentionDate)
                {
                    File.Delete(filePath);
                }
            }
        }
    }
}

スレッドセーフにするために、lockを使用しています。

さいごに

ソースコードをGitHubに公開しています。

ソースファイルはこちら

以上です。

26
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
25