やりたいこと
ASP.NET Core 2.x
を使った環境において、Trace.TraceError
やTrace.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 を参考にパッケージの導入&設定ファイルの追加まで行なってください。
参考にオレオレ設定も添付します。
<?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は無視し、以下の実装を行なってください。
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の通り設定を変更し、書き出されるログに情報を詰めるためのサポートクラスを追加します。
これは本来NLog
のcallsite
で賄える呼び出し情報等がうまく取れなかったための措置です。
※どこからログを書いてもcallsite
は常にSystem.Diagnostics.TraceInternal.WriteLine
になってしまっていた
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));
}
}
// 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
配下に書き出されていきます。
参考
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
で書き出していますがシカタナイネ。
誰かの助けになれば!