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

发布于 2024-08-15 01:57:15 字数 89 浏览 5 评论 0原文

我希望在 log4net 消息中包含调用堆栈(例如调用我的方法)。有这样做的标准方法吗?

(我知道这会很慢,但我只需要在出现一些错误时执行此操作)

I wish to include the call stack (e.g. the methods that called me) in a log4net message. Is there a standard way of doing this?

(I know this will be slow, but I only need to do it on some errors)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

忆离笙 2024-08-22 01:57:15

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

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

请参阅 有关 PatternLayout 的文档了解更多信息。

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

您总是可以使用像 new StackTrace().ToString() 这样的东西自己写出来,但我猜您问的原因是您希望它可以在日志记录中配置配置。

我将进行更深入的研究,但我的直觉是没有办法配置它,并且您最终必须实现自己的布局类。

编辑++
好的 - 这是一个自定义模式布局类,它派生自 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>

Yes - you can get this stack information using the following patterns in a pattern layout:

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

See the this documentation on the PatternLayout for more information.

Edit in response to Ian's comment below: I don't think log4net can be configured to write out the whole stack.

You can always fall back on writing it out for yourself, using something like new StackTrace().ToString(), but I'm guessing the reason you ask is you want this to be configurable in the logging configuration.

I'll have a deeper look, but my gut feeling is there is no way to configure this, and that you'd end up having to implement your own Layout class.

Edit++
OK - here is a custom pattern layout class that derives from PatternLayout but adds in a layout %stack.

This code is a bit rough - illustrative only - not production ready! (for example, you may not have security permission to access the stack you are trying to print)

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());
            }
        }
    }
}

You can then configure this with the following pattern configuration (note %stack at the end of the layout):

  <layout type="ScratchPad.CustomPatternLayout,ScratchPad">
    <conversionPattern value="%date %-5level %message%newline %type %file %line %method %location %class %stack" />
  </layout>
人间☆小暴躁 2024-08-22 01:57:15

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();
                }
            }
        }
    }
}

Robs answer was the best i found, i decided to extend it a little to print the full stack trace only for exceptions if it helps anyone else.

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();
                }
            }
        }
    }
}
无风消散 2024-08-22 01:57:15

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

If you are catching exceptions, then just log Exception.ToString(), as that will contain the full stack trace, and will be available as the normal log message.

暮凉 2024-08-22 01:57:15

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

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}";
    }
}

(完整源代码

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

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

It's not the cleanest approach, but a quick way to achieve this is to create an artificial exception that contains the desired stack trace:

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)

Then you could use it like so:

Log.Error("It went kaboom", new ArtificialStackTraceException());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文