Thursday, December 08, 2011

In the past weeks I have been working on an ASP.Net web site application. I tried to use my ILog and LogToFile initially. However it failed miserably. ASP.Net web pages are based server and client architecture pattern. The IIS Server does not know when the web page on client side will call back. This means that I cannot keep the log file opened forever. I tried to close the log file for each message log.  I found that I could not keep the same log file since my log file name is in a patten like mylog_yyyymmdd_hhmm.log. The sever side will open another log file since the file name is kept on the server side. I could remove minutes out, but I don't like it. Therefore my original library for logging messages is not good for web site applications.

Further investigating the sever side class codes, I found that on the IIS server, writing to a text file is not allowed. I tried to write to a folder out of inetpub folder, such as C:\Log, still it is not writable.  I may be able to figure out by change IIS application pool permission, but I don't that's may cause security holes.

I had to take different strategy to log messages. I changed my log helper class to write messages to a SQL server database. This actually works fine. I realize that there are some drawbacks about this. As I mentioned in the above analysis, I cannot keep database connection open all the time. So I have to close connection for each log.  This results a very slow logging process since the database connection has to be opened and closed constantly. But it works fine.  In order to improve performance, I used delegate for log message so that if log setting is off, no db connection operation at all.  The second good practice is to use a pair methods of BeginDBLog() and EndDBLog() to allow database connection open for several log processes.

Here are some class data members in DBLog class:

# region Data members
private static DBLog _log;
private IDbService _db;
private SQLQuery _sqlQuery;
private DBLogConfig _config;
# endregion

Since the instance of DBLog is singleton, I use a method to control the instance creation. The pair methods are used for keep the instance of IDbService alive:

public static DBLog GetInstance(DBLogConfig logConfig)
{
  if (_log == null)
  {
    _log = new DBLog(logConfig);
  }
  return _log;
}

#region CTOR
private DBLog(DBLogConfig config)
{
  _config = config;
  _sqlQuery = new SQLQuery(_config.StoreProcedureName);
}
#endregion

public void BeginDBLog()
{
  if (_db == null)
  {
    if (_config != null)
    {
      _db = new DBService(_config.DBConnection,
        _config.SQLOrOracle ?
        DBService.DBType.MicrosoftSQLServer :
        DBService.DBType.OracleServer);
    }
  }
}

public void EndDBLog()
{
  if ( _db != null )
  {
    _db.Close();
    _db = null;
  }
}

Here are methods for logging messages:

public void LogMessage(string flags, LogType logType, string msg)
{
  LogMessage(flags, logType, delegate() { return msg; });
}

public void LogMessage(string flags, LogType logType,
    LogHelper.GetMessageDelegate getMessageDelegate)
{
  if (IsLogSet(flags, logType) && getMessageDelegate != null)
  {
    bool close = false;
    if (_db == null)
    {
      BeginDBLog();
      close = true;
    }
    string msg = getMessageDelegate();
    if (!string.IsNullOrEmpty(msg))
    {
      if (_sqlQuery.Parameters.Count > 0)
      {
        _sqlQuery.Parameters.Clear();
      }
      bool sqlOrOracle = _config.SQLOrOracle;
      DateTime dt = DateTime.Now;
      if (msg.Length > MAX_LENGTH)
      {
        msg = msg.Substring(0, MAX_LENGTH - 1);
      }

      _sqlQuery.Parameters.Add(SQLQuery.GetParameter(sqlOrOracle,
        _config.ParameterDate, dt));
      _sqlQuery.Parameters.Add(SQLQuery.GetParameter(sqlOrOracle,
       _config.ParameterLogType, logType.ToString()));
      _sqlQuery.Parameters.Add(SQLQuery.GetParameter(sqlOrOracle,
        _config.ParameterApplication, _config.ApplicationName));
      _sqlQuery.Parameters.Add(SQLQuery.GetParameter(sqlOrOracle,
        _config.ParameterMessages, msg));
      _sqlQuery.Parameters.Add(SQLQuery.GetParameter(sqlOrOracle,
        _config.ParameterSiteID, _config.SiteID));

      int result = _db.ExecuteSqlNoneQueryCommand(_sqlQuery, CommandType.Text);
    }
    if (close)
    {
      EndDBLog();
    }
  }
}

Notes: the above codes depend on my DotNetCommonLibrary and a class of DBLogConfig for database configuration.  The configuration class is a simple class with property getters and setters for database type, stored procedure name, and parameter names, as well as related configuration values.

0 comments: