In trying to track down a performance issue that is only occurring in our production environment, we have enabled tracing within the app so see method calls and page load times.
This is working well and there is lots of information that helps to track down issues. However, the only way of viewing this information is to browse to the Trace.axd and then view each request individually.
It is also only possible to track the first X requests in this way and X has a maximum limit of 10,000.
Is there a way to direct this trace information to a file or database? I believe this can be done using System.Diagnostics
, however, I am not having much luck.
I have enabled tracing using
<trace enabled="true" writeToDiagnosticsTrace="true" />
I have tried using the XmlWriterTraceListener
using
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add
name="XmlWriterTraceListener"
type="System.Diagnostics.XmlWriterTraceListener"
initializeData="c:\trace.xml"
/>
</listeners>
</trace>
</system.diagnostics>
This results in an xml file containing the timestamps and data for the trace items such as "Begin Load", "End Load", etc.
However, it only seems to log a single request and does not log all requests. In addition, whilst the load times are useful, ideally I would like to have available all of the information that can be seen in the Trace.axd, such as request data, post data, session data, etc.
Is this possible at all without any major code changes? Ideally I would like to enable this using only web.config changes.
Alternatively, I have looked into other applications such as RedGate and Equatec's profiling tools, however, I would like to exhaust the non invasive tracing options first.
The application is written in ASP.Net 3.5 and C#.
I have a standard HttpModule i use for all my web applications to monitor performance. The logger used can be changed, also can do things like remove white space from source, compress, or send emails if certain limits are reached. Its easier than trawling through asp.net traces as you get only the information you decide is importent.
public class PerfHttpModule : IHttpModule {
private static Common.Logging.ILog log = Common.Logging.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static readonly string CONTEXT_RequestStart = "PerfHttpModule_RequestStart";
public static readonly string CONTEXT_RequestId = "PerfHttpModule_RequestId";
public void Init(HttpApplication context) {
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
private void context_BeginRequest(object sender, EventArgs e) {
try {
if (HttpContext.Current != null) {
HttpContext.Current.Items[CONTEXT_RequestStart] = DateTime.Now;
HttpContext.Current.Items[CONTEXT_RequestId] = random.Next(999999).ToString("D6");
log.Info("Url: " + HttpContext.Current.Request.Url + " (" + HttpContext.Current.Request.ContentLength + ")");
}
} catch {
}
}
private void context_EndRequest(object sender, EventArgs e) {
if (HttpContext.Current.Items.Contains(CONTEXT_RequestStart)) {
DateTime time1 = (DateTime)HttpContext.Current.Items[CONTEXT_RequestStart];
DateTime time2 = DateTime.Now;
double ms = (time2 - time1).TotalMilliseconds;
log.Info("TotalMilliseconds: " + ms);
if (ms > AppSettings.SlowPage || ms > AppSettings.ErrorSlowPage) {
StringBuilder sb = new StringBuilder();
sb.Append("Slow page detected." + "\t");
sb.Append("TotalMilliseconds: " + ms + "\t");
sb.Append("Url: " + HttpContext.Current.Request.Url.ToString());
if (ms > AppSettings.ErrorSlowPage) {
log.Error(sb.ToString());
} else if (ms > AppSettings.SlowPage) {
log.Warn(sb.ToString());
}
}
}
}
}
UPDATE
if (HttpContext.Current != null) {
NameValueCollection tmp = new NameValueCollection(HttpContext.Current.Request.ServerVariables);
foreach (string i in tmp.Keys) {
}
if (HttpContext.Current.Server != null) {
if (HttpContext.Current.Server.GetLastError() != null) {
}
}
if (HttpContext.Current.Session != null) {
foreach (string i in HttpContext.Current.Session.Keys) {
}
}
if (HttpContext.Current.Request.Cookies != null) {
foreach (string i in HttpContext.Current.Request.Cookies.Keys) {
}
}
if (HttpContext.Current.Response.Cookies != null) {
foreach (string i in HttpContext.Current.Response.Cookies.Keys) {
}
}
if (HttpContext.Current.Items != null) {
foreach (string i in HttpContext.Current.Items.Keys) {
}
}
if (HttpContext.Current.Request.Form != null) {
foreach (string i in HttpContext.Current.Request.Form.Keys) {
}
}
}
The trace data is under cover a standard DataSet. You can't get a hold on it officially, but here is a hack that can do it (it seems to work on .NET 2 to 4):
public static DataSet GetTraceData(Page page)
{
if (page == null)
throw new ArgumentNullException("page");
return (DataSet)typeof(TraceContext).GetField("_requestData",
BindingFlags.NonPublic | BindingFlags.Instance).GetValue(page.Trace);
}
Once you have the DataSet, you can do anything you want with it, save to an XML file (DataSet.WriteXml), a stream, etc.
Of course, since it uses an internal field, it may not be supported in the future.