Xamarin.Formsでログをファイル出力する(iOS編)

Xamarin の場合、Visual Studio を通してデバッグ実行する場合は、System.Diagnostics.Debug か System.Diagnostics.Trace を使います。そのまま Visual Studio のデバッグ出力ウィンドウに表示されるので、手軽にプログラムの動作が確認できます。もともと、System.Diagnostics.Debug などは、.NET の Windows プログラミングで使われていたものなので、.NET であれば全般的に使えます。
つまり、ASP.NET でも、Blazor でも Azure Functions でも同じ様に使えるわけです。

同じ様に使えるということは、知識の使い廻しができる点で、

System.Diagnostics.Debug.WriteLine( message );

の使い方が、どの .NET 環境で利用できるということです。
余談ですが、Console のほうも同じで、標準出力にデータを書き出すという点は何処でも同じです。

System.Console.WriteLine( message );

なので、このような書き方をしても、どの環境であっても「標準出力」があれば出力がされます。Xamarin の場合には標準出力がないのでどこにも出力されません。ただし、別途標準出力を作ってやれば、目的の標準出力に出力されるでしょう。

デバッグ出力をファイルに書き出す

お手軽なデバッグ出力ではありますが、常に Visual Studio から起動しないといけないのはいささか面倒です。特に、スマホのアプリの場合は、スマホ単体でアプリを起動することが多く、テスト作業をするにしても Visual Studio から常に立ち上げるのは難しいでしょう。ブレークポイントを置いて何らかのチェックをしたい場合はもあるでしょうが、一連の動きをデバッグ出力としてファイルに保存しておくのがよいでしょう。

デバッグ先の出力ファイルを独自に作ってもよいのですが、ここでは System.Diagnostics.Trace のリスナーの機能を使ってみましょう。

ちなみに NuGet からライブラリを追加してよいのであれば、NLog を使う方法もあります。

NLog を使って Xamarin.Forms からログ出力する方法 – Qiita

実は、Trace には Listeners コレクションがあって出力先を追加できます。普段は Visual Studio のデバッグ出力にしか出ないのですが、これにファイルストリームを追加すると、トレース結果をファイルに出力できます。

Trace.Listeners Property (System.Diagnostics) | Microsoft Docs

var tw = System.IO.File.OpenWrite(filename);
var tr1 = new TextWriterTraceListener(tw);
System.Diagnostics.Trace.AutoFlush = true;
System.Diagnostics.Trace.Listeners.Add(tr1);

出力したいファイル名を OpenWrite 関数で開いて、TextWriterTraceListener オブジェクトを作ります。これを Listeners コレクションに Add するだけです。
AutoFlush を true にしておくのは、トレース出力(WriteLineなど)のたびにファイルに書き込むことを示しています。いちいち Flush するとスピードは遅くなるのですが、不意のアプリのクラッシュのときに、ログファイルが途中までしか書き込まれないときがあるので、AutoFlush させておいたほうが無難です。

ファイル名をどうするのかという問題がありますが、これも .NET で一般的に使われる Environment.SpecialFolder.MyDocuments あたりを使えば大丈夫です。Xamarin.Essentials でも良いのですが、所詮ファイルの作成先が作れればいいので、これでも大丈夫です。

var dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(dir, $"log-{DateTime.Now.ToString("yyyyMMdd-HHmm")}.txt");

ファイル名は、起動したときの時分までで作成しています。こうすると、ファイル名がユニークになります。秒まであってもいいのですが、おそらくアプリのテスト起動は1分以内に行わないだろうから、これでいいでしょう。日付毎にまとめたい場合は、別途 OpenText などを使って工夫します。

もうひとつ、Info.plist で LSSupportsOpeningDocumentsInPlace を YES にしておきます。このようにすると、アプリのフォルダが「ファイル」に表示されるようになります。

<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

こうすると、Xamarin.Forms の共通プロジェクトのほうでログ出力ができるようになります。
iPhone では次のように、ファイルを開いてログの状態が確認できます。

iOS 側のデバッグ出力をファイルに書き出す

これでテスト用のログファイル出力は十分、と思ったのですが、もうひとつ難関がありました。
Xamarin.iOS 側のプロジェクトで System.Diagnostics.Trace を使ってもデバッグ出力ができません。この理由は判らないのですが、Xamarin.iOS のほうのプロジェクトで、Trace.WriteLine としても、Xamarin.Forms 側の共通プロジェクトで出したログに合わせて出力されることはありません。多分、System.Diagnostics.Trace の実体がひとつしかないので、Xamarin.iOS 側から触れないようになっているのかもしれません。

仕方がないので、iOS側の Trace は自作します。最小限の機能で十分なので、こんな感じで WriteLine だけ作っておきます。

public class IosTrace
{
    static IosTrace()
    {
        Listeners = new List<TraceListener>();
    }
    public static List<TraceListener> Listeners { get; }
    public static bool AutoFlush { get; set; } = true;
    public static void WriteLine(string message)
    {
        foreach ( var it in Listeners)
        {
            it.WriteLine(message);
            if ( AutoFlush == true ) it.Flush();
        }
    }
}

もとの System.Diagnostics.Trace と同じ様に Listeners コレクションに TextWriterTraceListener オブジェクトを追加すれば ok です。ファイル名は、Xamarin.Forms の共通プロジェクトで作ったものとは別にしておきます。

var dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(dir, $"ios-{DateTime.Now.ToString("yyyyMMdd-HHmm")}.txt");
var tw = System.IO.File.OpenWrite(filename);
var tr1 = new TextWriterTraceListener(tw);
IosTrace.AutoFlush = true;
IosTrace.Listeners.Add(tr1);

このようにしておくと、Xamarin.iOS 側で落ちたときのスタックトレースや微妙なコールバックの状態が判るようになります。簡易的なものなので、ファイル名や行番号などは付けていませんが、色々つけるような場合は NLog を使ったほうがいいかもしれません。

さて、iOS のほうがこれでいいのですが、Android の場合はどうなるでしょうか。というは話は、続きのブログ記事に書いておきます。

カテゴリー: 開発 パーマリンク