menu
  Home  ==>  papers  ==>  web  ==>  asp_net  ==>  asp_net_log_file   

ASP.NET Log File - Felix John COLIBRI.


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_streamSystem.Io.FileStream;
    my_c_stream_writerStreamWriter;
  ...
  my_c_stream:= FileStream.Create('c:\log\my_log.txt',
    FileMode.OpenFileAccess.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_logClass
         m_full_file_nameSystem.String;
         Constructor create_log(p_full_file_nameSystem.String);
         procedure write_to_log(p_textSystem.String);
       end// c_log

Here is the constructor:

Constructor c_log.create_log(p_full_file_nameSystem.String);
  var l_c_streamSystem.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.CreateFileAccess.Write);
          l_c_stream.Close();
        end;
  end// create_log

with the writing method

procedure c_log.write_to_log(p_textSystem.String);
  var l_c_streamSystem.Io.FileStream;
      l_c_stream_writerStreamWriter;
      l_date_timeSystem.DateTime;
      l_dateSystem.String;
  begin
    l_c_stream:= FileStream.Create(m_full_file_name,
      FileMode.OpenFileAccess.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_logc_logNil;
    _m_log_nameSystem.String'';
    _do_logBooleanTrue;
    _m_indentationInteger= 0;

procedure create_asp_net_log(p_full_file_nameSystem.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_textSystem.String);
  begin
    if not _do_log
      then Exit;
    if _m_c_logNil
      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"

asp net new project

   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:

asp net path and file name

   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"

asp net file name

   the project folder will look like this:

asp net project folder

   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
   select "Project | Options "
   Delphi opens the Options dialog

delphi
options dialog

   select "Directories"
   Delphi opens the directories tabsheet

delphi directories tabsheet

   click the "search path" ellipsis and enter the dot_net_units and dot_net_classes pathes

delphi c# net helpers



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:

log in 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

the log
content



Here is another example, with a ButtonClick and display of all predefined events:

creation and click log




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
    Name :
    E-mail :
    Comments * :
     

  • 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.
Created: mar-06. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
        – delph_asp_net_portal
        – cassini_spy
        – asp_net_log_file
        – viewstate_viewer
        – master_pages
        – asp_net_20_databases
        – asp_net_20_security
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog