CoverStory
LANGUAGES:
C#
ASP.NET
VERSIONS: 2.0
Understanding the ASP.NET Lifecycle (Redux)
Logging and Troubleshooting
By Adwait Ullal
Understanding the lifecycle of ASP.NET pages is vital to
troubleshooting issues in ASP.NET applications. Logging can be strategically
added to events using the .NET Framework s built-in tracing. These logs can aid
in pinpointing the causes of performance bottlenecks, as well as in gathering
usage statistics for your applications. This article explains the key steps in
the lifecycle of a page. It also shows how to easily integrate tracing into
these steps, and how to use these logs in conjunction with other tools to
troubleshoot performance. This article employs C# with .NET Framework 2.0 and
Visual Studio .NET 2005, but the concepts apply to .NET Framework 1.0 and 1.1.
(This article has been adapted from Understanding
the ASP.NET Lifecycle: Logging and Troubleshooting published in the October
2004 issue of asp.netPRO, with the
consent and cooperation of the author, Luther Miller.)
Many things happen behind the scenes when an ASP.NET page
is invoked. Eventually, the code in the page is processed, and an HTML response
is sent back to the client. For a good overview of this process, see ASP.NET
Page Life Cycle Overview ( Visual Web Developer/Web Pages sub-topic
hierarchy) in the Visual Studio 2005 documentation. But what processing
actually takes place? When does authentication take place? When does the view state
get parsed and processed? When does a button click event get fired? The answers
are in the .NET Framework documentation but not necessarily where you d
expect to find them.
Look for a page titled ASP.NET Web Server Control Event
Model in the Visual Web Developer/Web Pages sub-topic hierarchy of the Visual
Studio 2005 documentation. With the realization that an ASP.NET Page is a
special type of ASP.NET control, this document explains the lifecycle of a page.
The table in this document lists all the phases of the life of a control, as
well as the methods that correspond to each step. Some of the methods also have
corresponding events. For example, the Load step corresponds to the OnLoad
method and fires the Load event. Visual Studio creates the Page_Load method and
hooks it up to the Load event for you when you create a new page. Some of the
steps, such as Load View
State and Render, don t fire events,
although they have methods that you can override.
Page processing is simply a part of what takes place when
a request is handled by ASP.NET. Find ASP.NET Application Life Cycle Overview
under ASP.NET Life Cycle in the Visual Web Developer/Web Pages sub-topic
hierarchy of Visual Studio 2005 documentation. This document contains a table
that lists events and the order in which they are fired. You can write code for
these events and place it in the Global.asax file. Note that a Page is a type
of HTTP handler, so the part of this list that refers to the handler is
executed is essentially where all the Page s lifecycle steps take place.
Combining the events and methods from the HttpApplication class with the Page
class yields a complete picture of what happens when a request is handled (see
Figure 1).
|
Object
|
Step
|
Description
|
|
HttpApplication
|
*BeginRequest
|
Request processing is starting.
|
|
HttpApplication
|
*AuthenticateRequest
|
User has been identified.
|
|
HttpApplication
|
AuthorizeRequest
|
User authorization has been verified.
|
|
HttpApplication
|
ResolveRequestCache
[The handler is created.]
|
See if response can be retrieved from cache, avoiding
handler execution.
|
|
HttpApplication
|
AcquireRequestState
|
Session state for current request acquired.
|
|
HttpApplication
|
PreRequestHandlerExecute
[The handler is executed.]
|
Handler (Page) is about to be executed.
|
|
Page
|
Init event (OnInit method)
|
Initialize settings needed in the Page.
|
|
Page
|
LoadViewState method
|
ViewState is processed and state is restored to
controls.
|
|
Some Controls
|
LoadPostData method
|
Process postback data and update controls; only controls
that implement IpostBackDataHandler.
|
|
Page
|
Load event
(OnLoad method)
|
Execute any code common to all requests for the page.
|
|
Some Controls
|
RaisePostDataChangedEvent method
|
Raise events such as TextBox Changed; only controls that
implement IpostBackDataHandler.
|
|
Page
|
RaisePostBackEvent method
|
Raise events such as Button Click.
|
|
Page
|
PreRender event (OnPreRender method)
|
Chance to make changes before state is saved and page is
rendered.
|
|
Page
|
SaveViewState method
|
State of controls is saved.
|
|
Page
|
Render method
|
The page s response is created.
|
|
Page
|
Dispose method
|
Final cleanup.
|
|
Page
|
Unload event (OnUnload method)
|
Another chance for final cleanup, but Dispose is where
cleanup should occur.
|
|
HttpApplication
|
PostRequestHandlerExecute
|
Handler (Page) execution has finished.
|
|
HttpApplication
|
ReleaseRequestState
[Response output filtered.]
|
Session state gets saved.
|
|
HttpApplication
|
UpdateRequestCache
|
Provide opportunity for response to be added to the
cache for future requests.
|
|
HttpApplication
|
*EndRequest
|
Request processing is finished.
|
|
*Included by
default in global.asax.
|
Figure 1: The phases of the life of a
control, as well as the methods that correspond to each step.
How can you take advantage of this information? Using the
.NET Framework s tracing classes, we can simply add a line of code that writes
the date, time, and some other basic information to a file whenever any of
these steps takes place. If performance is not as good as you expect, you can
use this information to see exactly where the performance hit takes place.
Finding a one-second gap between AuthenticateRequest and AuthorizeRequest leads
to a different conclusion than finding a one-second gap from the start of
LoadViewState to the start of LoadPostData. The entire time from BeginRequest
to EndRequest is how long ASP.NET spends processing the user s request; if this
is five seconds, is the time being eaten up in code you write in the Page_Load
event, or somewhere else?
If you are not familiar with the System.Diagnostics
tracing classes, I suggest you read up on them. Adding some instructions to the
configuration section of the web.config file is a good place to start; see Figure
2 for an example that defines a trace switch, as well as a text file to output
trace statements to. Be sure that the folder where the log file should go ( C:\Logs
in Figure 2) exists and that the user account that runs ASP.NET (usually ASPNET
for Windows XP) has rights to this folder; otherwise, you may see a Configuration
Error when you start your application.
<configuration>
...
<system.diagnostics>
<switches>
<add
name="Lifecycle" value="4" /> <!-- Verbose=4 -->
</switches>
<trace
autoflush="true" indentsize="0">
<listeners>
<add
name="LifecycleLogFile"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="C:\Logs\lifecycle.log"/>
</listeners>
</trace>
</system.diagnostics>
...
</configuration>
Figure 2: Avoid
configuration errors.
Some of the lifecycle steps are events, some are methods,
and in some cases, both are provided. For the HttpApplication object, we can
write events for all the steps, but the methods behind the scenes are not
exposed. For the Page class on the other hand, all the steps have analogous
methods, but they don t all have events. Where an event is provided, we can
write code to be executed when the event fires. For the methods, we need to
override the method in order to add tracing (we also case the base method from
our override so that everything continues to work correctly).
For example, we can add code to the Page_Load event if we
want to add a line of code that will write the date and time to a log file
using the Trace classes whenever the Page s Load step takes place. We can also
override the OnLoad method, making sure we call base.OnLoad from our OnLoad.
Tracing each step enables us to build a timeline of what
steps took place when. Then we can determine how long each step took, which
will expose which steps, if any, are suffering from performance issues. For
example, if the Render step takes two seconds, you ll know that the problem is
there and not in the Load event. You are now in a much better position to find
the source of the problem than if all that you knew was that the whole request
is taking about two seconds.
You ll need to log very specific information to properly
reconstruct the timeline for each request. An ASP.NET application typically has
multiple users and, thus, concurrent requests you need a way to match entries
in the log file to a particular request, because the steps from different
requests will be interspersed. Each concurrent request runs on a different
thread, but all the steps of a single request run on the same thread. This
makes the thread id very useful. Other information, such as the username, the
URL of the requested page, and the session id, are also useful. Figure 3 contains
an example of a trace statement that logs this information.
public static TraceSwitch lifecycleSwitch =
new
TraceSwitch("Lifecycle", "Lifecycle Log");
private void Page_Load(object sender, System.EventArgs e)
{
System.Diagnostics.Trace.WriteLineIf(
lifecycleSwitch.TraceInfo,
string.Format(
"{0:yyyy-MM-dd
HH:mm:ss.ffff}\t{1}\t{2}\t{3}\t{4}\
t{5}\t{6}\t{7}\t{8}",
DateTime.Now, "Page",
this.GetType().Name,
"Page_Load", "Start",
User.Identity.Name,
AppDomain.GetCurrentThreadId(),
Request.Url.PathAndQuery,
Session.SessionID ));
// Put user code to
initialize the page here
sqlDataAdapter1.Fill(dataSet1);
DataGrid1.DataBind();
System.Diagnostics.Trace.WriteLineIf(
lifecycleSwitch.TraceInfo,
string.Format(
"{0:yyyy-MM-dd
HH:mm:ss.ffff}\t{1}\t{2}\t{3}\t{4}\
t{5}\t{6}\t{7}\t{8}",
DateTime.Now, "Page",
this.GetType().Name,
"Page_Load", "End",
User.Identity.Name,
AppDomain.GetCurrentThreadId(),
Request.Url.PathAndQuery, Session.SessionID ));
}
Figure 3: A trace
statement that logs the username, URL of the requested page, and session id.
Looking at Figure 3 in detail, you can see that I added a
trace statement to the beginning and the end of the Page_Load event, which is
created by default in Visual Studio when a new ASP.NET page is created. Tracing
at the beginning and end of Page_Load allows me to determine how much time is
spent in the code that I wrote in that event. Notice that I specified System.Diagnostics.Trace
instead of simply Trace . This is necessary even if I reference System.Diagnostics
with a using statement because the Page class has a property called Trace that
is used for ASP.NET tracing. ASP.NET tracing is another kind of tracing that is
outside the scope of this article; to learn more about that, see ASP.NET Tracing
in the .NET Framework documentation. The tracing I m using here is referred to
as System.Diagnostics tracing.
Notice in Figure 3 that I use the WriteLineIf method. It
takes a boolean as the first parameter and only writes the line if that value
is true. By using the trace switch that was created in Figure 2, tracing can be
easily turned on and off and different levels of logging can be applied simply by
changing the web.config file. The levels and their corresponding values are
nothing (0), Error (1), Warning (2), Info (3), and Verbose (4). I find it odd
that you can t specify the enumeration by name in the web.config file, but
maybe Microsoft will fix that in a future release. I set the value to 4 in Figure
2 so that all levels would be output; if you set the value, say, to 2, then
only the Error and Warning levels would be output. You can create multiple
switches to suit your needs.
In the call to WriteLineIf I delimit all the data that I
output with tabs; this makes it easier to parse the data later. I output the
current date/time, an arbitrary category (I chose Page ), the name of the Web
form s class (I get the name using reflection), the name of the step I m in,
whether it is the start or end of the step, the user making the request, the id
of the thread I m in, the requested URL (it may contain more useful information
than just the name of the Web form s class), and the current session id (which may
help draw together separate requests from the same user s session).
This may seem like a hefty chunk of code to put at the
beginning and end of each event especially when you recall all the events
from Figure 1 and consider how many Web forms you may have in your application.
Fortunately, there is a more efficient solution than adding this code to all
your forms. All pages that you create using ASP.NET inherit from
System.Web.UI.Page by default. We can create a custom class, let s call it
TracingPage, that inherits from the default Page class. Then, we have our pages
inherit from our custom class instead of the default. In TracingPage, all the
methods that correspond to each step can be overloaded and the logging can be
performed there. Then, any page in your application can be made to do the
tracing simply by having it inherit from TracingPage. Note that I prefer to
overload the methods instead of adding event handlers not all the page steps
have events, but we can take care of all the steps by overloading their
respective methods.
Take a look at my TracingPage class in Listing One. I created a method called DoStandardTrace
that logs all the information that we need. I made it as generic as possible
it even retrieves the name of the step that is being logged (such as OnInit)
from the stack frame so that it doesn t need to be passed in (it assumes that
it is being called directly by the method whose name it should log). I then
override each of the methods in the Page class that correspond to a step that
should be logged. In each method, I log the start of the method, call the
base class implementation of the method, and then log the end of the method.
The processing actually occurs when the base class implementation is called.
For example, when base.OnLoad is called, your Page_Load event will get fired.
From the sample code (available for download; see end of article
for details), run WebForm2, which inherits from the TracingPage class. (Note:
the sample assumes you have SQL Server and the pubs database installed
locally.) After the page loads, click the button once to cause a post back.
Your log file should now look similar to Figure 4. (If you received a
configuration error, be sure that the output folder for the log file exists.)
Notice that the initial page request did not execute the LoadViewState and the
RaisePostBackEvent methods, but that the request caused by clicking the button
did. Also, note how the output looks. The request, for example, is /Lifecycle/WebForm2.aspx ,
which will also contain any query parameters that may have been included. The
thread id is an integer (on your workstation it may often be the same value,
but in a multi-user environment you should see the number change often because
different threads from the pool service different requests). The session id, an
alphanumeric string, will remain the same for all requests coming from the same
browser session. Finally, notice that the user name was blank in my example because
I did not turn on any type of authentication. If you are using Windows
Authentication, the user name would be in the familiar domain\user format.
Logging this sort of information can help you track down problems that users
report. The session id and user name help you follow the workflow of requests
performed by a given user.
2004-07-18 14:58:14.3101 Page WebForm2_aspx
OnInit Start 700 / Lifecycle/WebForm2.aspx
4fpvhuyyvt3zpkfyckw3k4v1
2004-07-18 14:58:14.3201 Page WebForm2_aspx
OnInit End 700 / Lifecycle/ ...
2004-07-18 14:58:14.3302 Page WebForm2_aspx
OnLoad Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.4203 Page WebForm2_aspx
OnLoad End 700 / Lifecycle/ ...
2004-07-18 14:58:14.4203 Page WebForm2_aspx
OnPreRender Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.4203 Page WebForm2_aspx
OnPreRender End 700 / Lifecycle/ ...
2004-07-18 14:58:14.4303 Page WebForm2_aspx
SaveViewState Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.4303 Page WebForm2_aspx
SaveViewState End 700 / Lifecycle/ ...
2004-07-18 14:58:14.7107 Page WebForm2_aspx
Render Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.7608 Page WebForm2_aspx
Render End 700 / Lifecycle/ ...
2004-07-18 14:58:14.7608 Page WebForm2_aspx
OnUnload Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.7608 Page WebForm2_aspx
OnUnload End 700 / Lifecycle/ ...
2004-07-18 14:58:14.7608 Page WebForm2_aspx
Dispose Start 700 / Lifecycle/ ...
2004-07-18 14:58:14.7608 Page WebForm2_aspx
Dispose End 700 / Lifecycle/ ...
2004-07-18 14:58:23.8238 Page WebForm2_aspx
OnInit Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.8238 Page WebForm2_aspx
OnInit End 700 / Lifecycle/ ...
2004-07-18 14:58:23.8438 Page WebForm2_aspx
LoadViewState Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.8538 Page WebForm2_aspx
LoadViewState End 700 / Lifecycle/ ...
2004-07-18 14:58:23.8839 Page WebForm2_aspx
OnLoad Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.8939 Page WebForm2_aspx
OnLoad End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9039 Page WebForm2_aspx
RaisePostBackEvent
Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9139 Page WebForm2_aspx
RaisePostBackEvent
End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9240 Page WebForm2_aspx
OnPreRender Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9340 Page WebForm2_aspx
OnPreRender End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9340 Page WebForm2_aspx
SaveViewState Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9340 Page WebForm2_aspx
SaveViewState End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9440 Page WebForm2_aspx
Render Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9540 Page WebForm2_aspx
Render End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9540 Page WebForm2_aspx
OnUnload Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9540 Page WebForm2_aspx
OnUnload End 700 / Lifecycle/ ...
2004-07-18 14:58:23.9540 Page WebForm2_aspx
Dispose Start 700 / Lifecycle/ ...
2004-07-18 14:58:23.9640 Page WebForm2_aspx
Dispose End 700 / Lifecycle/ ...
Figure 4: A sample
log file.
The TracingPage class takes care of logging what happened
during page processing. Now it s time to add some tracing to the global.asax
file to trace what is happening at the application level. Open the global.asax
file (Visual Studio 2003 adds it for you by default) and look at the source
code. You ll see that events like Application_BeginRequest already exist,
making it easy to add logging to them. The only choice you have here is to
handle events (you can t override methods like we did in TracingPage). Only
some of the events have already been added. If you look at global.asax in
design view you can open the properties window and select any event you want to
capture; adding events this way will create new events called
Global_[EventName]. However, you can also add additional events with the
correct Application_[EventName], like the defaults in the file, and they will
be picked up automatically.
I ve added logging for all the application events in the
downloadable code for this article. In Listing Two, you
can see that the code is very similar to that in TracingPage. There is a
generic routine to log the information that we want to capture, and then a call
to that routine from each of the events. However, for certain events, some of
the information will not be available yet, and attempting to access it will
throw an exception. For example, in the Application_Start event, you cannot
access the Request object, so I pass flags to the DoStandardTrace method
telling it whether or not to try to log the request and the session (the two
troublemakers).
With tracing now in place for both Page and
HttpApplication events, I encourage you to experiment with pages in your
existing ASP.NET applications, and then examine the logs to get a feel for what
kind of data is produced. You can customize the logging to suite your needs.
For example, you could create multiple trace switches to change the granularity
of what you log. You could create switches for each step, and then configure
which steps are logged by changing the switch values in web.config.
You might also wish to add logging to other important
areas of your application. For example, if you have a data access wrapper, you
could log right before and right after every stored procedure call, enabling
you to analyze database performance in your ASP.NET application. Anywhere that
your application performs operations where you may want to monitor performance
are potential places to log Web services, remoting, third-party APIs, etc.
If you are collecting quite a bit of data, as you are
likely to do in a real-world multi-user application, you ll also need a way to
process this data so that it is usable. Application_BeginRequest and
Application_EndRequest give you the big picture of how long request processing
took. You could write macros in Excel to import the log file and process the
data once you find a BeginRequest, you ll want to find the matching
EndRequest for the same thread id. You might import the data into SQL Server in
order to process it. You ll want to match up the Start and End log entries from
each of the various page steps to calculate how long each step took. Then match
up each of the steps to a request, and group all the data by request, so that
you can walk through the steps of an individual request.
You may run into some nuances when processing the data.
Sometimes the Page s OnUnload method is called twice (this appears to be a bug).
Also, if you make use of Response.Redirect or Response.End, you might notice
that some of the steps you were expecting are skipped. Server.Transfer or your own
custom HTTP handlers may also call for special processing. Finally, your
processing code will need to pull together data elements from different steps for
it to make sense. For example, if you want to summarize data by
BeginRequest/EndRequest, you ll need to find the user, requested URL, and
session ids from other events that took place after BeginRequest (because not
all values were known yet).
You can use this information to calculate average response
time for each page, etc. Or, for requests that took too long you can look at
each step to see where the issue is. The processed data can be used for usage
statistics (which users are using which parts of the application?, etc.), for
performance statistics (did that new query optimization make a difference?), or
to follow the steps a user apparently took when running into some elusive bug
that was reported.
Last spring I deployed a mission-critical trading
application. Unfortunately, performance degraded considerably after it was in
production for a short period of time. I was at a loss as to what the problem
could be, but I had to get it resolved quickly. I began by adding some tracing
to check database performance. The problem wasn t there, so I added some
tracing to the page lifecycle, which also appeared to be processing quickly
at least from OnInit to OnUnload. I had to examine everything that might be
happening from the split second that the user clicked a button in the browser
to the second that the new page finished rendering in the browser. In the end,
several tools came into play. I used a third-party product (HttpWatch) on the
user s machine to see if there were any delays on the client before and after
the request to the Web server. I enabled detailed logging in IIS and correlated
what IIS thought the request processing time was with ASP.NET and HttpWatch.
Lifecycle tracing ended up pointing to a performance hit
being taken at Application_AuthenticateRequest in the authentication stage. I
used netcap and netmon to capture all network traffic on the Web server and
client. I found that the delay correlated with the Web server communicating
with a domain controller (DC) at a remote location. Apparently, it was
authenticating the user against the remote DC when it should have been
communicating with a DC in the same rack. An active directory configuration
issue was identified and resolved. Understanding and logging the complete
ASP.NET lifecycle was the key to pinpointing and resolving this issue.
There are even more lifecycle steps in ASP.NET 2.0 that
you can use for logging. The Page object adds methods such as OnLoadComplete,
and the HttpApplication object expands, adding Pre- and Post- events such as
PreAuthenticateRequest and PostAuthenticateRequest instead of only
AuthenticateRequest. The TracingPage class from this article should get you
started. If you are using partial pages (the default), simply add the
Inherits= TracingPage attribute to the @Page directive in the aspx file. Also,
the documentation specifically includes instructions for using
System.Diagnostics tracing and adding it to the web.config file as presented
here.
The sample code for
this article is available for download.
Adwait Ullal is an
independent .NET Architect in Saratoga, CA. He has architected systems for such
Fortune 100 companies as American Express, JP Morgan Chase, and Visa. Contact
Adwait at mailto:adwait.ullal@gmail.com
with questions or comments.
Begin Listing One
public class TracingPage : System.Web.UI.Page
{
public static System.Diagnostics.TraceSwitch
lifecycleSwitch = new
System.Diagnostics.TraceSwitch(
"Lifecycle",
"Lifecycle Log");
public const string
START_MARKER = "Start";
public const string
END_MARKER = "End";
public string currentPage
= null;
public string currentUser
= null;
public string
currentRequest = null;
public int
currentThreadId = 0;
protected void
DoStandardTrace(string marker)
{
// Use the Stack Frame
to determine which method is
// requesting logging
so that we can log the method
// name without passing
it in.
System.Diagnostics.StackFrame callingFrame =
new
System.Diagnostics.StackTrace().GetFrame(1);
System.Diagnostics.Trace.WriteLineIf(
lifecycleSwitch.TraceInfo,
string.Format(
"{0:yyyy-MM-dd
HH:mm:ss.ffff}\t{1}\t{2}\t{3}\t{4}\
t{5}\t{6}\t{7}\t{8}",
System.DateTime.Now, // time
stamp
"Page", // category
currentPage, // Web Form class
callingFrame.GetMethod().Name, // step (OnInit, etc.)
marker, // start, end, etc
currentUser, // user name
currentThreadId, // current thread id
currentRequest, // URL requested
Session.SessionID //
session id
));
}
override protected void
OnInit(System.EventArgs e)
{
currentPage =
this.GetType().Name;
currentUser =
User.Identity.Name;
currentRequest =
Request.Url.PathAndQuery;
currentThreadId =
System.AppDomain.GetCurrentThreadId();
DoStandardTrace(START_MARKER);
base.OnInit(e);
DoStandardTrace(END_MARKER);
}
override protected void
LoadViewState(object savedState)
{
DoStandardTrace(START_MARKER);
base.LoadViewState(savedState);
DoStandardTrace(END_MARKER);
}
override protected void
OnLoad(System.EventArgs e)
{
DoStandardTrace(START_MARKER);
base.OnLoad(e);
DoStandardTrace(END_MARKER);
}
override protected void
RaisePostBackEvent(
System.Web.UI.IPostBackEventHandler sourceControl,
string eventArgument)
{
DoStandardTrace(START_MARKER);
base.RaisePostBackEvent(sourceControl, eventArgument);
DoStandardTrace(END_MARKER);
}
override protected void
OnPreRender(System.EventArgs e)
{
DoStandardTrace(START_MARKER);
base.OnPreRender(e);
DoStandardTrace(END_MARKER);
}
override protected object
SaveViewState()
{
DoStandardTrace(START_MARKER);
object returnObject =
base.SaveViewState();
DoStandardTrace(END_MARKER);
return returnObject;
}
override protected void
Render(
System.Web.UI.HtmlTextWriter
writer)
{
DoStandardTrace(START_MARKER);
base.Render(writer);
DoStandardTrace(END_MARKER);
}
override public void
Dispose()
{
DoStandardTrace(START_MARKER);
base.Dispose();
DoStandardTrace(END_MARKER);
}
override protected void
OnUnload(System.EventArgs e)
{
DoStandardTrace(START_MARKER);
base.OnUnload(e);
DoStandardTrace(END_MARKER);
}
}//class TracingPage
End Listing One
Begin Listing Two
...
public static System.Diagnostics.TraceSwitch
lifecycleSwitch = new
System.Diagnostics.TraceSwitch(
"Lifecycle",
"Lifecycle Log");
protected void DoStandardTrace(bool request, bool session)
{
// Use the Stack Frame to
determine which method is
// requesting logging so
that we can log the method
// name without passing
it in.
System.Diagnostics.StackFrame callingFrame =
new
System.Diagnostics.StackTrace().GetFrame(1);
System.Diagnostics.Trace.WriteLineIf(
lifecycleSwitch.TraceInfo,
string.Format(
"{0:yyyy-MM-dd
HH:mm:ss.ffff}\t{1}\t{2}\t{3}\t{4}\
t{5}\t{6}\t{7}\t{8}",
System.DateTime.Now, // time stamp
"Global", // category
this.GetType().Name, // Web Form class
callingFrame.GetMethod().Name, // step (OnInit, etc.)
"Event", // start, end, etc
(request &&
(User != null)) ?
User.Identity.Name : null,
// user name
System.AppDomain.GetCurrentThreadId(
), // current thread id
request ?
Request.Url.PathAndQuery : null, // URL requested
session ?
Session.SessionID : null //
session id
));
}
protected void Application_Start(Object sender, EventArgs e)
{
DoStandardTrace(false,
false);
}
protected void Session_Start(Object sender, EventArgs e)
{
DoStandardTrace(false,
true);
}
//
// REQUEST EVENTS
//
protected void Application_BeginRequest(Object sender,
EventArgs e)
{
DoStandardTrace(true,
false);
}
protected void Application_AuthenticateRequest(Object sender,
EventArgs e)
{
DoStandardTrace(true,
false);
}
protected void Application_AuthorizeRequest(Object sender,
EventArgs e)
{
DoStandardTrace(true,
false);
}
...
End Listing Two