/// <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) { bgzipcompress = m_source.bgzipcompress; //scompresscmd = m_source.scompresscmd; //suncompressedcmd = m_source.suncompressedcmd; scompressext = m_source.scompressext; //scompressoptions = m_source.scompressoptions; bcopy = m_source.bcopy; bcopytruncate = m_source.bcopytruncate; bcreate = m_source.bcreate; bdaily = m_source.bdaily; bdateext = m_source.bdateext; sdateformat = m_source.sdateformat; bdelaycompress = m_source.bdelaycompress; bifempty = m_source.bifempty; smail = m_source.smail; iminsize = m_source.iminsize; imaxsize = m_source.imaxsize; imaxage = m_source.imaxage; bmissingok = m_source.bmissingok; bmonthly = m_source.bmonthly; solddir = m_source.solddir; spostrotate = m_source.spostrotate; sprerotate = m_source.sprerotate; sfirstaction = m_source.sfirstaction; slastaction = m_source.slastaction; irotate = m_source.irotate; lsize = m_source.lsize; bsharedscripts = m_source.bsharedscripts; istart = m_source.istart; stabooext = m_source.stabooext; bweekly = m_source.bweekly; byearly = m_source.byearly; bshred = m_source.bshred; ishredcycles = m_source.ishredcycles; bmaillast = m_source.bmaillast; ssmtpserver = m_source.ssmtpserver; ismtpport = m_source.ismtpport; bsmtpssl = m_source.bsmtpssl; ssmtpuser = m_source.ssmtpuser; ssmtpuserpwd = m_source.ssmtpuserpwd; sinclude = m_source.sinclude; stabooext = m_source.stabooext; ssmtpfrom = m_source.ssmtpfrom; bretry_logfileopen = m_source.bretry_logfileopen; inumretry_logfileopen = m_source.inumretry_logfileopen; inumms_retry_logfileopen = m_source.inumms_retry_logfileopen; }
/// <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) { bcompress = m_source.bcompress; //scompresscmd = m_source.scompresscmd; //suncompressedcmd = m_source.suncompressedcmd; scompressext = m_source.scompressext; //scompressoptions = m_source.scompressoptions; bcopy = m_source.bcopy; bcopytruncate = m_source.bcopytruncate; bcreate = m_source.bcreate; bdaily = m_source.bdaily; bdateext = m_source.bdateext; sdateformat = m_source.sdateformat; bdelaycompress = m_source.bdelaycompress; bifempty = m_source.bifempty; smail = m_source.smail; iminsize = m_source.iminsize; imaxage = m_source.imaxage; bmissingok = m_source.bmissingok; bmonthly = m_source.bmonthly; solddir = m_source.solddir; spostrotate = m_source.spostrotate; sprerotate = m_source.sprerotate; sfirstaction = m_source.sfirstaction; slastaction = m_source.slastaction; irotate = m_source.irotate; lsize = m_source.lsize; bsharedscripts = m_source.bsharedscripts; istart = m_source.istart; stabooext = m_source.stabooext; bweekly = m_source.bweekly; byearly = m_source.byearly; bshred = m_source.bshred; ishredcycles = m_source.ishredcycles; bmaillast = m_source.bmaillast; ssmtpserver = m_source.ssmtpserver; ismtpport = m_source.ismtpport; bsmtpssl = m_source.bsmtpssl; ssmtpuser = m_source.ssmtpuser; ssmtpuserpwd = m_source.ssmtpuserpwd; sinclude = m_source.sinclude; stabooext = m_source.stabooext; ssmtpfrom = m_source.ssmtpfrom; }
private static 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(); 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, cla.Debug); } }
/// <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> private static bool PreRotate(logrotateconf lrc, string path_to_logfile) { if (lrc.PreRotate == null) throw new ArgumentNullException("lrc.PreRotate"); return CreateScriptandExecute(lrc.PreRotate, path_to_logfile); }
private static void ProcessConfigFile(string m_path_to_file) { Logging.Log(Strings.ParseConfigFile+" " + m_path_to_file,Logging.LogType.Verbose); StreamReader sr = new StreamReader(m_path_to_file); bool bSawASection = false; // 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(GlobalConfig); ProcessConfileFileSection(line, sr, lrc); } else { if (bSawASection == false) GlobalConfig.Parse(line, cla.Debug); else { Logging.Log(Strings.GlobalOptionsAboveSections, Logging.LogType.Error); } } } }
/// <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> private static 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("%s", ((double)(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; }
/// <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> private static 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> /// 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> private static 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 (cla.Debug == false) { ShredFile sf = new ShredFile(m_filepath); sf.ShredIt(lrc.ShredCycles, cla.Debug); } } else { Logging.Log(Strings.DeletingFile+" " + m_filepath, Logging.LogType.Verbose); if (cla.Debug == false) { File.Delete(m_filepath); } } }
private static string GetRotateLogDirectory(string logfilepath, logrotateconf lrc) { if (lrc.OldDir != "") return lrc.OldDir; else return Path.GetDirectoryName(logfilepath); }
/// <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> private static bool CheckForRotate(string logfilepath, logrotateconf lrc) { if (cla.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.IfEmpty == false) { 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 = 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> private static 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 (cla.Debug == false) { try { using (FileStream fs = new FileStream(m_filepath, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (System.IO.Compression.GZipStream zs = new System.IO.Compression.GZipStream(new FileStream(compressed_file_path, FileMode.Create), System.IO.Compression.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); } } } DeleteRotateFile(m_filepath, lrc); } catch (Exception e) { Logging.Log("Error in CompressRotatedFile with file " + m_filepath, Logging.LogType.Error); Logging.LogException(e); return; } } }
/// <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> private static 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<FileSystemInfo>(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<FileInfo>(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); continue; } } } // iterate through array, determine if file needs to be removed or emailed if (lrc.DateExt == true) { 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(new char[] { '.' }); // 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 (cla.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> private static 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 System.Net.NetworkCredential(lrc.SMTPUserName, lrc.SMTPUserPassword); } smtp.EnableSsl = lrc.SMTPUseSSL; smtp.Send(mm); } catch (Exception e) { Logging.LogException(e); return; } finally { a.Dispose(); } } }
private static 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 (cla.Debug) Logging.Log(Strings.RotateSimulated,Logging.LogType.Debug); if ((lrc.PreRotate!=null)&&(lrc.SharedScripts==false)) PreRotate(lrc,fi.FullName); // determine path to put the rotated log file string rotate_path = GetRotatePath(lrc,fi); // age out old logs AgeOutRotatedFiles(lrc, fi,rotate_path); // determine name of rotated log file string rotate_name = GetRotateName(lrc,fi); bool bLogFileExists = fi.Exists; // now either rename or copy (depends on copy setting) if ((lrc.Copy)||(lrc.CopyTruncate)) { Logging.Log(Strings.Copying+" " + fi.FullName + Strings.To + rotate_path + rotate_name, Logging.LogType.Verbose); if (cla.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) { File.SetCreationTime(rotate_path + rotate_name, DateTime.Now); File.SetLastAccessTime(rotate_path + rotate_name, DateTime.Now); File.SetLastWriteTime(rotate_path + rotate_name, DateTime.Now); } } if (lrc.CopyTruncate) { Logging.Log(Strings.TruncateLogFile,Logging.LogType.Verbose); if (cla.Debug == false) { try { if (bLogFileExists) { FileStream fs = new FileStream(fi.FullName, FileMode.Open); 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); if (cla.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 (cla.Debug == false) { try { 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) { CompressRotatedFile(rotate_path + rotate_name, lrc); rotate_name += lrc.CompressExt; } } if (lrc.MailLast==false) SendEmail(fi.FullName,lrc, rotate_path + rotate_name); // rotation done, update status file if (cla.Debug == false) { Logging.Log(Strings.UpdateStatus, Logging.LogType.Verbose); Status.SetRotationDate(fi.FullName); } if ((lrc.PostRotate != null) && (lrc.SharedScripts == false)) PostRotate(lrc,fi.FullName); }
private static void RemoveOldRotateFile(string logfilepath,logrotateconf lrc,FileInfo m_fi) { if ((lrc.MailAddress != "") && (lrc.MailLast)) { // attempt to mail this file SendEmail(logfilepath, lrc, m_fi.FullName); } DeleteRotateFile(m_fi.FullName, lrc); }
/// <summary> /// Execute any lastaction 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> private static bool ProcessLastAction(logrotateconf lrc) { if (lrc.LastAction == null) throw new ArgumentNullException("lrc.LastAction"); return CreateScriptandExecute(lrc.LastAction, ""); }