过去我也遇到过同样的问题,而真正帮助我的是能够使用 Mono.Cecil 在 DLL 中注入一行跟踪代码。下面的代码将跟踪注入到 DLL 的未签名版本中,并输出一个新的 DLL,该 DLL 可用于通过在开头注入日志行来在代码中创建“缩进跟踪”(如果需要,则结束......未在下面显示)每个方法的时间,以便您可以对每个调用的进入和存在进行计时。有很多第三方工具,但这很容易(最多工作一天),让开发人员完全控制,而且作为奖励,是免费的。
您还需要创建一个 DLL(下面是 IISStackTraceProvider),其中包含跟踪类和可用于记录数据的静态调用“TraceStep”。要跨工作进程和线程重建该数据,您可以使用“HttpContext.Items 属性”将 GUID 和秒表与每个 BeginRequest/EndRequest 相关联。由于您的通话挂起...您想跟踪任何“开始”但从不“结束”或以超时结束的通话,然后将其余的电话扔掉以保持快速。
我已经在我们的生产环境中的网络场中针对每台服务器每小时约 100 万次调用进行了测试,而不会影响性能,但请注意您要记录哪些请求以及哪些请求会被丢弃。另外,我使用 Redis 来捕获日志,因为写入时间非常快而且它是免费的,然后在我捕获问题后只需读取 Redis 数据。
class TraceInjection
{
private ELogLevel logLevel;
public enum ELogLevel
{
eLow,
eMid,
eHigh
}
public TraceInjection(ELogLevel logLevel)
{
this.logLevel = logLevel;
}
public bool InjectTracingLine(string assemblyPath, string outputDirectory)
{
CustomAttribute customAttr;
AssemblyDefinition asmDef;
// New assembly path
string fileName = Path.GetFileName(assemblyPath);
string newPath = outputDirectory + "\\" + fileName;
// Check if Output directory already exists, if not, create one
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
ModuleDefinition modDefCopy = null;
TypeDefinition typDefCopy = null;
try
{
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(System.IO.Path.GetDirectoryName(assemblyPath));
var parameters = new ReaderParameters
{
AssemblyResolver = resolver,
};
// Load assembly
asmDef = AssemblyDefinition.ReadAssembly(assemblyPath, parameters);
String functionsFound = "";
foreach (var modDef in asmDef.Modules)
{
modDefCopy = modDef;
foreach (var typDef in modDef.Types)
{
typDefCopy = typDef;
foreach (MethodDefinition metDef in typDef.Methods)
{
try
{
// Skipping things I personally don't want traced...
if (metDef.IsConstructor ||
metDef.IsAbstract ||
metDef.IsCompilerControlled ||
metDef.IsGetter ||
metDef.IsSetter
) continue;
functionsFound += String.Format("{0}\r\n", metDef.Name.Trim());
// Get ILProcessor
ILProcessor ilProcessor = metDef.Body.GetILProcessor();
/*** Begin Method ******/
// Load fully qualified method name as string
Instruction i1 = ilProcessor.Create(
OpCodes.Ldstr,
String.Format(">,{0},{1}", metDef.Name.Replace(",", ""), asmDef.Name.Name)
);
ilProcessor.InsertBefore(metDef.Body.Instructions[0], i1);
// Call the method which would write tracing info
Instruction i2 = ilProcessor.Create(
OpCodes.Call,
metDef.Module.Import(
typeof(IISStackTraceProvider).GetMethod("TraceStep", new[] { typeof(string) })
)
);
ilProcessor.InsertAfter(i1, i2);
}catch(Exception ex)
{
// ...
}
}
}
}
Console.Write(functionsFound);
Console.ReadKey();
// Save modified assembly
asmDef.Write(newPath, new WriterParameters() { WriteSymbols = true });
}
catch (Exception ex)
{
modDefCopy = null;
typDefCopy = null;
// Nothing to be done, just let the caller handle exception
// or do logging and so on
throw;
}
return true;
}
public bool TryGetCustomAttribute(MethodDefinition type, string attributeType, out CustomAttribute result)
{
result = null;
if (!type.HasCustomAttributes)
return false;
foreach (CustomAttribute attribute in type.CustomAttributes)
{
if (attribute.Constructor.DeclaringType.FullName != attributeType)
continue;
result = attribute;
return true;
}
return false;
}
}