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

この記事は、[Xamarin Advent Calendar 2016(その2)](http://qiita.com/advent-calendar/2016/xamarin2) の23日目の記事です。

---

# 0. はじめに
Xamarin に限らずアプリのデバッグを効率的に行うためにはログ出力ライブラリの利用は必須だと思います。
[System.Diagnostics.Debug](https://msdn.microsoft.com/ja-jp/library/system.diagnostics.debug(v=vs.110).aspx) クラスも便利ですが、複雑なアプリケーションのデバッグになると、厳しい場合があります。
本記事では、[NLog](http://nlog-project.org/) を使って Xamarin.Forms のアプリケーションからログを出力する方法について記載します。

NLog は以下のような機能をもつ非常に便利なライブラリです。

- 構成ファイルで出力レベルの切り替えを行うことができる
- ログ出力の日付時刻、プロセス ID などのログ解析時に必要な典型的な項目を自動的に出力することができる
- ログファイルのフォーマットに CSV, JSON 形式など指定することができる
- ログファイルのローリングができる

NLog は、Xamarin.Android, Xamarin.iOS をサポートしていますが、Xamarin.Forms はサポートしていません。
しかしながら、[DependencyService](https://developer.xamarin.com/guides/xamarin-forms/dependency-service/introduction/) や [Unity](https://unity.codeplex.com/) (DI コンテナ）等の機能を利用することで、Xamarin.Forms でも NLog の便利な機能を利用することが可能になります。

# 1. ログ出力までの手順

## 1.1 プロジェクトの作成

サンプルコードを GitHub に置いています。

- [Xamarin-Forms-NLog-Sample](https://github.com/tafuji/Xamarin-Forms-NLog-Sample)

サンプルは Main.xaml にボタンを二つ配置して、ボタンを押すとファイルにログ出力する簡単なサンプルです。

![Main](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/63c9a9bfcbd10857c02baf533ac9b891f35e33c5/Main.png)

ソリューションの初期構造は以下の流れで作っています。

1. Visual Stuido 2015 で[Blank App (Xamarin.Forms Portable)]テンプレートを選択し、ソリューション名を「NLogSample」指定してプロジェクトを作成
2. NLogSample.UWP、NLogSample.Windows、NLogSample.WinPhone プロジェクトは削除
3. ログ出力処理を実装する共有プロジェクト(NLogSample.Shared) を追加
4. NLogSample プロジェクトに Main.xaml を追加

![Initial Solution](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/9bfb3f8869b1fe5e248d091897c30d00023756e0/Initial-Solution.png)

## 1.2 NuGet パッケージのインストール

NuGet で以下のパッケージをネイティブのプロジェクト（NLogSample.Droid, NLogSample.iOS）にインストールします。

- NLog
    - ライブラリ本体です
- NLog.Config
    - 構成ファイルのテンプレートです

NLog.Config をインストールすると、以下の二つのファイルがプロジェクトに作成されますが、Android ネイティブのプロジェクトに配置されたファイルは移動が必要です。

- NLog.config
- NLog.xsd

Platform |NLog.config ファイルの配置場所   |注意事項|
:-----------|:-----------|:-----------|
Android |Asset フォルダ直下に配置します |ビルドアクションを AndroidAsset に設定してください|
iOS |プロジェクト直下|特にありません|

![AndroidAsset に設定](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/ad3b300ede51964133552717608145737240fe64/NLogConfigFileBuildActionSetting.png)

## 1.3 ログ出力のためのインターフェースを作成
NLog のログ出力インターフェース（[ILogger](https://github.com/NLog/NLog/blob/master/src/NLog/ILogger.cs)）の中からアプリケーションで利用するインターフェースを Xamarin.Forms のプロジェクトに定義します。

![ILoggingService](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/2bcdd7ba78391b831dc93260e993370560a2ad49/ILoggingService.png)

今回は、NLog のメソッドからから以下のメソッドをインタフェースとして定義しました。
インターフェースの各メソッドを、NLog の [ILogger](https://github.com/NLog/NLog/blob/master/src/NLog/ILogger.cs) のメソッドに1対1に対応させています。

```csharp:ILoggingService.cs
public interface ILoggingService
{
    void Info(string message);
    void Info(string format, params object[] args);

    void Error(string message);
    void Error(string format, params object[] args);
    void Error(Exception e, string message);
    void Error(Exception e, string format, params object[] args);

    void Fatal(string message);
    void Fatal(string format, params object[] args);
    void Fatal(Exception e, string message);
    void Fatal(Exception e, string format, params object[] args);

    void Debug(string message);
    void Debug(string format, params object[] args);

    void Trace(string message);
    void Trace(string format, params object[] args);

    void Warn(string message);
    void Warn(string format, params object[] args);
}
```

## 1.4 実装クラスの作成
ログ出力処理は、Android、iOS でも同じなので、共有プロジェクト（NLogSample.Shared）を Android ネイティブ、iOS ネイティブの両方から参照します。

![プロジェクト構造](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/9f7cddd58e344828192064e5daee66dd58900aa2/Project-Structure.png)

共有プロジェクトに、実装クラスを作成します。実装時のポインとは以下の点です。

- NLog.ILogger のインスタンスの取得
    - `LogManager.GetCurrentClassLogger()` を利用してインスタンスを取得します
- Xamarin.Forms の ILoggingService のメソッドの処理内で、NLog を使ったログ出力処理を実装する

コードの例を以下に示します。
今回は、Xamarin.Forms の DependencyService 経由でインスタンス化されるようにしています。

```csharp:LoggingServiceImplementation.cs
using System;
using NLogSample.Logging;
using Xamarin.Forms;
using NLog;

[assembly: Dependency(typeof(LoggingServiceImplementation))]
namespace NLogSample.Logging
{
    public class LoggingServiceImplementation : ILoggingService
    {
        private ILogger logger = LogManager.GetCurrentClassLogger();

        public void Error(string message)
        {
            logger.Error(message);
        }
        // その他のメソッドは省略しています
    }
}
```

## 1.5 ログ出力処理の実装
ログを出力する処理を必要に応じて記述します。例を以下に示します。

```csharp:Main.xaml.cs
var logger = DependencyService.Get<ILoggingService>();

logger.Info("Information レベルのログ出力");
logger.Warn("警告レベルのログ出力");
logger.Debug("デバッグレベルのログ出力");
logger.Trace("詳細レベルのログ出力");
```

## 1.6 構成ファイルの設定
ログ出力に関する設定を構成ファイルで設定します。以下の二つのセクションの設定を行います

- `<targets>` セクションに `<target>` 要素を追加して出力先のパス、出力項目の設定を行います
- `<rules>` セクションに `<logger>` 要素を追加して出力レベルなどの設定を行います

先ずは、NLog.config のテンプレートに記載さている例を利用して、以下のように設定します。

`<target>` セクション定義の例を示します。

```xml:NLog.config
<!-- Android での設定例 -->
<target xsi:type="File" 
        name="logfile" 
        encoding="shift_jis"
        fileName="/data/data/NLogSample.Droid/logs/nlog-sample.log"
        layout="${longdate},${uppercase:${level}},${message}" />
```

属性 |概要   |補足|
:-----------|:-----------|:-----------|
xsi:type |出力方法の種類を指定します||
name |`rules` で指定する `logger` の出力先の名前|`logger` で指定する `writeTo` 属性の値を合わせてください|
encoding |出力ファイルのエンコード||
fileName |出力ファイルのパス|後述の出力先の設定値を参考にしてください|
layout |出力レイアウト||

各プラットフォームでログの出力先の設定値は以下を参考にしてください。

Platform |fileName 属性の設定値（例）   |説明|
:-----------|:-----------|:-----------|
Android |/data/data/NLogSample.Droid/logs/nlog-sample.log| /data/data/[パッケージ名]/[ログファイル名]|
iOS |${specialfolder:folder=MyDocuments}/../Library/nlog-sample.log||

`logger` 要素の設定例を示します。

```xml:NLog.config
  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules> 
```
属性 |概要   |
:-----------|:-----------|
name |logger の名前を指定します|
minlevel |出力レベルの最低値を指定します|
writeTo |出力先設定の名前を指定します|

## 1.7 動作確認

Android の実機にサンプルをデプロイして確認した結果を以下に示します。
logger の minlevel で Debug レベルを指定しているため、 Trace レベルのログが出力されていないことがわかります。

![Android-Reuslt](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/4523e46ab9b120cfb9585a55c1d22ac4689c63ca/Result-Android.png)

# 2. ログ出力の調整

## 2.1 出力レベルの切り替え
出力レベルは、`<rules>` セクションに定義した `<logger>` 要素の `minlevel` の値を変更することで切り替えることができます。
出力レベルは、降順で以下の通りです。

- Fatal
- Error
- Warn
- Info
- Debug
- Trace

`minlevel` の値を ”Info” に変更すると Debug, Trace レベルのログは出力されなくなります。また、"Off" にするとログ出力は行われなくなります。必要に応じて出力レベルを指定してください。

![minlevel-Info](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/c386af4229be0a54062d0b5b4ae0739c011e4e4c/TraceLevel-Result-Android.png)

## 2.2 出力項目のカスタマイズ
ログ解析時に必要となる典型的な項目（プロセス ID など）を自動的に出力することができます。
`<target>` セクションの `layout` 属性を削除して、子要素に `<layout>` タグを追加、子要素の `<column>` タグで出力項目を設定します。
ここでは、CSV ファイルにログを出力するための設定値の例を示します。

```xml:NLog.config
<target name="logfile"
        xsi:type="File"
        fileName="/data/data/NLogSample.Droid/logs/nlog-sample.csv"
        encoding="shift_jis">
       <layout xsi:type="CSVLayout">
         <quoting>All</quoting>
         <withHeader>true</withHeader>
         <delimiter>Comma</delimiter>
         <column name="time" layout="${longdate}" />
         <column name="logger" layout="${logger}"/>
         <column name="level" layout="${level}"/>
         <column name="machinename" layout="${machinename}"/>
          <column name="windows-identity" layout="${windows-identity}"/>
          <column name="appdomain" layout="${appdomain}"/>
          <column name="processid" layout="${processid}"/>
          <column name="processname" layout="${processname}"/>
          <column name="threadid" layout="${threadid}"/>
          <column name="message" layout="${message}" />
          <column name="stacktrace" layout="${exception:format=Type,Message,StackTrace,Data:maxInnerExceptionLevel=5}" />
        </layout>
</target>
```
Android の実機に配置したサンプルで動作確認を行うと、以下のように出力されました。

![Customize-Layout](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/ab940689bf6b0846c2397d01e77523ffa10c7cbc/Customize-Layout.png)

レイアウト、既定の出力項目の詳細については以下を参考にしてください。

- [Layouts](https://github.com/nlog/nlog/wiki/Layouts)
- [Layout Renderers](https://github.com/nlog/nlog/wiki/Layout-Renderers)

## 2.3 ログファイルの退避

一つのファイルにログを出力し続けるとファイルサイズが肥大化してしまいますが、NLog では、適切なタイミングで、過去のログを別のファイルに退避することができます。ファイルローリングの設定は、`<target>` 要素の属性値で設定することができます。
一定時間経過するとファイルを退避したい場合の設定項目を以下に示します。

属性 |概要   |設定例|
:-----------|:-----------|:-----------|
archiveFileName|退避先のファイル名の形式|/data/data/NLogSample.Droid/logs/archives/nlog-sample-{#}.csv|
archiveNumbering|退避の方法|Date|
archiveEvery|退避する時間間隔|Day, Minute 等|
archiveDateFormat|ファイル名に付与する日付のフォーマット|yyyy-MM-dd-HH-mm|
maxArchiveFiles|退避するファイル数の最大値|５|

1分経過するとファイルの退避を行い、過去五世代まで archives ディレクトリに保存する場合は、以下のような設定になります。ファイル名には、[yyyy-MM-dd-HH-mm] の形式で日付時刻が付与されます。
（動作確認用のサンプルのため、退避の時間間隔を1分としています。）

```xml:NLog.config
<target name="logfile"
        xsi:type="File"
        fileName="/data/data/NLogSample.Droid/logs/nlog-sample.csv"
        archiveFileName="/data/data/NLogSample.Droid/logs/archives/nlog-sample-{#}.csv"
        archiveEvery="Minute"
        archiveNumbering="Date"
        maxArchiveFiles="5"
        archiveDateFormat="yyyy-MM-dd-HH-mm"
        encoding="shift_jis">
       <layout xsi:type="CSVLayout">
         <quoting>All</quoting>
         <withHeader>true</withHeader>
         <delimiter>Comma</delimiter>
         <column name="time" layout="${longdate}" />
         <column name="logger" layout="${logger}"/>
         <column name="level" layout="${level}"/>
         <column name="machinename" layout="${machinename}"/>
          <column name="windows-identity" layout="${windows-identity}"/>
          <column name="appdomain" layout="${appdomain}"/>
          <column name="processid" layout="${processid}"/>
          <column name="processname" layout="${processname}"/>
          <column name="threadid" layout="${threadid}"/>
          <column name="message" layout="${message}" />
          <column name="stacktrace" layout="${exception:format=Type,Message,StackTrace,Data:maxInnerExceptionLevel=5}" />
        </layout>
</target>

```
Android の実機でサンプルを動作させると、1分間隔でログファイルが退避されています。

![Rolling](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/2ed8c9a366433aa90e71a64973b0a797e6efc1bd/Rolling.png)

保存数の最大値を超える場合は、一番古いログファイルが削除されていることがわかります。

![Rolling-Result](https://gist.githubusercontent.com/tafuji/e635225eba58df192bfc5f20ff3339ff/raw/2ed8c9a366433aa90e71a64973b0a797e6efc1bd/Rolling-Result.png)

ログファイル退避のその他の設定方法に関しては、以下を参照してください。

- [Archival Options](https://github.com/nlog/NLog/wiki/File-target#archival-options)


# 3. まとめ
手順のまとめを以下に箇条書きで記載します。

- Native プロジェクト(Android, iOS)に Nlog をインストールする
- NLog で利用するログ出力処理のインターフェースを Xamarin.Forms プロジェクトに定義する
- 共有プロジェクトで、Xamarin.Forms で定義したインタフェースを実装する
- Native プロジェクトで共有プロジェクトを参照する
- Xamarin.Forms のプロジェクトでは、インタフェース経由でログ出力する
    - DependencyService, Unity (DI コンテナ) 等を利用する
- NLog の構成ファイル（NLog.config）の配置場所
    - Android は Asset フォルダ直下
    - iOS はプロジェクト直下で可
- お好みで構成ファイルの設定を行う
- その他の詳細設定は本家のサイトを参考にする
    - [NLog](http://nlog-project.org/)
    - [NLog Tutorial](https://github.com/NLog/NLog/wiki/Tutorial)
        - NLog 利用方法の全般的なトピックはここが参考になります
    - [File target](https://github.com/nlog/NLog/wiki/File-target)
        - ファイル出力に関する `<target>` の設定の詳細は、ここを参照してください
    - [Layouts](https://github.com/nlog/nlog/wiki/Layouts)
        - 出力レイアウトに関する設定の詳細は、ここに記載されています
    - [Layout Renderers](https://github.com/nlog/nlog/wiki/Layout-Renderers)
        - 出力項目のカスタマイズを行いたい場合はここを参照してください
    - [Archival Options](https://github.com/nlog/NLog/wiki/File-target#archival-options)
        - ファイルのローリング設定の詳細はここを参照してください

# さいごに
この記事では、デバッグログを出力するために NLog の利用手順について記載しました。
NLog 以外にも [@amay077](http://qiita.com/amay077) さんの [「Xamarin.Forms をガチで使う時のプロジェクト構成(2016冬Ver)」](http://qiita.com/amay077/items/5df0cb5c37d0598ce90f) で紹介されている [MetroLog](https://www.nuget.org/packages/MetroLog/) というライブラリもあります。
どちらのライブラリも System.Diagnostics.Debug クラスを利用するよりも作業効率が上がると思いますので、お好みで選択していただければと思います。

この記事が、NLog を利用される方に少しでも役立てば、非常にうれしく思います。