NLogのログ暗号化の拡張


最近、ある要求があり、ログを暗号化する必要がありました。システムがずっとNLogを使用していたので、この基盤の上での暗号化しか選択の余地がありませんでした。この記事で紹介するNLogの使用方法は以前から存在していますが、後々のために記録しておきます。

NLogでの暗号化は比較的に取り扱いやすいです。TargetWithLayoutのサブクラスを定義し、そのWriteメソッドをオーバーライドするだけです。暗号化アルゴリズムは自由に決めることができ、これにより柔軟性を最大化できます。以下のコード中のEncryptedFileTargetがカスタマイズされたTargetWithLayoutサブクラスです。そして、`[Target("MyEncryptedFile")]`アトリビュートは、nlog.configでこの暗号化クラスを設定するために使用されます。それ以外には特に参照する必要はありません。

```csharp
using NLog.Targets;
using NLog;
using NLog.Web;
using System.Security.Cryptography;
using System.Text;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Host.UseNLog();
var app = builder.Build();
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
    app.Logger.LogInformation("完成测试");
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        )).ToArray();
    return forecast;
});
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
[Target("MyEncryptedFile")]
public sealed class EncryptedFileTarget : TargetWithLayout
{
    public string FileName { get; set; }
    private string key = "bb45f81e2db4a7668d98638402e2461991785f98a89520051149c271652514ba";

    protected override void Write(LogEventInfo logEvent)
    {
        var logMessage = this.Layout.Render(logEvent);
        var encryptedMessage = Encrypt(logMessage, key);
        FileName = FileName.Replace("${basedir}", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)).Replace("${shortdate}", DateTime.Now.ToString("yyyy-MM-dd"));
        System.IO.File.AppendAllText(FileName, encryptedMessage + Environment.NewLine);
    }

    private string Encrypt(string clearText, string key)
    {
        var clearBytes = Encoding.Unicode.GetBytes(clearText);
        using (var encryptor = Aes.Create())
        {
            var saltBytes = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 };
            var pdb = new Rfc2898DeriveBytes(key, saltBytes, 1000, HashAlgorithmNumber.SHA256);

            encryptor.Key = pdb.GetBytes(32);
            encryptor.IV = pdb.GetBytes(16);
            using (MemoryStream ms = new MemoryStream())
            {
                using (var cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(clearBytes, 0, clearBytes.Length);
                    cs.Close();
                }
                clearText = Convert.ToBase64String(ms.ToArray());
            }
        }
        return clearText;
    }
}
```

以下はMyEncryptedFileを使用するNLogの設定ファイルで、EncryptedFileTargetが属する名前空間をreferencesノードに含める必要があります。

```xml
<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info">
  <extensions>
    <add assembly="NLogDemo" />
  </extensions>
  <targets>
    <target xsi:type="MyEncryptedFile" name="allfile" fileName="${basedir}\logs\nlog-AspNetCore-all-${shortdate}.log"
        layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" />
    <target xsi:type="File" name="ownFile-web" fileName="${basedir}\logs\nlog-AspNetCore-own-${shortdate}.log"
        layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
    <target xsi:type="Console" name="lifetimeConsole" layout="${MicrosoftConsoleLayout}" />
  </targets>
  <rules>
    <logger name="*" minlevel="Trace" writeTo="allfile" />
    <logger name="Microsoft.Hosting.Lifetime" minlevel="Info" writeTo="lifetimeConsole, ownFile-web" final="true" />
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
    <logger name="System.Net.Http.*" maxlevel="Info" final="true" />
    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
  </rules>
</nlog>
```

以下は暗号化されたログの効果です：

![加密后的效果](https://mmbiz.qpic.cn/sz_mmbiz_png/4CAA63nvHRBRxjSlwp33v5B9kU0A2YJbD7SVtHh6XX7xRX2yNHtIXGUqTicHMic4sibrGrNTjkDibcZFVedaCNYXbQ/640?wx_fmt=png&from=appmsg)

(Translated by GPT)


元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247488196&idx=1&sn=d7a268d3e5768f5ab80b507f4fdc0f6f&chksm=9f004deea877c4f8437972533841f148a7936715ea0d23e78c26d4d446919fc27897448582f1&token=1006949870&lang=zh_CN#rd&wt.mc_id=MVP_325642