最近项目上线后遇到exception没有堆栈信息。所以跟踪一下 源码,其中主要的code如下:
// Returns the stack trace as a string. If no stack trace is // available, null is returned. public virtual String StackTrace { #if FEATURE_CORECLR [System.Security.SecuritySafeCritical] #endif get { // By default attempt to include file and line number info return GetStackTrace(true); } } // Computes and returns the stack trace as a string // Attempts to get source file and line number information if needFileInfo // is true. Note that this requires FileIOPermission(PathDiscovery), and so // will usually fail in CoreCLR. To avoid the demand and resulting // SecurityException we can explicitly not even try to get fileinfo. #if FEATURE_CORECLR [System.Security.SecurityCritical] // auto-generated #endif private string GetStackTrace(bool needFileInfo) { string stackTraceString = _stackTraceString; string remoteStackTraceString = _remoteStackTraceString; #if !FEATURE_CORECLR if (!needFileInfo) { // Filter out file names/paths and line numbers from _stackTraceString and _remoteStackTraceString. // This is used only when generating stack trace for Watson where the strings must be PII-free. stackTraceString = StripFileInfo(stackTraceString, false); remoteStackTraceString = StripFileInfo(remoteStackTraceString, true); } #endif // !FEATURE_CORECLR // if no stack trace, try to get one if (stackTraceString != null) { return remoteStackTraceString + stackTraceString; } if (_stackTrace == null) { return remoteStackTraceString; } // Obtain the stack trace string. Note that since Environment.GetStackTrace // will add the path to the source file if the PDB is present and a demand // for FileIOPermission(PathDiscovery) succeeds, we need to make sure we // don't store the stack trace string in the _stackTraceString member variable. String tempStackTraceString = Environment.GetStackTrace(this, needFileInfo); return remoteStackTraceString + tempStackTraceString; } #if FEATURE_CORECLR [System.Security.SecuritySafeCritical] #endif public override String ToString() { return ToString(true, true); } #if FEATURE_CORECLR [System.Security.SecurityCritical] // auto-generated #endif private String ToString(bool needFileLineInfo, bool needMessage) { String message = (needMessage ? Message : null); String s; if (message == null || message.Length <= 0) { s = GetClassName(); } else { s = GetClassName() + ": " + message; } if (_innerException!=null) { s = s + " ---> " + _innerException.ToString(needFileLineInfo, needMessage) + Environment.NewLine + " " + Environment.GetResourceString("Exception_EndOfInnerExceptionStack"); } string stackTrace = GetStackTrace(needFileLineInfo); if (stackTrace != null) { s += Environment.NewLine + stackTrace; } return s; }
Exception的StackTrace属性只返回当前对象的站信息,toString方法首先需要获取当前的Message,然后获取内部exception的tostring方法,最后获取 GetStackTrace方法的返回值,该方法主要内容来源于 Environment.GetStackTrace方法,其实现code如下:
internal static String GetStackTrace(Exception e, bool needFileInfo) { // Note: Setting needFileInfo to true will start up COM and set our // apartment state. Try to not call this when passing "true" // before the EE's ExecuteMainMethod has had a chance to set up the // apartment state. -- StackTrace st; if (e == null) st = new StackTrace(needFileInfo); else st = new StackTrace(e, needFileInfo); // Do no include a trailing newline for backwards compatibility return st.ToString( System.Diagnostics.StackTrace.TraceFormat.Normal ); }
调用的是StackTrace的tostring方法,其实现如下:
internal String ToString(TraceFormat traceFormat) { bool displayFilenames = true; // we'll try, but demand may fail String word_At = "at"; String inFileLineNum = "in {0}:line {1}"; if(traceFormat != TraceFormat.NoResourceLookup) { word_At = Environment.GetResourceString("Word_At"); inFileLineNum = Environment.GetResourceString("StackTrace_InFileLineNumber"); } bool fFirstFrame = true; StringBuilder sb = new StringBuilder(255); for (int iFrameIndex = 0; iFrameIndex < m_iNumOfFrames; iFrameIndex++) { StackFrame sf = GetFrame(iFrameIndex); MethodBase mb = sf.GetMethod(); if (mb != null) { // We want a newline at the end of every line except for the last if (fFirstFrame) fFirstFrame = false; else sb.Append(Environment.NewLine); sb.AppendFormat(CultureInfo.InvariantCulture, " {0} ", word_At); Type t = mb.DeclaringType; // if there is a type (non global method) print it if (t != null) { sb.Append(t.FullName.Replace('+', '.')); sb.Append("."); } sb.Append(mb.Name); // deal with the generic portion of the method if (mb is MethodInfo && ((MethodInfo)mb).IsGenericMethod) { Type[] typars = ((MethodInfo)mb).GetGenericArguments(); sb.Append("["); int k=0; bool fFirstTyParam = true; while (k < typars.Length) { if (fFirstTyParam == false) sb.Append(","); else fFirstTyParam = false; sb.Append(typars[k].Name); k++; } sb.Append("]"); } // arguments printing sb.Append("("); ParameterInfo[] pi = mb.GetParameters(); bool fFirstParam = true; for (int j = 0; j < pi.Length; j++) { if (fFirstParam == false) sb.Append(", "); else fFirstParam = false; String typeName = "<UnknownType>"; if (pi[j].ParameterType != null) typeName = pi[j].ParameterType.Name; sb.Append(typeName + " " + pi[j].Name); } sb.Append(")"); // 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(); } #if FEATURE_CAS_POLICY catch (NotSupportedException) { // Having a deprecated stack modifier on the callstack (such as Deny) will cause // a NotSupportedException to be thrown. Since we don't know if the app can // access the file names, we'll conservatively hide them. displayFilenames = false; } #endif // FEATURE_CAS_POLICY 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: mpMyFile.cs:line 5" sb.Append(' '); sb.AppendFormat(CultureInfo.InvariantCulture, inFileLineNum, fileName, sf.GetFileLineNumber()); } } #if FEATURE_EXCEPTIONDISPATCHINFO if (sf.GetIsLastFrameFromForeignExceptionStackTrace()) { sb.Append(Environment.NewLine); sb.Append(Environment.GetResourceString("Exception_EndStackTraceFromPreviousThrow")); } #endif // FEATURE_EXCEPTIONDISPATCHINFO } } if(traceFormat == TraceFormat.TrailingNewLine) sb.Append(Environment.NewLine); return sb.ToString(); }
这个方法最主要的还是 StackFrame sf = GetFrame(iFrameIndex); 它返回跟踪栈的信息。跟踪栈的信息主要来源于StackTrace的构造函数
public StackTrace(Exception e, bool fNeedFileInfo) { if (e == null) throw new ArgumentNullException("e"); Contract.EndContractBlock(); m_iNumOfFrames = 0; m_iMethodsToSkip = 0; CaptureStackTrace(METHODS_TO_SKIP, fNeedFileInfo, null, e); }
核心实现CaptureStackTrace code如下:
private void CaptureStackTrace(int iSkip, bool fNeedFileInfo, Thread targetThread, Exception e) { m_iMethodsToSkip += iSkip; StackFrameHelper StackF = new StackFrameHelper(fNeedFileInfo, targetThread); GetStackFramesInternal(StackF, 0, e); m_iNumOfFrames = StackF.GetNumberOfFrames(); if (m_iMethodsToSkip > m_iNumOfFrames) m_iMethodsToSkip = m_iNumOfFrames; if (m_iNumOfFrames != 0) { frames = new StackFrame[m_iNumOfFrames]; for (int i = 0; i < m_iNumOfFrames; i++) { bool fDummy1 = true; bool fDummy2 = true; StackFrame sfTemp = new StackFrame(fDummy1, fDummy2); sfTemp.SetMethodBase(StackF.GetMethodBase(i)); sfTemp.SetOffset(StackF.GetOffset(i)); sfTemp.SetILOffset(StackF.GetILOffset(i)); #if FEATURE_EXCEPTIONDISPATCHINFO sfTemp.SetIsLastFrameFromForeignExceptionStackTrace(StackF.IsLastFrameFromForeignExceptionStackTrace(i)); #endif // FEATURE_EXCEPTIONDISPATCHINFO if (fNeedFileInfo) { sfTemp.SetFileName(StackF.GetFilename (i)); sfTemp.SetLineNumber(StackF.GetLineNumber(i)); sfTemp.SetColumnNumber(StackF.GetColumnNumber(i)); } frames[i] = sfTemp; } // CalculateFramesToSkip skips all frames in the System.Diagnostics namespace, // but this is not desired if building a stack trace from an exception. if (e == null) m_iMethodsToSkip += CalculateFramesToSkip(StackF, m_iNumOfFrames); m_iNumOfFrames -= m_iMethodsToSkip; if (m_iNumOfFrames < 0) { m_iNumOfFrames = 0; } } // In case this is the same object being re-used, set frames to null else frames = null; }
顺便提一下,VS在release模式下是可以调试的,也可以获取堆栈信息。