C#でConsoleへの出力内容をTraceに転送する(その後ファイルに出力する

タイトルの通りなのですが、以下のようなConsole出力をそのままにして出力先をTraceに転送したい、(というか、Traceに転送した後にファイルに書き出したい)事があります。

例えばこんな感じ、、、

  • 最初はコンソールアプリで開発して今コールへログを出していた
  • 途中からDLLに変更した
  • リリース済みでConsoleをTraceに置き換えるのはいろいろ事情があって難しい

従って、既にConsoleに出力されている箇所はそのままにしつつ、内容をTraceに転送してそこからファイルに出力する方法を紹介したいと思います。

「何故Traceに一旦転送」するかというと、Traceへの出力先を変更する機能は既に資産として持っている事や、リンクするアプリケーションによってTraceが必要 or 不要、Traceの内容がファイルに欲しい等の事情が異なるためです。

もちろんConsoleへ出した内容を直接ファイルに出すこともできますが、実際に運用を始めると以外と取り回しが悪い(インスタンスが一つしか設定できない、出力先がTextWriterの継承なので拡張が面倒、TraceとかDebugは出力先を複数指定できる、TraceListnerの拡張は単純なので簡単)ためConsoleへ出力した内容をTraceへ転送する仕組みの紹介をしたいと思います。

確認環境

実行および確認環境は以下の通りです。

  • Windows10
  • VisualStudio2017
  • C# 7.2
  • .NET Framework 4.7.2

Console → Trace へデータを転送するクラス

Consoleの出力内容はConsole.Outに設定されているクラスはConsole.SetOut(TextWriter)メソッドで入れ替えることができるので、TextWriterを継承した自作クラスを作成します。

using System;
using System.Diagnostics;
using System.IO;
using System.Text;

// Consoleへの出力をTraceへ転送するためのクラス
public class TraceWriter : TextWriter
{
    // TextWriterからの要請で実装
    public override Encoding Encoding => Encoding.UTF8;
    public TraceWriter(IFormatProvider formatProvider) : base(formatProvider) { }

    // 既定の初期値でオブジェクトを初期化するコンストラクタ
    public TraceWriter() { }

    // Overrideしていないメンバーが呼ばれた時に呼び出される
    public override void Write(string value) => Trace.Write(value);

    // Console.WriteLine(string)が呼ばれた時に呼び出される
    public override void WriteLine(string value) => Trace.WriteLine(value);
}

実装はかなり単純化です内容をTrace.Write系のメソッドに転送するのみです。TextWriterのvirtualメソッド数がかなりあるため、お試しで2つのみoverrideしています。Console.Write(int)等の未overrideメソッドが呼ばれた時はTraceWriter.Write(string)にメッセージが流れてきます。

Trace → ファイルに出力するクラス

こっちは他のサイトでも色々説明があるので簡単に。受け取った内容を固定のファイルに転送します。

using System.Diagnostics;
using System.IO;

// Traceへの出力をファイルへ書き出すためのクラス
public class TraceToFileListner : TraceListener
{
    // 出力するファイルパス
    private string path;

    // 出力するファイルパスを指定してオブジェクトを初期化する
    public TraceToFileListner(string filePath) => this.path = filePath;

    // 超簡単(でかなり微妙)なファイルへの書き出し処理
    //  → 本当はもっとまともな実装のログ出力機構に内容を転送すること
    public override void Write(string message) => 
        File.AppendText(this.path).DisposeContext(message);

    // これも↑と同じ
    public override void WriteLine(string message) => 
        File.AppendText(this.path).DisposeContext(message);
}

// TextWriterの拡張メソッドの定義
public static class TextWriterExtension
{
    // Open済みのStreamに一回書き込んでStreamを閉じる操作(仮実装なのてすごく適当)
    public static void DisposeContext(this TextWriter writer, string message)
    {
        using (writer) { writer.WriteLine(message); }
    }
}

固定ファイルパスをリテラルで持つのは最低の実装ですがサンプルという事で勘弁してください。

使い方

上記2つのクラスを組み合わせてConsoleに出した内容をファイルに転送します。

using System;
using System.Diagnostics;

public static void Main(string[] args)
{
    // (1) Traceをファイルに書き出す設定をする
    Trace.Listeners.Add(new TraceToFileListner(@"d:\log.log"));
    // (2) Consoleの出力先を変更する
    Console.SetOut(new TraceWriter());

    // (3) いつも通りにConsoleに出力する
    Console.WriteLine(1);
    Console.WriteLine("asdf");

    // (4)
    // d:\log.logに以下の内容が出力される
    // > 1
    // > asdf
}

基本的な構造はこれで実現できます。

ご注意

上記コードですが、簡単な実装のため、このまま使用するとパフォーマンスや拡張性が低いです。実用の際は以下追加で検討しましょう。

  • TextWriterクラスのvirtualメソッドを適切にoverrideしているか?(必要なら全部overrideすること。
  • TraceToFileListnerクラスの出力先は性能が保証された出力先になっているか?

以上です。