/// <summary> /// Logs message to file. Depending on Alerts configuration, this may also result in sending an SMTP message. /// </summary> /// <param name="message">Message text to be logged (and may be sent)</param> /// <param name="logLevel">Defines what the minimum level of logging should be configured (see LogLevel property) in order to log this message and AlertsLogLevel property to mail this message. This allows changing the level of details logged without changing and recompiling the application.Default=ExceptionsOnly</param> /// <param name="localLogLevel">Redefines global log level for this particular call</param> /// <param name="insertBlankLines">How you want the blank lines being inserted</param> public static void LogMessage(string message, LogLevels logLevel,int localLogLevel,enuBlankLines insertBlankLines){ LogMessage(message,null,insertBlankLines,logLevel,localLogLevel,""); }
/// <summary> /// Logs message to file. Depending on Alerts configuration, this may also result in sending an SMTP message. /// </summary> /// <param name="message">Message text to be logged (and may be sent)</param> /// <param name="logFile">Log file. If omitted, LogFilePathName, AppSettings["LogFile"] or Log.Log files will be used</param> /// <param name="insertBlankLines">Inserts empty lines before and after the message when writing to the log.</param> /// <param name="logLevel">Defines what the minimum level of logging should be configured (see LogLevel property) in order to log this message and AlertsLogLevel property to mail this message. This allows changing the level of details logged without changing and recompiling the application.Default=ExceptionsOnly</param> /// <param name="localLogLevel">Redefines global log level for this particular call</param> /// <param name="messageID">Unique ID of the message. It is required so that the message does not cause Alert more than once in a period configured (AlertResetIntervalMinutes property)</param> public static void LogMessage(string message, string logFile, enuBlankLines insertBlankLines, LogLevels logLevel, int localLogLevel, string messageID){ #region Immediately check if we are logging before collecting any info if(_blnUseLowestLogLevelIfLocalLevelConfigured && localLogLevel>0){//If it is 0 it means it is not set and -1 is passed to this proc when the param is omitted if(((int)logLevel>_intLogLevel||(int)logLevel>localLogLevel) && (int)logLevel>_intAlertsLogLevel) return; } else{ if((int)logLevel>_intLogLevel && (int)logLevel>localLogLevel && (int)logLevel>_intAlertsLogLevel) return; } #endregion string strAction="Start"; string strMessageToThrowToCaller=""; System.Text.StringBuilder sb=new System.Text.StringBuilder(TimeStamp); try{ string strMessageFallBack=""; #region Get the calling method System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(); System.Diagnostics.StackFrame stackFrame; System.Reflection.MethodBase methodBase=null; for(int i=1;i<10;i++){//The trace may be full of our internal stuff which we don't want stackFrame = stackTrace.GetFrame(i); methodBase = stackFrame.GetMethod(); if(methodBase.DeclaringType.Name!="Logging")break; } #endregion #region Create the message's meta tag. It is needed for both - Log and Alert strAction="MetaTag"; sb.Append("\t"); sb.Append(System.Threading.Thread.CurrentThread.GetHashCode().ToString()); sb.Append(" ");sb.Append(System.Threading.Thread.CurrentThread.Name); sb.Append("\t");sb.Append(((int)logLevel));sb.Append("/");sb.Append(localLogLevel);sb.Append("/");sb.Append(_intLogLevel);sb.Append("/");sb.Append(_intAlertsLogLevel); sb.Append("\t");sb.Append(System.Environment.MachineName); sb.Append("\t");sb.Append(System.Environment.UserName); sb.Append("\t");sb.Append(methodBase.DeclaringType.Name);sb.Append(".");sb.Append(methodBase.Name);sb.Append(": "); sb.Append(message); methodBase=null;stackFrame=null;stackTrace=null; #endregion #region Log To File //NOT EVERY USER CAN WRITE TO EVENT LOG, SO WE KEEP IT AS A FALL-BACK //WHEN LOG FILE IS UNACCESSIBLE WE ATTEMPT TO LOG TO BACKUP LOG AND EVENT LOG. if((_blnUseLowestLogLevelIfLocalLevelConfigured && ((int)logLevel<=_intLogLevel && ((int)logLevel<=localLogLevel || localLogLevel<=0))) || !_blnUseLowestLogLevelIfLocalLevelConfigured && ((int)logLevel<=_intLogLevel || (int)logLevel<=localLogLevel)){ //Then we actually worry about the file we are writing into logFile=GetLogFilePathName(logFile);//That exuses every caller from calling GetLogFilePathName(logFile), especially if this method figures out that we are not writing this call if(! _blnLogFileSizeExceeded){//That refers to the globally-configured file unless there happened to be a local file that tripped that flag #region Check File Size strAction="File Size"; try { // Check size of the log file and do not allow more than 2 Mb System.IO.FileInfo fi = new System.IO.FileInfo(logFile); if(fi.Exists){ if(fi.Length > _lngLogFileSizeLimitBytes){ sb=new System.Text.StringBuilder("\r\n\r\nThe log file size has exceeded "); sb.Append(_srtLogFileSizeLimitMb); sb.Append("Mb limit, and no logging will be performed to this file anymore. Fix the cause of the excessive logging or reconfigure the limit."); _blnLogFileSizeExceeded = true; #region fix the log level to a level of sending an alert, so that the user is notified about not writing to the log if((int)logLevel > _intAlertsLogLevel){ logLevel=(LogLevels)_intAlertsLogLevel; } #endregion } } }catch{}//do not abuse resources. #endregion #region Insert blank lines as requested switch(insertBlankLines){ case enuBlankLines.None: break; case enuBlankLines.DoubleTopAndBottom://THat what was in previous implementation sb.Insert(0,Environment.NewLine+Environment.NewLine);sb.AppendLine(); break; case enuBlankLines.TopAndBottom: sb.Insert(0,Environment.NewLine);sb.AppendLine(); break; case enuBlankLines.Top: sb.Insert(0,Environment.NewLine); break; case enuBlankLines.Bottom: sb.AppendLine(); break; default: throw new NotImplementedException("insertBlankLines="+insertBlankLines.ToString()+" is not implemented"); } #endregion strAction="Log to " + logFile; strMessageFallBack=WriteLineToFileWithExceptionReturned(logFile,sb.ToString()); if(strMessageFallBack!=""){ #region fix the log level to a level of sending an alert, so that the user is notified about failure of writing to the log if((int)logLevel > _intAlertsLogLevel){ logLevel=(LogLevels)_intAlertsLogLevel; } #endregion #region Fall back to other media //We need to alter the message itself because now it also goes to Email alert sb.Insert(0,TimeStamp + "\tCould not log message to [" + logFile+"] file. \r\nReason: ["+strMessageFallBack+"]\r\nMessage attempted to log:["); sb.Append("]"); strAction="Log to fallback file"; WriteLineToFileWithExceptionReturned(LogFileFallback,"\r\n"+sb.ToString()); //try to write to EventLog (will need permission to create a registry key, which may be haemorrhoid //To enable writing to event log, fire regedt32.exe tool and navigate to // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog key. Select Permissions in Security menu (on top) // and add ASPNET (for website) or another account you are using for service, etc. strAction="Log to EventLog"; try{ //System.Security.Policy.ApplicationSecurityInfo asi = new System.Security.Policy.ApplicationSecurityInfo(AppDomain.CurrentDomain.ActivationContext); //System.Diagnostics.EventLog.WriteEntry(asi.ApplicationId.Name,strMessageFallBack,System.Diagnostics.EventLogEntryType.Error); System.Diagnostics.EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName,strMessageFallBack,System.Diagnostics.EventLogEntryType.Error); } catch(Exception exx){//report the problem to fallback file strAction="Log EventLog failure to LogFileFallback"; sb.Insert(0,TimeStamp+"\tCould not log message to EventLog when logging to the primry log file failed\r\nReason: ["+exx.Message+"], User="******"\r\nMessage attempted to log:["); sb.Append("]"); string strFailure=WriteLineToFileWithExceptionReturned(LogFileFallback,"\r\n"+sb.ToString()); if(strFailure!=""){ strAction="Create message to throw to caller"; strMessageToThrowToCaller= "Failed to write to fallback log file ["+LogFileFallback+"] Error=["+strFailure +"\r\nAlso failed to write to either ["+logFile+"] OR Windows Event Log. Error:{"+strMessageFallBack +"}.\r\nTo enable writing to Event Log, start regedit.exe (regedt32.exe in older Windows) tool and navigate to " +@"//HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog key. Select Permissions in Security menu (on top) or in context menu " +"and add ASPNET (for website) or another account you are using for service, etc.\r\n" +"This account also needs Write permission for directory configured for the log file, and ideally for directory where this dll resides, for fail-back logging."; } } #endregion }//if(strMessageFallBack!="") } } #endregion #region ALERTS if((int)logLevel <= _intAlertsLogLevel){//We do not bother with local log levels when sending alerts. Local log levels are for active troubleshooting. bool blnSend=false; #region Figure out message key string strKey=""; strAction="Figure out message key"; if(messageID.Length > 0){ strKey=messageID; } else{ if(_blnAlertsUseMessageText){ if(message.Length > MESSAGE_RECOGNITION_LEN){ strKey=message.Substring(0,MESSAGE_RECOGNITION_LEN); } else{strKey=message;} } } #endregion #region Update Journal with new key strAction="Update Journal with new key"; if (strKey.Length > 0) {//we can surpress repetitive alerts from this piece of code if(_htbAlerts.ContainsKey(strKey)){ DateTime dtm = (DateTime)_htbAlerts[strKey]; if(dtm.AddMinutes(_dblAlertResetIntervalMinutes)<DateTime.Now){ //Update the key _htbAlerts[strKey]=DateTime.Now; blnSend=true; } } else{ _htbAlerts.Add(strKey,DateTime.Now); blnSend=true; } } #endregion #region Ensure that the journal does not grow too big if(_htbAlerts.Count>_intAlertsJournalSize){ DateTime dtmMinDate = DateTime.MaxValue; string strMinDateKey = ""; DateTime dtmCutOff = DateTime.Now.AddMinutes(-_dblAlertResetIntervalMinutes); string strKeysToRemove=""; strAction="List Journal messages to remove"; System.Collections.IDictionaryEnumerator enu = _htbAlerts.GetEnumerator(); try{ while(enu.MoveNext()){ DateTime dtmCurr = (DateTime)enu.Value; string strCurr = (string)enu.Key; if(dtmCurr<dtmCutOff){ strKeysToRemove+="|"+strCurr; } if(dtmCurr<dtmMinDate){ dtmMinDate=dtmCurr; strMinDateKey=strCurr; } } } catch{}//concurrent process may invalidate the enumerator at any time. Let it be so. enu=null; strAction="Remove old messages from Journal"; if(strKeysToRemove.Length>0){ char[] chaSeparator = {'|'}; string[] strKeysBeRemoved = strKeysToRemove.Remove(0,1).Split(chaSeparator); for(int i=0;i<strKeysBeRemoved.Length;i++){ try{ _htbAlerts.Remove(strKeysBeRemoved[i]); } catch{} } } else{//There are no obsolete keys. Remove the oldest one try{ _htbAlerts.Remove(strMinDateKey); } catch{} } } #endregion #region Send the Alert if(blnSend){ strAction="Send SMTP message"; string strSmtpErrors; bool blnSmtpSent=SendSMTP(_strAlertsRecipients,"EXCEPTION OCCURED" ,System.AppDomain.CurrentDomain.FriendlyName + " at " + System.Environment.MachineName + ":\r\n\r\n" +sb.ToString()+ "\r\nSee log file "+ logFile+" for details.\r\n\r\n\r\n\r\n" ,MailPriority.High, out strSmtpErrors); if (!blnSmtpSent ||strSmtpErrors!="") { if(!_blnLogFileSizeExceeded){ strAction="Attempt writing SMTP failure to "+LogFilePathName; if(blnSmtpSent){ // WriteLineToFile(LogFilePathName,"\r\n"+TimeStamp+"\tThe Alert had been sent, but Primary SMTP servers failed: "+ WriteLineToFileWithExceptionReturned(LogFilePathName,"\r\n"+TimeStamp+"\tThe Alert had been sent, but Primary SMTP servers failed: "+ strSmtpErrors+"\r\n");//We do not care if the log file is accesible or not. We worried about this before //So we can warn about this problem by email strAction="Attempt sending SMTP failure by email"; SendSMTP(_strAlertsRecipients,"EXCEPTION OCCURED" ,System.AppDomain.CurrentDomain.FriendlyName + " at " + System.Environment.MachineName +":\r\n\r\nCan't send messages using primary SMTP servers:\r\n"+strSmtpErrors +"\r\nSee log file "+ logFile+" for details.\r\n\r\n\r\n\r\n" ,MailPriority.High, out strSmtpErrors); } else{ // WriteLineToFile(LogFilePathName,"\r\n"+TimeStamp+"\tCOULD NOT SEND ALERT: "+ WriteLineToFileWithExceptionReturned(LogFilePathName,"\r\n"+TimeStamp+"\tCOULD NOT SEND ALERT: "+ strSmtpErrors+"\r\n");//We do not care if the log file is accesible or not. We worried about this before } } } } #endregion } #endregion } catch(Exception e){//here we catch any unexpected exception sb=new System.Text.StringBuilder(TimeStamp); sb.Append("\tUNEXPECTED ERROR.\r\nAction=["); sb.Append(strAction);sb.Append("]\r\nError=[");sb.Append(e.Message);sb.Append("], User="******"\rLocationce=[");sb.Append(e.Source);sb.Append("]\r\nStack Trace=[");sb.Append(e.StackTrace);sb.Append("]"); // WriteLineToFile("FallBack.log",strMessage); WriteLineToFileWithExceptionReturned(LogFileFallback,sb.ToString()); } if(strMessageToThrowToCaller!="")throw new Exception(strMessageToThrowToCaller); #region Show Pop-up desktop form if asked and warranted if(_blnInteractive && (int)logLevel<3){//Warnings, Errors and Fatal frmError objErrorForm = new frmError(); objErrorForm.txtError.Text = sb.ToString(); objErrorForm.butViewLog.Tag=_strLogFilePathName; objErrorForm.ShowDialog(); } #endregion }