ASP.NET Log File - Felix John COLIBRI. |
- abstract : an Asp.Net journaling CLASS for debugging and monitoring Web Developments
- key words : ASP.NET, log, journal, utility, debugging, trace
- software used : Windows XP Home, Delphi 2005
- hardware used : Pentium 2.800 Mhz, 512 M memory, 250 G hard disc
- scope : Delphi 8 , Delphi 2005, Delphi 2006, Windows
- level : Delphi Web Developer
- plan :
1 - Goal of an Asp.Net log file We explain how to create an Asp.Net log file.
Asp.Net is delivered with all kinds of debugging features. Without rejecting all those tools, we are so used to indented log files that we started to implement them as our first step in the Asp.Net world.
One of the reason is that the file log centralizes all debugging text during the lifetime of a user visit: - when the user requests a page, the Web Server starts a new AppDomain (a kind of lightweight thread)
- when the user clicks on a link, this will start another AppDomain
So debugging messages tied to an AppDomain will not survive this execution. The log file will remain on disk, as long as we want. And can be viewed in case of
crash, telling us the whole story which led to this sad happening.
2 - The Asp.Net log file CLASS 2.1 - Writing to a log file Writing to the log file is quite easy:
- we create (or open) a FileStream
- we create a StreamWriter to write strings
- then we call StreamWriter.WriteLine( xxx );
Here is a code snippet:
var my_c_stream: System.Io.FileStream;
my_c_stream_writer: StreamWriter; ...
my_c_stream:= FileStream.Create('c:\log\my_log.txt',
FileMode.Open, FileAccess.Write);
my_c_stream_writer:= StreamWriter.Create(l_c_stream);
my_c_stream_writer.BaseStream.Seek(0, SeekOrigin.End);
my_c_stream_writer.WriteLine('hello World');
my_c_stream_writer.Close; my_c_stream.Close; |
2.2 - The UNIT The CLASS definition is: c_log= Class
m_full_file_name: System.String;
Constructor create_log(p_full_file_name: System.String);
procedure write_to_log(p_text: System.String);
end; // c_log | Here is the constructor:
Constructor c_log.create_log(p_full_file_name: System.String);
var l_c_stream: System.Io.FileStream;
begin Inherited Create;
m_full_file_name:= p_full_file_name; // -- create an empty file
if not &File.Exists(p_full_file_name)
then begin
l_c_stream:= FileStream.Create(p_full_file_name,
FileMode.Create, FileAccess.Write);
l_c_stream.Close(); end;
end; // create_log | with the writing method
procedure c_log.write_to_log(p_text: System.String);
var l_c_stream: System.Io.FileStream;
l_c_stream_writer: StreamWriter;
l_date_time: System.DateTime;
l_date: System.String; begin
l_c_stream:= FileStream.Create(m_full_file_name,
FileMode.Open, FileAccess.Write);
l_c_stream_writer:= StreamWriter.Create(l_c_stream);
l_c_stream_writer.BaseStream.Seek(0, SeekOrigin.End);
l_date_time:= System.DateTime.Now;
l_date:= System.String.Format(' {0} ',
[l_date_time.ToString('g')]);
l_c_stream_writer.WriteLine(l_date+ ' '+ p_text);
l_c_stream_writer.Close; l_c_stream.Close;
end; // write_to_log |
Usually the project using the log declares a c_log variable, and uses this
variable to create and write to the log. In the Asp.Net case, since each new page lives in a separate AppDomain, this is not convenient. So we use a self sufficient log: - on page request creates, and uses the log
- the other pages simply write to the log
This is achieved by using a global variable hidden in the IMPLEMENTATION of the log UNIT, and a common fixed path and file name. Here is the creation global procedure:
const k_default_name= 'log.txt';
var _m_c_log: c_log= Nil;
_m_log_name: System.String= '';
_do_log: Boolean= True;
_m_indentation: Integer= 0;
procedure create_asp_net_log(p_full_file_name: System.String);
begin if _m_log_name= ''
then _m_log_name:= p_full_file_name;
if not _do_log
then Exit;
if _m_c_log<> Nil
then Exit;
if p_full_file_name= ''
then p_full_file_name:= k_default_name;
_m_c_log:= c_log.create_log(p_full_file_name);
end; // create_asp_net_log | and the log writing procedure is:
procedure write_to_asp_net_log(p_text: System.String);
begin if not _do_log
then Exit;
if _m_c_log= Nil
then create_asp_net_log(_m_log_name);
// -- handle the ">", "<" parts
if Pos('<', p_text)> 0
then Dec(_m_indentation);
p_text:= _f_spaces(_m_indentation)+ p_text;
_m_c_log.write_to_log(p_text);
if Pos('>', p_text)> 0
then Inc(_m_indentation);
end; // write_to_asp_net_log | where the '>' and '<' are a way to indent / unindent the log output
2.3 - Where should we place the Log File ? The log file is saved in a separate path, in order to be easily located in the project. If our project is located in p_xxx\, the we decided to place the log file in p_xxx\_log\asp_log.txt
Do NOT place the log in the p_xxx\BIN directory. For a complete explanation, see the the BIN changes story below.
2.4 - When should the log be created ?
Many many papers can be found via Google about "the life of an Asp.Net page". Oh yes. Could even be called "The secret life of an Asp.Net page", or maybe "Everything you ever wanted to know about the life of an Asp.Net page".
The sad story is that the mere publishing of those pages demonstrate that following the flow of control during the rendering of a page is not obvious. We naturally wanted to create the log in the first event which requires a log
entry. So we first placed the creation in the main .Aspx tWebForm1.Page_Load or tWebForm1.OnInit events. We later had to investigate Global.Asax, and found out that events in this file were called before the main page. The
documentation confirms this in the "HTML Pipeline" descriptions. So we placed the creation in the Global.Asax create event.
2.5 - log example Here is a simple example of using the log file. First start and name a new
ASP.NET application: | start Delphi | |
select "file | new | asp.net application" | |
Delphi presents the path / file / server dialog | | type the requested information. In our case the project will be located in:
c:\programs\us\web\asp_net\p_asp_net_log_file so the dialog will look like: |
| then, in the top right tree project manager, right click the WebForm1.aspx line, and rename the file name "a_asp_net_log_file.aspx"
| | the project folder will look like this:
| | compile and run to make sure that everyting in correct |
| Delphi starts Cassini, loads Internet Explorer which displays our application |
We have gathered all our helper files in a separate folder which is reachable from all our projects. To compile our log unit, we tell Delphi where the folder is located
We then prepare the path for the log | open a Windows Explorer, and create the log path. For instance:
c:\programs\us\web\asp_net\p_asp_net_log_file\_asp_log |
Now we create the log in Global.Asax: |
then, in the top right tree project manager, click Global.Asax: | |
Delphi opens the file in the Editor | | add the unit in the USES after the implementation |
| add the log creation in the Global CONSTRUCTOR (there is a "// TODO: Add any constructor code after InitializeComponent call" that we removed)
constructor TGlobal.Create;
begin inherited; InitializeComponent;
create_asp_net_log('c:\programs\us\web\asp_net\'
+ 'p_asp_net_log_file\_asp_log\the_log.txt'); write_to_asp_net_log('======');
write_to_asp_net_log('log_started'); end; // TGlobal.Create |
| | compile and run | |
click on the the_log.txt file | | NotePad is started and it displays the log content |
Here is another example, with a ButtonClick and display of all predefined events:
3 - Mini Howto To use our CLASS: - add the u_c_asp_net_log unit to your Asp.Net project. Also add the dependent units (all in the zip below)
- add the USES clause to all the .PAS which will either create or write to the log file
- create the log destination directory. If you want a new log, remove any existing log.txt
- add the log creation in an event called before any log write
- add all the log write in your .PAS code
4 - Improvements 4.1 - The log in BIN saga
Usually, as other Delphi 6 projects available on this site demonstrate, we place the .EXE, of a project in a separate directory. This directory also houses the DCU'x, and a log\ sub directory
BIN is build by default by Asp.Net. So this is fixed. Then we decided to nest a log\ sub directory in BIN We then encountered random "Thread Timeout" exceptions, which could not be explained.
Out of despair we went on Google searching for +"asp.net"+thread timeout". After a couple of trials, we hit the following page:
Strange thread timeout behavior again
This was my lucky day since I somehow browsed this 54 K long search and anguished thread, instead of clicking away after one or two pages, to finally stumble on the following paragraph (near the end, of course - hilite was added):
Hi James, I'm sorry for keeping you waiting for so long time. We've try debuging on
this issue, and finally found that the problem is caused by the log file we put in the "log" subfolder under the "bin" sub folder of the web application's root folder. In ASP.NET the asp.net runtime will monitor the
"bin" sub folder of the web app's root folder and the web.config or other system configuration files. If any of them has been modified, the certain web application's AppDomain will reboot. As for the situation in our issue,
the webmethod writes a log file in the "log" folder which is under the "bin" sub directory of the application's root folder, that cause the ASP.NET runtime detect the changes in the "bin" directory and then recycle
the application's AppDomain. I've tested changed the log file's destination folder such as under the "log" folder directly under the application's root folder. Such as : strLogFileFullFilename = Server.MapPath("Log") +
Path.DirectorySeparatorChar + Format(Now(), "yyyyMMddHHmmss") + ".TXT" After that, the webmethod will be executed correctly as long as you set the "TimeOut" attribute in the config file to proper value.
Please check out the preceding result. If you have any questions on it, please feel free to let me know. Steven Cheng Microsoft Online Support Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no rights.) ===== Re: Strange thread timeout behavior again Hi! Steven, Thanks so much you found out the reason finally! I've modified my original
application and proved that the problem is solved. | I had read about this automatic recompilation of .ASPX pages, whith all this
shadow copying business and all. But I was miles away from the diagnostic when the exceptions started hitting the application. I would certainly question Mr CHENG's postscript "Get Secure with
www.microsoft.com". Must be some kind of Redmond private joke, I guess. But he did save my day, so, thank you very much, Mr CHENG.
In conclusion: NEVER place any custom file which is updated in the BIN directory.
4.2 - Programming Notes - Much could be done to improve this log. We could add a display on a web page Label, concatenate the logs etc.
- I'm still not very efficient with the C# notation:
- System.Io.File.Exists instead of the FileExists function
- & File to avoid the reserved FILE
- FileAccess.Write because Java (and therefore C#) does not have decent
constants.
But whining and crying is no solution: either stick with Delphi 6, which is fine, or for Asp.Net development, bite the bullet. In this area, the best
book still remains the Xavier PACHECO's "Delphi 8 for .Net", which is better than most of the books on Delphi, even the recent Delphi 2006 books where nearly all authors try to cater to both Vcl.Net and Windows Forms
developments. Fatal error. - Using this old log file will certainly look outdated in our time and age of steppers, symbolic debuggers and event logs, but, after over 20 years of
developments, I personally have never found a more efficient tool. I used one with the Apple UCSD Pascal, then for 6502 assembler, Prolog, Postscript, 8086 assembler, Delphi, of course. And the first thing for .Net I developed
was, of all things, the log file.
5 - Download the Sources Here are the source code files: The .ZIP file(s) contain: - the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any
other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP - are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate:
the more traffic and Google references we get, the more articles we will write.
6 - The author Felix John COLIBRI works at the Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring), Delphi Consulting and Delph
training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions. |