エラー時に子プロセスの標準エラー出力を取得する方法

あるプログラム A (a.exe) から、他のプログラム B (b.exe) を呼び出すとします。 このときに b.exe でエラーが発生したときに、標準エラー出力にエラーを出力しているとします。

この状況で b.exe で標準エラー出力に出力したエラーメッセージを a.exe で受け取るにはどうしたらよいでしょうか?

子プロセスの標準エラー出力を取得する方法

実はこれとほぼ同様のテーマで、別の記事「子プロセスの標準出力を取得する方法」 では、子プロセスから出力を受け取ることについて書きました。

その記事ではプロセスの親子関係について、少し詳しく書いています。

今回は少し視点を変えて、現実的な課題として「エラーが発生した時に子プロセスのエラー情報をたくさん収集したい」 という点について考えたいと思います。

子プロセス側の準備

エラーが発生した時には、まずはエラーが発生したこと自体を知りたいですよね。 そしてさらに、できればそのエラーの内容・原因も知りたいものです。

このためには、どのような準備をしておけば良いでしょうか。

まずエラーが発生したということを、親プロセスに知らせるためには、System.Environment.ExitCode が使えます。 正常終了時に 0 として、それ以外の場合には意味のある値を設定することによって、呼び出し側のプログラムがエラーを検出することができます。

次に、エラーの内容を渡すためには、もちろんログファイルに書き込んでおくというのも基本的なことではありますが、 そのほかに、通常の出力は標準出力に書きエラーは標準エラーに出力しておく、ということが必要です。

標準出力等については「「標準出力」「標準エラー出力」とは?」をみてください。

そうしないと、どのメッセージがエラーの内容でどの内容が通常の出力か区別することが難しくなります。

その他、下の例で行うようにコンソールに出力する際に表示色を変えて、エラーを目立たせるということもあるでしょう。これはユーザーが直接プログラムを使う時にはとてもわかりやすいので、良いと思います。

さて、それを踏まえ、今回エラーをレポートする側のプログラムを次のようにします。

using System;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            var color = Console.ForegroundColor;

            for (var i = 0; i < 10; i++)
            {

                Thread.Sleep(300);

                if (i % 2 == 0)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.WriteLine("{0} Hello, this is standard output.", i + 1);
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.Error.WriteLine("{0} THIS IS ERROR OUT", i + 1);
                }
            }

            Console.ForegroundColor = color;
            System.Environment.ExitCode = 1;

        }

    }
}

これを実行すると次のようになります。緑の文字が標準出力への書き込み、黄色が標準エラー出力への書き込みになります。

今はエラー処理の問題としていますので、System.Environment.ExitCode1 にセットして、 何かエラーが発生したということを知らせることにしましょう。

子プロセス終了後にまとめてエラー出力を取得する方法

上記のプログラムを起動して、そこから標準エラーの内容を受け取るプログラムは次のように書けます。

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("*** START CONSOLEAPP2.EXE ***");
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = @"C:\src\test\...\Debug\ConsoleApp1.exe";
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardError = true;

            using (var process = new Process())
            {
                process.StartInfo = startInfo;
                process.Start();

                process.WaitForExit();

                if (process.ExitCode == 0)
                {

                }
                else
                {
                    var msg = process.StandardError.ReadToEnd();
                    Print(msg, ConsoleColor.Yellow);
                }

            }

            Console.WriteLine("*** END CONSOLEAPP2.EXE ***");
            Thread.Sleep(1000);
        }

        public static void Print(string s, ConsoleColor color)
        {
            var initColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.Write(s);
            Console.ForegroundColor = initColor;
        }
    }
}

ExitCode0 以外の場合をエラーと判断して、 標準エラーの出力を読み取ります。

子プロセスから情報を取得したい場合の ProcessStartInfo の設定については 「子プロセスの標準出力を取得する方法」をみてください。

この WaitForExit() メソッドは標準出力・標準エラーのバッファが一杯になったときに、 ハングアップすることが知られています。現状 4kb が上限になります。そのため大量のエラー出力には不向きです。 また、万が一の時のため、WaitForExit() に待ち時間をミリ秒で渡しておくと良いでしょう。

子プロセスから随時エラー出力を取得する方法

子プロセスがエラー情報を出力するときに、随時それを呼び出すことも可能です。

プロセスオブジェクトのStandardError プロパティは StreamReader として実装されていますので、 Read メソッドでデータを読み込むことが可能です。

次の例では char[8] をバッファとして、バイトストリームを読み取ります。

using System;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {

            Console.WriteLine("*** START CONSOLEAPP2.EXE ***");
            ProcessStartInfo startInfo = new ProcessStartInfo();
            startInfo.FileName = @"C:\src\test\...\Debug\ConsoleApp1.exe";
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardError = true;

            using (var process = new Process())
            {
                process.StartInfo = startInfo;
                process.Start();

                char[] buff = new char[8];
                var c = 0;

                while ((c = process.StandardError.Read(buff, 0, 8)) > 0)
                {
                    string s = new string(buff, 0, c);
                    Print(s, ConsoleColor.Yellow);
                }

                process.WaitForExit();

            }

            Console.WriteLine("*** END CONSOLEAPP2.EXE ***");
            Thread.Sleep(1000);
        }

        public static void Print(string s, ConsoleColor color)
        {
            var initColor = Console.ForegroundColor;
            Console.ForegroundColor = color;
            Console.Write(s);
            Console.ForegroundColor = initColor;
        }
    }
}

実行結果は次のようになります。

バッファがいっぱいになるまで Read メソッドが返らないので、微妙に少しずつデータが読み込まれています。

スムーズに読み込むには、バッファのサイズをもっと小さく取れば良いです。しかしその場合、読み取るデータ量が増えた時にチビチビデータを読み取ることになり、 無駄に CPU を使う可能性がでてきますので、適度に調整する必要があります。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 C# 入門