LoginSignup
5
3

More than 5 years have passed since last update.

ASP.NET CoreでSystem.Diagnostic.TraceをNLogに流し込む方法

Posted at

やりたいこと

ASP.NET Core 2.xを使った環境において、Trace.TraceErrorTrace.TraceInformationで書き出される内容をNLogで記録したい。

なにがうれしい?

標準のSystem.Diagnostic.Traceでログを吐けるので、各RazorPages/ControllerでLoggerを宣言・DIしなくて済む。
NLogで物理ファイルに吐き出せるので Azure Application Insights ではそのうち消えてしまう生ログが永続化できる。
NLog以外のロガーパッケージを導入する際に変更が容易(未検証)。

環境等

ターゲットフレームワーク
  • .NET Core 2.0
使用パッケージ
  • Microsoft.AspNetCore.All 2.0.5
  • NLog 4.5.7
  • NLog.Web.AspNetCore 4.5.4
開発環境
  • Windows 10 Pro
  • Visual Studio Enterprise 2017 v15.7.5

実装

公式ページの 0,1,2,3 を参考にパッケージの導入&設定ファイルの追加まで行なってください。
参考にオレオレ設定も添付します。

nlog.config
<?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"
      internalLogFile="c:\temp\internal-nlog.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <variable name="standardLayout" value="${date:universalTime=false:format=yyyy-MM-dd HH\:mm\:ss.fffK:culture=ja-JP} ${uppercase:${level}} ${message}"/>
  <!-- よく使うcallsiteは今回においてはうまく機能しなかった。後述。 -->

  <targets>
    <!--
      これ↓らの記述で出力できる。Info/Warn/Error/Debug
      Trace.TraceInformation("Trace.TraceInformation");
      Trace.TraceWarning("Trace.TraceWarning");
      Trace.TraceError("Trace.TraceError");
      Trace.WriteLine("Trace.WriteLine");
    -->

    <target xsi:type="File" name="logfileDebug" fileName="${basedir}/logs/${shortdate}_Debug.log"
            layout="${standardLayout}" />

    <target xsi:type="File" name="logfileInfo" fileName="${basedir}/logs/${shortdate}_Info.log"
            layout="${standardLayout}" />

    <target xsi:type="File" name="logfileWarn" fileName="${basedir}/logs/${shortdate}_Warn.log"
            layout="${standardLayout}" />

    <target xsi:type="File" name="logfileError" fileName="${basedir}/logs/${shortdate}_Error.log"
            layout="${standardLayout} ${exception:format=ToString}" />

  </targets>

  <rules>
    <logger name="*" minlevel="Debug" maxlevel="Debug" writeTo="logfileDebug" />
    <logger name="*" minlevel="Warn" maxlevel="Warn"  writeTo="logfileWarn" />
    <logger name="*" minlevel="Error" writeTo="logfileError" />
    <logger name="Microsoft.*" minlevel="Info" maxlevel="Info" final="true" />
    <logger name="*" minlevel="Info" maxlevel="Info" writeTo="logfileInfo" />
  </rules>
</nlog>

次に公式ページの手順4は無視し、以下の実装を行なってください。

Startup.cs
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            //~略~

            //NLogを使う設定を宣言し
            env.ConfigureNLog("nlog.config");
            loggerFactory.AddNLog();
            //さらにNLogに流し込むListenerを追加することをでTrace.Xxxxでの出力がNLog設定で書き出される
            Trace.Listeners.Add(new NLogTraceListener()
            {
                IndentSize = 4,
                Name = "MyNLogTraceListener",
            });

            //本来もっと前に記録したいところだけど記述準的にここで初めてTraceをNLogに流し込めるようになるのでここでAppStartログ
            Trace.TraceInformation("Appcation Start!");
        }

そして公式ページの手順5の通り設定を変更し、書き出されるログに情報を詰めるためのサポートクラスを追加します。
これは本来NLogcallsiteで賄える呼び出し情報等がうまく取れなかったための措置です。
※どこからログを書いてもcallsiteは常にSystem.Diagnostics.TraceInternal.WriteLineになってしまっていた

MyLogger.cs
    public static class MyLogger
    {
        private static string Format(string sourceFilePath, string memberName, int sourceLineNumber, string message)
        {
            var fileName = sourceFilePath.Substring(sourceFilePath.LastIndexOf("\\") + 1);
            return $"{fileName} {memberName} {sourceLineNumber} {message}";
        }
        public static void Info(string message,
            [CallerFilePath] string sourceFilePath = "",
            [CallerMemberName] string memberName = "",
            [CallerLineNumber] int sourceLineNumber = 0)
        {
            Trace.TraceInformation(Format(sourceFilePath, memberName, sourceLineNumber, message));
        }

        public static void Warn(string message,
            [CallerFilePath] string sourceFilePath = "",
            [CallerMemberName] string memberName = "",
            [CallerLineNumber] int sourceLineNumber = 0)
        {
            Trace.TraceWarning(Format(sourceFilePath, memberName, sourceLineNumber, message));
        }

        public static void Error(string message,
            [CallerFilePath] string sourceFilePath = "",
            [CallerMemberName] string memberName = "",
            [CallerLineNumber] int sourceLineNumber = 0,
            Exception ex = null)
        {
            if (ex == null)
            {
                Trace.TraceError(Format(sourceFilePath, memberName, sourceLineNumber, message));
            }
            else
            {
                Trace.TraceError(Format(sourceFilePath, memberName, sourceLineNumber, message) + Environment.NewLine + ex.ExtractException());
            }
        }

        public static void Debug(string message,
            [CallerFilePath] string sourceFilePath = "",
            [CallerMemberName] string memberName = "",
            [CallerLineNumber] int sourceLineNumber = 0)
        {
            Trace.WriteLine(Format(sourceFilePath, memberName, sourceLineNumber, message));
        }
    }
ExceptionHelper.cs
    // https://teratail.com/questions/24669
    public static class ExceptionHelper
    {
        public static string ExtractException(this Exception ex, int indent = 2)
        {
            var indentStr = new String(' ',indent);
            StringBuilder traceLog = new StringBuilder();
            StackTrace trace = new StackTrace(ex, true);
            foreach (var frame in trace.GetFrames())
            {
                traceLog.AppendLine($"{indentStr}File Name : {frame.GetFileName()}");
                traceLog.AppendLine($"{indentStr}Class Name : {frame.GetMethod().ReflectedType.Name}");
                traceLog.AppendLine($"{indentStr}Method Name : {frame.GetMethod()}");
                traceLog.AppendLine($"{indentStr}Line Number : {frame.GetFileLineNumber()}");
                traceLog.AppendLine($"=======================================================");
            }

            return traceLog.ToString();
        }
    }

あとは好きに、ログを残したい場所にMyLogger.InfoなりMyLogger.Errorなり書くことで、所定のフォルダのlogs配下に書き出されていきます。
image.png

参考

Automatically log System.diagnostics.trace messages to an Nlog target
Routing System.Diagnostics.Trace and System.Diagnostics.TraceSource logs through NLog
Getting started with ASP.NET Core 2
Azureの診断モニタにエラーログのソース行数を表示する方法
呼び出し元情報 (C#)
方法 : トレース リスナーを作成し初期化する
how to route System.Diagnostics.TraceSource logs through NLog in .NET Core

最後に

「nlog.config」が小文字じゃないと動かなかったり、Startup.csではなくProgram.csで設定するサンプルがあったりで割と素直にいきませんでした。
結局ログはTrace.TraceXxxxではなくMyLogger.Xxxxで書き出していますがシカタナイネ。

誰かの助けになれば!

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3