log4net是否支持在日志消息中包含调用堆栈

时间:2009-12-15 09:30:29

标签: .net debugging log4net

我希望在log4net消息中包含调用堆栈(例如,调用我的方法)。有没有一种标准的方法呢?

(我知道这会很慢,但我只需要做一些错误)

4 个答案:

答案 0 :(得分:11)

是 - 您可以在模式布局中使用以下模式获取此堆栈信息:

%type %file %line %method %location %class

有关详细信息,请参阅PatternLayout上的this文档。

编辑以回应以下Ian的评论:我不认为 log4net可以配置为写出整个堆栈。

您可以随时使用类似new StackTrace().ToString()之类的内容自行编写,但我猜您问的原因是您希望在日志记录配置中配置它。

我会有更深入的了解,但我的直觉是没有办法配置它,并且你最终必须实现自己的Layout类。

修改++ 好的 - 这是一个自定义模式布局类,它派生自PatternLayout,但在布局%stack中添加。

这段代码有点粗糙 - 仅供说明 - 不准备生产! (例如,您可能没有安全权限来访问您尝试打印的堆栈)

public class CustomPatternLayout : PatternLayout
{
    public CustomPatternLayout()
    {
        this.AddConverter("stack", typeof(StackTraceConverter));
    }
}

public class StackTraceConverter : PatternLayoutConverter
{
    protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
    {
        var stack = new StackTrace();

        var frames = stack.GetFrames();
        for (var i = 0; i < frames.Length; i++ )
        {
            var frame = frames[i];

            // if the stack frame corresponds to still being inside the log4net assembly, skip it.
            if (frame.GetMethod().DeclaringType.Assembly != typeof(LogManager).Assembly)
            {
                writer.WriteLine("{0}.{1} line {2}",
                    frame.GetMethod().DeclaringType.FullName,
                    frame.GetMethod().Name, 
                    frame.GetFileLineNumber());
            }
        }
    }
}

然后,您可以使用以下模式配置对此进行配置(请注意布局末尾的%stack):

  <layout type="ScratchPad.CustomPatternLayout,ScratchPad">
    <conversionPattern value="%date %-5level %message%newline %type %file %line %method %location %class %stack" />
  </layout>

答案 1 :(得分:2)

如果要捕获异常,则只需记录Exception.ToString(),因为它将包含完整的堆栈跟踪,并将作为正常的日志消息提供。

答案 2 :(得分:2)

Robs的答案是我找到的最好的答案,我决定将它扩展一点,以便只有在例外情况下帮助其他人打印完整的堆栈跟踪。

public class StackTraceConverter : PatternLayoutConverter
{
    private static readonly Assembly _assembly = typeof (PatternLayoutConverter).Assembly;

    public StackTraceConverter()
    {
        base.IgnoresException = false;
    }

    protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
    {
        var ex = loggingEvent.ExceptionObject;
        if (ex == null)
            return;
        writer.WriteLine(ex.ToString());

        bool displayFilenames = true;   // we'll try, but demand may fail
        var stack = new StackTrace(displayFilenames);
        int skip = 0;
        for (var i = 0; i < stack.FrameCount; i++)
        {
            var sf = stack.GetFrame(i);
            var mb = sf.GetMethod();
            if (mb != null)
            {
                var t = mb.DeclaringType;
                if (t.Assembly != _assembly)
                {
                    //this skips the current method and the method catching the exception
                    if (skip < 2)
                    {
                        skip++;
                        continue;
                    }
                    writer.Write("   at ");

                    // if there is a type (non global method) print it
                    if (t != null)
                    {
                        writer.Write(t.FullName.Replace('+', '.'));
                        writer.Write(".");
                    }
                    writer.Write(mb.Name);

                    // deal with the generic portion of the method
                    if (mb is MethodInfo && mb.IsGenericMethod)
                    {
                        Type[] typars = ((MethodInfo) mb).GetGenericArguments();
                        writer.Write("[");
                        int k = 0;
                        bool fFirstTyParam = true;
                        while (k < typars.Length)
                        {
                            if (fFirstTyParam == false)
                                writer.Write(",");
                            else
                                fFirstTyParam = false;

                            writer.Write(typars[k].Name);
                            k++;
                        }
                        writer.Write("]");
                    }

                    // arguments printing
                    writer.Write("(");
                    ParameterInfo[] pi = mb.GetParameters();
                    bool fFirstParam = true;
                    for (int j = 0; j < pi.Length; j++)
                    {
                        if (fFirstParam == false)
                            writer.Write(", ");
                        else
                            fFirstParam = false;

                        String typeName = "<UnknownType>";
                        if (pi[j].ParameterType != null)
                            typeName = pi[j].ParameterType.Name;
                        writer.Write(typeName + " " + pi[j].Name);
                    }
                    writer.Write(")");

                    // source location printing
                    if (displayFilenames && (sf.GetILOffset() != -1))
                    {
                        // If we don't have a PDB or PDB-reading is disabled for the module,
                        // then the file name will be null.
                        String fileName = null;

                        // Getting the filename from a StackFrame is a privileged operation - we won't want
                        // to disclose full path names to arbitrarily untrusted code.  Rather than just omit
                        // this we could probably trim to just the filename so it's still mostly usefull.
                        try
                        {
                            fileName = sf.GetFileName();
                        }
                        catch (SecurityException)
                        {
                            // If the demand for displaying filenames fails, then it won't
                            // succeed later in the loop.  Avoid repeated exceptions by not trying again.
                            displayFilenames = false;
                        }

                        if (fileName != null)
                        {
                            // tack on " in c:\tmp\MyFile.cs:line 5"
                            writer.Write(" in {0}:line {1}", fileName, sf.GetFileLineNumber());
                        }
                    }
                    writer.WriteLine();
                }
            }
        }
    }
}

答案 3 :(得分:1)

这不是最干净的方法,但实现这一目标的一种快速方法是创建一个包含所需堆栈跟踪的人为异常:

public class ArtificialStackTraceException : Exception
{
    public ArtificialStackTraceException(
        bool shouldIncludeFileInfo = true,
        string message = "Artificial exception used to generate a stack trace."
    ) : base(message)
    {
        var stackTrace = new System.Diagnostics.StackTrace(shouldIncludeFileInfo);
        StackTrace = stackTrace.ToString();
    }

    public override string StackTrace { get; }

    public override string ToString()
    {
        return $"{nameof(ArtificialStackTraceException)}:{Environment.NewLine}{StackTrace}";
    }
}

full source

然后你可以像这样使用它:

Log.Error("It went kaboom", new ArtificialStackTraceException());