例えばログファイルにログを書き出すようなプログラムを作った場合に、ログファイルがどんどん大きくなって知らないうちに何十ギガバイトもの大きさになっていたりするとトラブルの元だ。
そういう場合には、ログファイルを管理する各種の便利なライブラリがあるのでそう言うのを使っても良いだろう。
log4netと言うのが有名らしい。ワテも使ってみようかなと思ったのだが何となく大掛かりな感じだし、取り合えずちょちょっとテキストファイルに実行中のログが書ければ良いので自作してみた。
ログファイルが大きくなり過ぎないように指定した大きさ(バイト数)でファイルの末尾を取り出す関数だ。
まあ例えて言うとUnix, Linuxのtailコマンドみたいなもんかな。
では、本題に入ろう。
C#版 tailコマンドモドキ
即席で作ってみた。
毎度お馴染みだが、ワテ流のヘンテコな思い付きの変数ネーミングなので気になる人はスマートに修正して下さい。
public class TestClass
{
    public static void tail(string pathFNameLog, long tailBytesKeep)
    {
        var pathFNameLog_tmp = pathFNameLog + ".tmp";
        File.Delete(pathFNameLog_tmp);  // 有れば消す。
 
        using (var reader = new StreamReader(pathFNameLog))
        {
            if (reader.BaseStream.Length > tailBytesKeep)
            {
                reader.BaseStream.Seek( -tailBytesKeep, SeekOrigin.End);
            }
            string line;
            using (StreamWriter wTmp = File.AppendText(pathFNameLog_tmp))
            {
                while ((line = reader.ReadLine()) != null)
                {
                    //Console.WriteLine(line);
                    wTmp.WriteLine(line);
                }
            }
        }
 
        File.Delete(pathFNameLog);
        File.Move(pathFNameLog_tmp, pathFNameLog);
    }
}
コード1. C#版テキストファイルを末尾から指定したバイト数に切り詰める関数
使い方
pathFNameLogは、絶対パスでログファイル名を指定。ファイルは存在しているという前提なので、もし無い場合はエラーが出るかな?未確認だ。
tailBytesKeepで残すバイト数を指定。この例では末尾の100KBを取り出す。
const int KB = 1024; const int MB = 1024 * 1024; const string pathFNameLog = @"D:\LogFolder\log.txt"; TestClass.tail(pathFNameLog: pathFNameLog, tailBytesKeep: 100 * KB);
コード2. 指定したファイルの末尾から100KBを残してそれ以外を切り捨てる処理の例
コードの説明
処理対象ファイル名に .tmp を付けた仮ファイルに一旦書き出すのでそのファイル名を作成し、古いファイルが有れば削除しておく。
var pathFNameLog_tmp = pathFNameLog + ".tmp"; File.Delete(pathFNameLog_tmp); // 有れば消す。
コード3. 仮出力用のファイル名の生成と古いファイルが有れば削除する
あとは、指定したバイト数だけ読み飛ばす処理を入れて、その後で、
StreamReaderで処理対象ファイルをReadLine()で一行ずつ読み込む。
書き出すのは StreamWriterだ。
   using (var reader = new StreamReader(pathFNameLog))
   {
       if (reader.BaseStream.Length > tailBytesKeep)
       {
           reader.BaseStream.Seek(-tailBytesKeep, SeekOrigin.End);
       }
       string line;
       using (StreamWriter wTmp = File.AppendText(pathFNameLog_tmp))
       {
           while ((line = reader.ReadLine()) != null)
           {
               //Console.WriteLine(line);
               wTmp.WriteLine(line);
           }
       }
   }
コード4. 不要部分を読み飛ばし、必要部分を末尾から指定バイト数だけ取り出す処理
SeekOrigin.Endを指定すると末尾が基準となり、-tailBytesKeepでマイナス値を指定するのが味噌だ。これで読み込み位置(シーク位置)が末尾から 100KB 位置にセット出来る。
その状態でwhileループを回してその100KBを読み取ってWriteLine(line)で仮ファイルに書き出す。
なおReadLine()で読み込むと改行コードが除去される。なので出力時には逆にWriteLine(line)を使って改行コードを補っている。
最後に、仮ファイル名(.tmp)を処理対象ファイル名 pathFNameLog に置き換えて完了。
File.Delete(pathFNameLog); File.Move(pathFNameLog_tmp, pathFNameLog);
コード5. 処理対象ファイルを削除して、仮ファイルの名前を変える。
この関数を使って、即席で自前のログファイル出力機能を作ってみた。
まあ、簡単に実装出来たので早速使っているが使い勝手はいい感じ。
デバッグではログファイルに記録を残しておくと予想外のバグを発見できるなどのメリットもあるのでログファイルはお勧めだ。
まとめ
C#の場合、StreamReader, StreamWriterはとっても便利だ。
usingを使うと勝手にファイルのオープンやクローズをやってくれるので、C/C++で fopen() 関数で苦労したのが懐かしい。
C#では、MemoryStream, XmlReader, XmlWriterなどもあるので使い方を覚えると益々役に立つ。
C#関連本を読む
↑有名な川俣さんだ。
↑あんた誰や?
NLogが便利
なお、現在のワテはプログラム開発でログファイル出力にはNLogを使っている。
オープンソースで大勢の人が使っているし、高性能なログ出力機能を持つのでワテも使ってみたのだ。
そしたらとっても便利。
その辺りの詳細は以下の関連記事を参照下さい。














コメント