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)
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>
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();
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With