最近項目上線後遇到exception沒有堆棧信息。因此跟蹤一下 源碼,其中主要的code以下:app
// 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以下:ide
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:\tmp\MyFile.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的構造函數oop
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以下:ui
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模式下是能夠調試的,也能夠獲取堆棧信息。this