public Rotater( string[] args ) { this._argsParser = null; this._globalConfig = new LogrotateConf(); this._filePathConfigSection = new Dictionary<string, LogrotateConf>(); this._status = null; this.Init( args ); }
/// <summary> /// Execute any firstaction commands. The commands are written to a temporary script file, and then this script file /// is executed with c:\windows\cmd. /// </summary> /// <param name="lrc">logrotateconf object</param> /// <param name="path_to_logfile">The path to the logfile, which is passed as a paramenter to the script</param> /// <returns>True if script is executed with no errors, False is there was an error</returns> bool ProcessFirstAction( LogrotateConf lrc ) { if ( lrc.FirstAction == null ) { throw new ArgumentNullException( "lrc.FirstAction" ); } return this.CreateScriptAndExecute( lrc.FirstAction, "" ); }
void ProcessConfileFileSection( string starting_line, StreamReader sr, LogrotateConf lrc ) { // the first part of the line contains the file(s) or folder(s) that will be associated with this section // we need to break this line apart by spaces string split = ""; bool bQuotedPath = false; for ( int i = 0; i < starting_line.Length; i++ ) { switch ( starting_line[i] ) { // if we see the open brace, we are done case '{': i = starting_line.Length; break; // if we see a ", then this is either starting or ending a file path with spaces case '\"': if ( bQuotedPath == false ) { bQuotedPath = true; } else { bQuotedPath = false; } split += starting_line[i]; break; case ' ': // we see a space and we are not processing a quote path, so this is a delimeter and treat it as such if ( bQuotedPath == false ) { string newsplit = ""; // remove any invalid characters before adding char[] invalidPathChars = Path.GetInvalidPathChars(); foreach ( char ipc in invalidPathChars ) { for ( int ii = 0; ii < split.Length; ii++ ) { if ( split[ii] != ipc ) { newsplit += split[ii]; } } split = newsplit; newsplit = ""; } lrc.Increment_ProcessCount(); this._filePathConfigSection.Add( split, lrc ); split = ""; } else { split += starting_line[i]; } break; default: split += starting_line[i]; break; } } /* string[] split = starting_line.Split(new char[] { ' ' }); for (int i = 0; i < split.Length-1; i++) { FilePathConfigSection.Add(split[i], lrc); } */ // read until we hit a } and process while ( true ) { string line = sr.ReadLine(); if ( line == null ) { break; } line = line.Trim(); Logging.Log( Strings.ReadLine + " " + line, Logging.LogType.Debug ); // skip blank lines if ( line == "" ) { continue; } // if line begins with #, then it is a comment and can be ignored if ( line[0] == '#' ) { Logging.Log( Strings.Skipping + " " + Strings.Comment, Logging.LogType.Debug ); continue; } if ( line.Contains( "}" ) ) { break; } lrc.Parse( line, this._argsParser.Debug ); } }
void ProcessConfigFile( string m_path_to_file ) { Logging.Log( Strings.ParseConfigFile + " " + m_path_to_file, Logging.LogType.Verbose ); bool bSawASection = false; using ( StreamReader sr = new StreamReader( m_path_to_file ) ) { // read in lines until done while ( true ) { string line = sr.ReadLine(); if ( line == null ) { break; } line = line.Trim(); Logging.Log( Strings.ReadLine + " " + line, Logging.LogType.Debug ); // skip blank lines if ( line == "" ) { continue; } // if line begins with #, then it is a comment and can be ignored if ( line[0] == '#' ) { Logging.Log( Strings.Skipping + " " + Strings.Comment, Logging.LogType.Debug ); continue; } // see if there is a { in the line. If so, this is the beginning of a section // otherwise it may be a global setting if ( line.Contains( "{" ) ) { bSawASection = true; Logging.Log( Strings.Processing + " " + Strings.NewSection, Logging.LogType.Verbose ); // create a new config object taking defaults from Global Config LogrotateConf lrc = new LogrotateConf( this._globalConfig ); this.ProcessConfileFileSection( line, sr, lrc ); } else { if ( bSawASection == false ) { this._globalConfig.Parse( line, this._argsParser.Debug ); } else { Logging.Log( Strings.GlobalOptionsAboveSections, Logging.LogType.Error ); } } } } }
/// <summary> /// Execute any prerotate commands. The commands are written to a temporary script file, and then this script file is /// executed with c:\windows\cmd. /// </summary> /// <param name="lrc">logrotateconf object</param> /// <param name="path_to_logfile">The path to the logfile, which is passed as a paramenter to the script</param> /// <returns>True if script is executed with no errors, False is there was an error</returns> bool PreRotate( LogrotateConf lrc, string path_to_logfile ) { if ( lrc.PreRotate == null ) { throw new ArgumentNullException( "lrc.PreRotate" ); } return this.CreateScriptAndExecute( lrc.PreRotate, path_to_logfile ); }
/// <summary> /// Returns the path that the rotated log should go, depends on the olddir directive /// </summary> /// <param name="lrc">logrotateconf object</param> /// <param name="fi">the rotated log fileinfo object</param> /// <returns>String containing path for the rotated log file</returns> string GetRotatePath( LogrotateConf lrc, FileInfo fi ) { // determine path to put the rotated log file string rotate_path = ""; if ( lrc.OldDir != "" ) { if ( !Directory.Exists( lrc.OldDir ) ) { Directory.CreateDirectory( lrc.OldDir ); } rotate_path = lrc.OldDir + "\\"; } else { rotate_path = Path.GetDirectoryName( fi.FullName ) + "\\"; } return rotate_path; }
/// <summary> /// Determines the name of the rotated log file /// </summary> /// <param name="lrc">logrotateconf object</param> /// <param name="fi">FileInfo object of the rotated log file</param> /// <returns>String containing rotated log file name</returns> string GetRotateName( LogrotateConf lrc, FileInfo fi ) { string rotate_name = ""; if ( lrc.DateExt ) { string time_str = lrc.DateFormat; time_str = time_str.Replace( "%Y", DateTime.Now.Year.ToString() ); time_str = time_str.Replace( "%m", DateTime.Now.Month.ToString( "D2" ) ); time_str = time_str.Replace( "%d", DateTime.Now.Day.ToString( "D2" ) ); time_str = time_str.Replace( "%H", DateTime.Now.Hour.ToString( "D2" ) ); time_str = time_str.Replace( "%M", DateTime.Now.Minute.ToString( "D2" ) ); time_str = time_str.Replace( "%S", DateTime.Now.Second.ToString( "D2" ) ); time_str = time_str.Replace( "%s", ( DateTime.UtcNow - new DateTime( 1970, 1, 1 ) ).TotalSeconds.ToString() ); rotate_name = fi.Name + time_str; } else { rotate_name = fi.Name + "." + lrc.Start; } return rotate_name; }
string GetRotateLogDirectory( string logfilepath, LogrotateConf lrc ) { if ( lrc.OldDir != "" ) { return lrc.OldDir; } return Path.GetDirectoryName( logfilepath ); }
/// <summary> /// Delete a file using the configured method (shred or delete) /// </summary> /// <param name="m_filepath">Path to the file to delete</param> /// <param name="lrc">Currently loaded configuration file</param> void DeleteRotateFile( string m_filepath, LogrotateConf lrc ) { if ( File.Exists( m_filepath ) == false ) { return; } if ( lrc.Shred ) { Logging.Log( Strings.ShreddingFile + " " + m_filepath, Logging.LogType.Verbose ); if ( _argsParser.Debug == false ) { ShredFile sf = new ShredFile( m_filepath ); sf.ShredIt( lrc.ShredCycles, _argsParser.Debug ); } } else { Logging.Log( Strings.DeletingFile + " " + m_filepath, Logging.LogType.Verbose ); if ( _argsParser.Debug == false ) { File.Delete( m_filepath ); } } }
/// <summary> /// Check to see if the logfile specified is eligible for rotation /// </summary> /// <param name="logfilepath">Full path to the log file to check</param> /// <param name="lrc">logrotationconf object</param> /// <returns>True if need to rotate, False if not</returns> bool CheckForRotate( string logfilepath, LogrotateConf lrc ) { if ( this._argsParser.Force ) { Logging.Log( Strings.ForceOptionRotate, Logging.LogType.Verbose ); return true; } bool bDoRotate = false; // first check if file exists. if it doesn't error out unless we are set not to if ( File.Exists( logfilepath ) == false ) { if ( lrc.MissingOK == false ) { Logging.Log( logfilepath + " " + Strings.CouldNotBeFound, Logging.LogType.Error ); return false; } } FileInfo fi = new FileInfo( logfilepath ); //if (logfilepath.Length == 0) if ( fi.Length == 0 ) { if ( lrc.NotIfEmpty || !lrc.IfEmpty ) { Logging.Log( Strings.LogFileEmpty + " - " + Strings.Skipping, Logging.LogType.Verbose ); return false; } } // determine if we need to rotate the file. this can be based on a number of criteria, including size, date, etc. if ( lrc.MinSize != 0 ) { if ( fi.Length < lrc.MinSize ) { Logging.Log( Strings.NoRotateNotGTEMinimumFileSize, Logging.LogType.Verbose ); return false; } } if ( lrc.Size != 0 ) { if ( fi.Length >= lrc.Size ) { Logging.Log( Strings.RotateBasedonFileSize, Logging.LogType.Verbose ); bDoRotate = true; } } else { //if ((lrc.Daily == false) && (lrc.Monthly == false) && (lrc.Yearly == false)) // fix for rotate not working as submitted by Matt Richardson 1/19/2015 if ( ( lrc.Daily == false ) && ( lrc.Weekly == false ) && ( lrc.Monthly == false ) && ( lrc.Yearly == false ) ) { // this is a misconfiguration is we get here Logging.Log( Strings.NoTimestampDirectives, Logging.LogType.Verbose ); } else { // check last date of rotation DateTime lastRotate = this._status.GetRotationDate( logfilepath ); TimeSpan ts = DateTime.Now - lastRotate; if ( lrc.Daily ) { // check to see if lastRotate is more than a day old if ( ts.TotalDays > 1 ) { bDoRotate = true; } } if ( lrc.Weekly ) { // check if total # of days is greater than a week or if the current weekday is less than the weekday of the last rotation if ( ts.TotalDays > 7 ) { bDoRotate = true; } else if ( DateTime.Now.DayOfWeek < lastRotate.DayOfWeek ) { bDoRotate = true; } } if ( lrc.Monthly ) { // check if the month is different if ( ( lastRotate.Year != DateTime.Now.Year ) || ( ( lastRotate.Year == DateTime.Now.Year ) && ( lastRotate.Month != DateTime.Now.Month ) ) ) { bDoRotate = true; } } if ( lrc.Yearly ) { // check if the year is different if ( lastRotate.Year != DateTime.Now.Year ) { bDoRotate = true; } } } } return bDoRotate; }
/// <summary> /// Compress a file using .Net /// </summary> /// <param name="m_filepath">the file to compress</param> /// <param name="lrc">logrotateconf object</param> void CompressRotatedFile( string m_filepath, LogrotateConf lrc ) { int chunkSize = 65536; FileInfo fi = new FileInfo( m_filepath ); if ( fi.Extension == "." + lrc.CompressExt ) { return; } string compressed_file_path = m_filepath + "." + lrc.CompressExt; Logging.Log( Strings.Compressing + " " + m_filepath, Logging.LogType.Verbose ); if ( this._argsParser.Debug == false ) { try { using ( FileStream fs = new FileStream( m_filepath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) { using ( GZipStream zs = new GZipStream( new FileStream( compressed_file_path, FileMode.Create ), CompressionMode.Compress ) ) { byte[] buffer = new byte[chunkSize]; while ( true ) { int bytesRead = fs.Read( buffer, 0, chunkSize ); if ( bytesRead == 0 ) { break; } zs.Write( buffer, 0, bytesRead ); } } } this.DeleteRotateFile( m_filepath, lrc ); } catch ( Exception e ) { Logging.Log( "Error in CompressRotatedFile with file " + m_filepath, Logging.LogType.Error ); Logging.LogException( e ); } } }
/// <summary> /// Age out old rotated files, and rename rotated files as needed. Also support delaycompress option /// </summary> /// <param name="lrc">logrotateconf object</param> /// <param name="fi">FileInfo object for the log file</param> /// <param name="rotate_path">the folder rotated logs are located in</param> void AgeOutRotatedFiles( LogrotateConf lrc, FileInfo fi, string rotate_path ) { DirectoryInfo di = new DirectoryInfo( rotate_path ); FileInfo[] fis = di.GetFiles( fi.Name + "*" ); if ( fis.Length == 0 ) { // nothing to do return; } // look for any rotated log files, and rename them with the count if not using dateext Regex pattern = new Regex( "[0-9]" ); // sort alphabetically reversed Array.Sort( fis, delegate( FileSystemInfo a, FileSystemInfo b ) { return ( ( new CaseInsensitiveComparer() ).Compare( b.Name, a.Name ) ); } ); // if original file is in this list, remove it if ( fis[fis.Length - 1].Name == fi.Name ) { // this is the original file, remove from this list Array.Resize( ref fis, fis.Length - 1 ); } // go ahead and remove files that are too old by age foreach ( FileInfo m_fi in fis ) { if ( lrc.MaxAge != 0 ) { // any log files that are "old" need to be handled - either deleted or emailed if ( m_fi.LastWriteTime < DateTime.Now.Subtract( new TimeSpan( lrc.MaxAge, 0, 0, 0 ) ) ) { Logging.Log( m_fi.FullName + " is too old - " + Strings.DeletingFile, Logging.LogType.Verbose ); RemoveOldRotateFile( fi.FullName, lrc, m_fi ); } } } // iterate through array, determine if file needs to be removed or emailed if ( lrc.DateExt ) { for ( int rotation_counter = lrc.Rotate - 1; rotation_counter < fis.Length; rotation_counter++ ) { // remove any entries that are past the rotation limit RemoveOldRotateFile( fi.FullName, lrc, fis[rotation_counter] ); } } else { foreach ( FileInfo m_fi in fis ) { // if not aged out and we are not using dateext, then rename the file // determine the rotation number of this file. Account if it is compressed string[] exts = m_fi.Name.Split( '.' ); // determine which (if any) of the extensions match the regex. w hen we find one we will use that as our rename reference int i; for ( i = exts.Length - 1; i > 0; i-- ) { if ( pattern.IsMatch( exts[i] ) ) { break; } } if ( Convert.ToInt32( exts[i] ) >= lrc.Rotate ) { // too old! RemoveOldRotateFile( fi.FullName, lrc, m_fi ); } else { int newnum = Convert.ToInt32( exts[i] ) + 1; // build new filename string newFile = ""; for ( int j = 0; j < i; j++ ) { newFile = newFile + exts[j]; newFile += "."; } newFile += newnum.ToString(); for ( int j = i + 1; j < exts.Length; j++ ) { newFile += "."; newFile += exts[j]; } Logging.Log( Strings.Renaming + " " + m_fi.FullName + Strings.To + rotate_path + newFile, Logging.LogType.Verbose ); if ( _argsParser.Debug == false ) { // the there is already a file with the new name, then delete that file so we can rename this one if ( File.Exists( rotate_path + newFile ) ) { DeleteRotateFile( rotate_path + newFile, lrc ); } File.Move( m_fi.FullName, rotate_path + newFile ); // if we are set to compress, then check if the new file is compressed. this is done by looking at the first two bytes // if they are set to 0x1f8b then it is already compressed. There is a possibility of a false positive, but this should // be very unlikely since log files are text files and will not start with these bytes if ( lrc.Compress ) { FileStream fs = File.Open( rotate_path + newFile, FileMode.Open ); byte[] magicnumber = new byte[2]; fs.Read( magicnumber, 0, 2 ); fs.Close(); if ( ( magicnumber[0] != 0x1f ) && ( magicnumber[1] != 0x8b ) ) { CompressRotatedFile( rotate_path + newFile, lrc ); } } } } } } }
/// <summary> /// Sends a log file as an email attachment if email setttings are configured /// </summary> /// <param name="m_filepath">Path to the original log file</param> /// <param name="lrc">LogRotationConf object</param> /// <param name="m_file_attachment_path">Path to the log file to attach to the email</param> void SendEmail( string m_filepath, LogrotateConf lrc, string m_file_attachment_path ) { if ( ( lrc.SMTPServer != "" ) && ( lrc.SMTPPort != 0 ) && ( lrc.MailLast ) && ( lrc.MailAddress != "" ) ) { Logging.Log( Strings.SendingEmailTo + " " + lrc.MailAddress, Logging.LogType.Verbose ); Attachment a = new Attachment( m_file_attachment_path ); try { MailMessage mm = new MailMessage( lrc.MailFrom, lrc.MailAddress ); mm.Subject = "Log file rotated"; mm.Body = Strings.ProgramName + " has rotated the following log file: " + m_filepath + "\r\n\nThe rotated log file is attached."; mm.Attachments.Add( a ); SmtpClient smtp = new SmtpClient( lrc.SMTPServer, lrc.SMTPPort ); if ( lrc.SMTPUserName != "" ) { smtp.Credentials = new NetworkCredential( lrc.SMTPUserName, lrc.SMTPUserPassword ); } smtp.EnableSsl = lrc.SMTPUseSSL; smtp.Send( mm ); } catch ( Exception e ) { Logging.LogException( e ); } finally { a.Dispose(); } } }
void RotateFile( LogrotateConf lrc, FileInfo fi ) { Logging.Log( Strings.RotatingFile + " " + fi.FullName, Logging.LogType.Verbose ); // we don't actually rotate if debug is enabled if ( this._argsParser.Debug ) { Logging.Log( Strings.RotateSimulated, Logging.LogType.Debug ); } if ( ( lrc.PreRotate != null ) && ( lrc.SharedScripts == false ) ) { this.PreRotate( lrc, fi.FullName ); } // determine path to put the rotated log file string rotate_path = this.GetRotatePath( lrc, fi ); // age out old logs this.AgeOutRotatedFiles( lrc, fi, rotate_path ); // determine name of rotated log file string rotate_name = this.GetRotateName( lrc, fi ); bool bLogFileExists = fi.Exists; // now either rename or copy (depends on copy setting) if ( ( lrc.Copy ) || ( lrc.CopyTruncate ) ) { Logging.Log( string.Format( "{0} {1}{2}{3}{4}", Strings.Copying, fi.FullName, Strings.To, rotate_path, rotate_name ), Logging.LogType.Verbose ); Logging.Log( "inside if", Logging.LogType.Required ); if ( this._argsParser.Debug == false ) { try { if ( bLogFileExists ) { File.Copy( fi.FullName, rotate_path + rotate_name, false ); } } catch ( Exception e ) { Logging.Log( "Error copying file " + fi.FullName + " to " + rotate_path + rotate_name, Logging.LogType.Error ); Logging.LogException( e ); return; } if ( bLogFileExists ) { DateTime now = DateTime.Now; File.SetCreationTime( rotate_path + rotate_name, now ); File.SetLastAccessTime( rotate_path + rotate_name, now ); File.SetLastWriteTime( rotate_path + rotate_name, now ); } } if ( lrc.CopyTruncate ) { Logging.Log( Strings.TruncateLogFile, Logging.LogType.Verbose ); if ( this._argsParser.Debug == false ) { try { if ( bLogFileExists ) { using ( FileStream fs = new FileStream( fi.FullName, FileMode.Open, FileAccess.Write, FileShare.ReadWrite ) ) { if ( fs.Length > 0 ) { fs.SetLength( 0 ); } fs.Close(); } } } catch ( Exception e ) { Logging.Log( "Error truncating file " + fi.FullName, Logging.LogType.Error ); Logging.LogException( e ); return; } } } } else { Logging.Log( Strings.Renaming + " " + fi.FullName + Strings.To + rotate_path + rotate_name, Logging.LogType.Verbose ); Logging.Log( "inside else", Logging.LogType.Required ); if ( this._argsParser.Debug == false ) { try { if ( bLogFileExists ) { File.Move( fi.FullName, rotate_path + rotate_name ); } } catch ( Exception e ) { Logging.Log( "Error renaming file " + fi.FullName + " to " + rotate_path + rotate_name, Logging.LogType.Error ); Logging.LogException( e ); return; } } if ( lrc.Create ) { Logging.Log( Strings.CreateNewEmptyLogFile, Logging.LogType.Verbose ); if ( this._argsParser.Debug == false ) { try { using ( FileStream fs = new FileStream( fi.FullName, FileMode.CreateNew ) ) { fs.SetLength( 0 ); fs.Close(); } } catch ( Exception e ) { Logging.Log( "Error creating new file " + fi.FullName, Logging.LogType.Error ); Logging.LogException( e ); return; } } } } // now, compress the rotated log file if we are set to if ( lrc.Compress ) { if ( lrc.DelayCompress == false ) { this.CompressRotatedFile( rotate_path + rotate_name, lrc ); rotate_name += lrc.CompressExt; } } if ( lrc.MailLast == false ) { this.SendEmail( fi.FullName, lrc, rotate_path + rotate_name ); } // rotation done, update status file if ( this._argsParser.Debug == false ) { Logging.Log( Strings.UpdateStatus, Logging.LogType.Verbose ); this._status.SetRotationDate( fi.FullName ); } if ( ( lrc.PostRotate != null ) && ( lrc.SharedScripts == false ) ) { this.PostRotate( lrc, fi.FullName ); } }
void RemoveOldRotateFile( string logfilepath, LogrotateConf lrc, FileInfo m_fi ) { if ( ( lrc.MailAddress != "" ) && ( lrc.MailLast ) ) { // attempt to mail this file this.SendEmail( logfilepath, lrc, m_fi.FullName ); } this.DeleteRotateFile( m_fi.FullName, lrc ); }
/// <summary> /// This constructor will create a logrotateconf object as a copy of another one /// </summary> /// <param name="m_source">The source logrotateconf object to copy</param> public LogrotateConf( LogrotateConf m_source ) { this.bcompress = m_source.bcompress; //scompresscmd = m_source.scompresscmd; //suncompressedcmd = m_source.suncompressedcmd; this.scompressext = m_source.scompressext; //scompressoptions = m_source.scompressoptions; this.bcopy = m_source.bcopy; this.bcopytruncate = m_source.bcopytruncate; this.bcreate = m_source.bcreate; this.bdaily = m_source.bdaily; this.bdateext = m_source.bdateext; this.sdateformat = m_source.sdateformat; this.bdelaycompress = m_source.bdelaycompress; this.bifempty = m_source.bifempty; this.smail = m_source.smail; this.iminsize = m_source.iminsize; this.imaxage = m_source.imaxage; this.bmissingok = m_source.bmissingok; this.bmonthly = m_source.bmonthly; this.solddir = m_source.solddir; this.spostrotate = m_source.spostrotate; this.sprerotate = m_source.sprerotate; this.sfirstaction = m_source.sfirstaction; this.slastaction = m_source.slastaction; this.irotate = m_source.irotate; this.lsize = m_source.lsize; this.bsharedscripts = m_source.bsharedscripts; this.istart = m_source.istart; this.stabooext = m_source.stabooext; this.bweekly = m_source.bweekly; this.byearly = m_source.byearly; this.bshred = m_source.bshred; this.ishredcycles = m_source.ishredcycles; this.bmaillast = m_source.bmaillast; this.ssmtpserver = m_source.ssmtpserver; this.ismtpport = m_source.ismtpport; this.bsmtpssl = m_source.bsmtpssl; this.ssmtpuser = m_source.ssmtpuser; this.ssmtpuserpwd = m_source.ssmtpuserpwd; this.sinclude = m_source.sinclude; this.stabooext = m_source.stabooext; this.ssmtpfrom = m_source.ssmtpfrom; }