Docker+.NET 6環境で動いているBlazor WebAssemblyアプリのログをファイル出力する

#はじめに
C#でログ出力する方法、Docker+.NETでアプリを動かす方法、Blazor WebAssemblyアプリの実装方法等、個々に的を絞ればそれらの情報は比較的簡単に手に入ります。
しかし、それらを組み合わせた情報は逆に入手しにくい印象です。
というわけで、今回は前回の内容からの続きですが、Docker+.NET 6環境で動いているBlazor WebAssemblyアプリからログをファイル出力する内容でお話します。

前回の内容は[Docker+.NET 6環境でBlazor WebAssemblyアプリを動かしてみる](https://qiita.com/k_oshima23/items/da7cccef48fbbebb0b87)を参照してください。

今回のファイル出力の対象ですが、Blazor WebAssemblyアプリのサーバ側の話です。
クライアント側はアプリがブラウザ上で動くため、ログをファイル出力することは考慮していません。
クライアント側のログはブラウザのコンソール等に出力します。
または運用例で考えると致命的なエラー発生時にエラー内容のログをエラー画面に表示させます。
クライアント利用者側にその内容を問い合わせてもらう程度でしょう。

話が長くなりましたが本題に入ります。

#前提条件
前回の記事の内容の設定が済んでいる前提である。
追加の条件としてDocker Composeが導入済みである。
今後、複数のDockerコンテナを組み合わせて運用することを想定して、今回からはDocker Composeを利用する。


#NLogの追加
今回はNLogを使用します。
NLog以外にもlog4netなど様々なライブラリがありますが、NLogはその中でも導入が比較的簡単です。

まずはコマンドプロンプトを起動してサーバ側のプロジェクトファイルにNLogを追加します。

コマンドプロンプトのカレントディレクトリを前回作成したHelloWorld.Server.csprojが置かれたフォルダへ移動させます。
dirコマンドを実行すると次のような一覧が表示されます。
![HelloWorld.Server.csproj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/5b06b118-84be-1716-5695-b81576acac60.png)


そこで次のコマンドを実行します。

```batch
dotnet add package NLog.Extensions.Logging
dotnet add package NLog.Web.AspNetCore
```

HelloWorld.Server.csprojをテキストエディタなどで開きます。

```xml:HelloWorld.Server.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.1" />
    <PackageReference Include="NLog.Extensions.Logging" Version="1.7.4" />
    <PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Client\HelloWorld.Client.csproj" />
    <ProjectReference Include="..\Shared\HelloWorld.Shared.csproj" />
  </ItemGroup>


</Project>

```

#NLogの設定
appsettings.jsonにNLogの設定を記載します。
今回の設定内容はログの種別問わずファイルへ書き出す設定にしています。

```json:appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Trace",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "NLog": {
    "internalLogLevel": "Info",
    "internalLogFile": "./log/internal-nlog.txt",
    "extensions": [
      {
        "assembly": "NLog.Extensions.Logging"
      },
      {
        "assembly": "NLog.Web.AspNetCore"
      }
    ],
    "targets": {
      "allfile": {
        "type": "File",
        "fileName": "./log/nlog-all-${shortdate}.log",
        "layout": "${longdate}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}"
      }
    },
    "rules": [
      {
        "logger": "*",
        "minLevel": "Trace",
        "writeTo": "allfile"
      }
    ]
  }
}
```

#ソースコードへロガーの設定を追記します。
まずServerフォルダ配下のProgram.csにNLogの設定を記載します。
今回はデフォルトで作成されたソースに追記します。

```csharp:Program.cs
// ↓追記
using NLog.Extensions.Logging;

var builder = WebApplication.CreateBuilder(args);

// NLogの設定情報が取れるようにappsetting.jsonを読み込む
var config = new ConfigurationBuilder()
     .SetBasePath(System.IO.Directory.GetCurrentDirectory())
     .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build();

// builderにNLogを追加する
builder.Logging.AddNLog(new NLogLoggingConfiguration(config.GetSection("NLog")));

// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();
```
今回はプロジェクト作成時にデフォルトで作成されるWeatherForecastController.csにログを出力する処理を記載します。

```csharp:WeatherForecastController.cs
using Microsoft.AspNetCore.Mvc;
using HelloWorld.Shared;

namespace HelloWorld.Server.Controllers;

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        // ここでログ出力させる
        _logger.LogInformation("Get called");
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .ToArray();
    }
}
```

#実際にBlazor WebAssemblyアプリを動してみます
Blazor WebAssemblyアプリ側のログ出力の準備ができたので、次にDocker Composeを使ってアプリ動作環境を構築します。
今回はこのようなフォルダとファイル構成にます。二階層まで表示しています。

```
C:.
│  docker-compose.yml
└─HelloWorld
    │  Dockerfile
    │  HelloWorld.sln
    │  
    ├─Client
    ├─Server
    └─Shared
```

docker-compose.ymlの内容は以下のように記載します。

```yml:docker-compose.yml
version: '3'

services:
  helloworld:
    build: 
      context: ./HelloWorld
    container_name: helloworld
    volumes:
      - ./helloworld/log:/app/log
    ports:
      - "80:80"
```

```
- ./helloworld/log:/app/log
```
このように書くことでローカル上にもログファイルが残せるようにします。

それではDocker Composeを使ってビルドします。
コマンドプロンプトのカレントディレクトリをdocker-compose.ymlが置かれたフォルダへ移動します。
次のコマンドをコマンドプロンプトで実行してビルドします。

```batch
docker-compose build
```

ビルドが完了したら起動するために次のコマンドプロンプトでコマンドを実行します。

```batch
docker-compose up -d
```

起動が完了するとプロジェクトフォルダにlogフォルダが増えます。

![logフォルダ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/4e7d27f8-5029-68b1-ae19-d2e1491ceb90.png)

logフォルダ内にはログファイルが増えています。
![log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/435376f4-0358-4589-2908-96bfede887d1.png)

中身を見ます。
![中身.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/1fc76d0d-6828-0de0-209d-2d8309237ef6.png)

次にログが更新されるか見ます。
Weather forecastをブラウザに表示すると。。。。
![天気.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/2ec72cf0-c4f5-46b0-e66a-00de9c233591.png)

logフォルダを見ると。。。。
![logフォルダ2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/740f3baf-ca36-c001-8870-c8240a25f6cc.png)

タイムスタンプが更新されています。では中身を見ます。
![log2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/1d8b2014-176d-9fc4-1597-ab49721589d3.png)

最終行にログが増えています。

コンテナを再起動しても。。。
![log3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342210/0e0b0f4a-a12d-7aa6-0cb4-9eb198893afc.png)

前回の内容が残っており続きから追記されています。

以上がDocker+.NET 6環境で動いているBlazor WebAssemblyアプリのログをファイル出力してみるお話でした。