我们有一个MVC应用程序,每个人都知道然后(从一天几次到每2-3天一次的任何事情)遭受站点范围的挂起(它不会响应任何请求而不会重新启动它拥有)。我们设法将嫌疑人的数量减少到一个页面(每当网站遭受挂起时,此页面将显示在挂起请求的顶部)。知道哪个页面是罪魁祸首并没有帮助,因为我们无法在开发机器上重新创建问题。事实上,我们甚至无法在生产机器上重新创建问题(大多数情况下,只需打开有问题的页面就不会破坏任何东西)。
我们所知道的是,有时当用户访问此页面(控制器操作)时,该站点将挂起,我们知道发出请求的方式并不多,这可能会导致挂起(来自IIS)日志我们知道用户如何到达有缺陷的页面,并且我们正在处理一个没有查询字符串参数的简单GET。)
我们想知道挂起发生在代码中的确切位置,但我们不知道如何从app / worker线程/ IIS / Windows Server中获取此类信息。没有异常保存到Windows日志中,我们内置到应用程序记录器中也没有选择任何异常(可能与挂起有关)。
有什么方法可以告诉IIS工作线程到底在做什么(比如获取相应的文件和代码行)?
P.S。我在另一个问题中描述了网站范围内的确切症状,但这些问题与此问题无关。此外,问题最终过于宽泛,只带来了一般性答案。 P.S.2 我们通过查看IIS中的工作进程\请求找到了违规页面。
答案 0 :(得分:3)
在过去,我遇到了同样的问题,真正快速帮助我的是能够使用Mono.Cecil在DLL中注入一行跟踪代码。下面的代码将跟踪注入到DLL的无符号版本中,并输出一个新的DLL,可以通过在开始时注入日志行来在代码中创建“缩进的跟踪”(如果需要则结束......下面未显示)每种方法的方法,以便您可以计时和每次通话的存在。有很多第三方工具,但这很容易(最好是一天工作),给开发人员完全控制,作为奖励,是免费的。
您还需要创建一个DLL(下面的IISStackTraceProvider),它具有跟踪类和静态调用“TraceStep”,可用于记录数据。要跨工作进程和线程重建该数据,可以使用“HttpContext.Items属性”将GUID和秒表与每个BeginRequest / EndRequest相关联。由于您的呼叫挂起...您想要跟踪任何“开始”但从不“结束”的呼叫或结束时间,并将其余部分抛弃以保持快速。
我已经在我们的生产环境中的Web场中每小时/每个服务器进行了大约一百万次调用,而不会影响性能,但要注意要记录的请求以及丢弃哪些请求。此外,我使用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;
}
}