using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace ProcessJobLog { /// /// Read a stream file that contains i5/OS job log spool file, /// create CSV output file of job log data. /// class ProcessJobLog { #region Constants for Job Log Header /// /// The JOB_LOG_HEADER constant is used to find the /// start of the job log header. Change this string as /// needed for your installed national language and /// character set. /// private const String JOB_LOG_HEADER = "Job Log"; /// /// The JOB_NAME constant is used to find the Job name /// string in the job log header. /// private const String JOB_NAME = "Job name"; /// /// The JOB_USER constant is used to find the Job user /// string in the job log header. /// private const String JOB_USER = "User"; /// /// The JOB_NUMBER constant is used to find the Job number /// string in the job log header. /// private const String JOB_NUMBER = "Number"; /// /// The JOB_DESCRIPTION constant is used to find the Job /// descrpition string in the job log header. /// private const String JOB_DESCRIPTION = "Job description"; /// /// The JOB_DESCRIPTION_LIBRARY constant is used to find the job /// description library in the job log header. /// private const String JOB_DESCRIPTION_LIBRARY = "Library"; #endregion #region Constants for Jog Log Message details /// /// The JOB_LOG_CAUSE constant is used to find the message cause /// in the job log. /// private const String JOB_LOG_CAUSE = "Cause ."; /// /// The JOB_LOG_FROM_MODULE constant is used to find the from-module /// in the job log. /// private const String JOB_LOG_FROM_MODULE = "From module"; /// /// The JOB_LOG_FROM_PROCEDURE constant is used to find the from-procedure /// in the job log. /// private const String JOB_LOG_FROM_PROCEDURE = "From procedure"; /// /// The JOB_LOG_FROM_USER constant is used to find the from-user /// in the job log. /// private const String JOB_LOG_FROM_USER = "From user"; /// /// The JOB_LOG_MESSAGE constant is used to find the message /// in the job log. /// private const String JOB_LOG_MESSAGE = "Message ."; /// /// The JOB_LOG_STATEMENT constant is used to find the from-statement /// and the to-statement in the job log. /// private const String JOB_LOG_STATEMENT = "Statement ."; /// /// The JOB_LOG_TO_MODULE is used to find the to-module /// in the job log. /// private const String JOB_LOG_TO_MODULE = "To module"; /// /// The JOB_LOG_TO_PROCEDURE is used to find the to-procedure /// in the job log. /// private const String JOB_LOG_TO_PROCEDURE = "To procedure"; #endregion #region Other constants /// /// The BLANK constant is used for a single blank char. /// private const Char BLANK = ' '; /// /// The COLON constant is used as the keyword /// separator for job log keywords. /// private const String COLON = ":"; /// /// The COLON_OFFSET constant is used to indicate /// where the value is in relation to the keyword text. /// private const Int32 COLON_OFFSET = 4; /// /// The QUOTE constant is used for a double-quote character. /// private const String QUOTE = "\""; /// /// The QUOTE_COMMA constant is used for a double-quote /// character followed by a comma. /// private const String QUOTE_COMMA = "\","; #endregion #region Main(args[]) /// /// Main method, processing starts her. /// /// /// An array of command line parameters. May be empty. /// static void Main(string[] args) { // check for 2 arguments (input file, output file), display // message, end if arguments not passed if (args.Length < 2) { Console.WriteLine("Usage: ProcessJobLog input_file output_file"); Console.ReadLine(); return; } String fileName = args[0].ToString(); String fileOut = args[1].ToString(); if (!File.Exists(fileName)) { Console.WriteLine("{0} does not exist.", fileName); return; } // Booleans used to track one-time processing of // job log header at beginning of job log file. bool isHeader = false; bool isHeaderProcessed = false; // Boolean used to track message processing bool inMessage = false; JobLog jobLog = new JobLog(); JobLogMessage jlm = new JobLogMessage(); String headerFields = String.Empty; String messageFields = String.Empty; // read/parse the input file, write out a CSV file using (StreamWriter sw = new StreamWriter(fileOut)) { String csvHeader = MakeCsvHeader(); sw.WriteLine(csvHeader); Console.WriteLine(csvHeader); using (StreamReader sr = File.OpenText(fileName)) { String input; headerFields = String.Empty; while ((input = sr.ReadLine()) != null) { // check for job log header if it has not been // encountered yet - it should be the first // line read in from the file if (!isHeader) { isHeader = CheckForHeader(input); } // job log header line is located, if it has // not been processed, process the next two // lines in the file if ((isHeader) && !(isHeaderProcessed)) { ProcessJobLogHeader(input, jobLog); } if ((inMessage) && (CheckForKeyword(input, JOB_LOG_MESSAGE))) { jlm.Message = ExtractKeywordValue(input, JOB_LOG_MESSAGE, 999); } // if the line begins with a message ID, process // the message if (CheckForMessageId(input)) { // if in a message, the header is done if (!isHeaderProcessed) { isHeaderProcessed = true; headerFields = CreateHeaderFields(jobLog); } messageFields = String.Empty; if (inMessage) { messageFields = CreateMessageFields(jlm); sw.WriteLine(headerFields + messageFields); Console.WriteLine(headerFields + messageFields); jlm = new JobLogMessage(); } ParseMsgId(input, jlm); inMessage = true; } } } // write out last CSV record messageFields = CreateMessageFields(jlm); sw.WriteLine(headerFields + messageFields); Console.WriteLine(headerFields + messageFields); } Console.WriteLine("*** END ***"); Console.ReadLine(); } #endregion #region CheckForHeader(input) /// /// Check for the job log header lines. The first line in the /// job log file is the "Job Log" statement, followed /// by two lines that contain job log header data to be parsed. /// /// /// The input line to parse. /// /// /// bool - true if "Display Job Log" is found. /// private static bool CheckForHeader(String input) { bool isHeader = false; // look for string "Job Log" in line. // NOTE: this is for the U.S. English OS/400 language code 2924. // Change this text string as required. if (input.Contains(JOB_LOG_HEADER)) { isHeader = true; } return isHeader; } #endregion #region CheckForKeyword /// /// Check for a message keyword in the current line of the /// job log being processed. /// /// /// The line of the job log being processed. /// /// /// The keyword to search for. /// /// /// bool - true if the keyword is in the current line. /// private static bool CheckForKeyword(String input, String keyword) { if (input.Contains(keyword)) { return true; } return false; } #endregion #region CheckForMessageId(input) /// /// Check for a MSGID at the start of an input line, parse as /// a message if MSGID is found. /// /// /// The input line to parse. /// /// /// bool - true if MSGID is found. /// private static bool CheckForMessageId(String input) { bool newMsg = false; // Use match pattern for OS/400 style message identifiers. // Char 1 : alphabetic // Char 2 - 3: any alphabetic or numeric // Char 4 - 7: any hex digit (0-9, A-F) Match match = Regex.Match(input, @"[A-Z][A-Z\d]{2}[A-F\d]{4}"); if ((match.Success) && (match.Index == 0)) { newMsg = true; } return newMsg; } #endregion #region CreateHeaderFields /// /// Create the CSV header fields for the job log. /// /// /// The JobLog object that contains the job log header values. /// /// /// String - a comma-delimited, comma-terminated string of /// job log header values. /// private static string CreateHeaderFields(JobLog jobLog) { StringBuilder sb = new StringBuilder(); sb.Append(MakeDelimitedString(jobLog.JobName)); sb.Append(MakeDelimitedString(jobLog.JobUser)); sb.Append(MakeDelimitedString(jobLog.JobNumber)); sb.Append(MakeDelimitedString(jobLog.JobDescription)); sb.Append(MakeDelimitedString(jobLog.JobDescriptionLibrary)); return sb.ToString(); } #endregion #region CreateMessageFields(jlm) /// /// Create the CSV message fields for the job log. /// /// /// The JobLogMessage object that contains the job log /// message values. /// /// /// String - a comma-delimited, comma-terminated string of /// job log message values. /// private static String CreateMessageFields(JobLogMessage jlm) { StringBuilder sb = new StringBuilder(); sb.Append(MakeDelimitedString(jlm.MessageId)); sb.Append(MakeDelimitedString(jlm.MessageType)); sb.Append(MakeDelimitedString(jlm.MessageSeverity)); sb.Append(MakeDelimitedString(jlm.MessageDate)); sb.Append(MakeDelimitedString(jlm.MessageTime)); sb.Append(MakeDelimitedString(jlm.MessageFromProgram)); sb.Append(MakeDelimitedString(jlm.MessageFromProgramLibrary)); sb.Append(MakeDelimitedString(jlm.MessageFromInstruction)); sb.Append(MakeDelimitedString(jlm.MessageToProgram)); sb.Append(MakeDelimitedString(jlm.MessageToProgramLibrary)); sb.Append(MakeDelimitedString(jlm.MessageToInstruction)); sb.Append(MakeDelimitedString(jlm.Message)); return sb.ToString(); } #endregion #region ExtractKeywordValue #region ExtractKeywordValue(input, keyword) /// /// Extract the value for the given keyword from the input string. /// The length to extract is 10. /// /// /// The input string that contains the keyword and value to extract. /// /// /// The keyword whose value is to be extracted. /// /// /// String - the value that was extracted. /// private static String ExtractKeywordValue(String input, String keyword) { return ExtractKeywordValue(input, keyword, 10); } #endregion #region ExtractKeywordValue(input, keyword, length) /// /// Extract the value for the given keyword from the input string. /// /// /// The input string that contains the keyword and value to extract. /// /// /// The keyword whose value is to be extracted. /// /// /// The number of characters that may be contained in the value. /// /// /// String - the value that was extracted. /// private static String ExtractKeywordValue(String input, String keyword, int length) { String returnValue = String.Empty; int start = input.IndexOf(keyword); if (start >= 0) { int end = input.IndexOf(COLON, start) + COLON_OFFSET; if ((end + length) > input.Length) { returnValue = input.Substring(end).Trim(); } else { returnValue = input.Substring(end, length).Trim(); } } return returnValue; } #endregion #endregion #region MakeCsvHeader /// /// Create the CSV header string. /// /// /// String - the CSV header string. /// private static String MakeCsvHeader() { StringBuilder sb = new StringBuilder(); // job log header fields sb.Append("JOB,"); sb.Append("USER,"); sb.Append("NUMBER,"); sb.Append("JOBD,"); sb.Append("JOBDLIB,"); // message header fields sb.Append("MSGID,"); sb.Append("TYPE,"); sb.Append("SEV,"); sb.Append("DATE,"); sb.Append("TIME,"); sb.Append("FROMPGM,"); sb.Append("FROMLIB,"); sb.Append("FROMINST,"); sb.Append("TOPGM,"); sb.Append("TOLIB,"); sb.Append("TOINST,"); // message fields sb.Append("MESSAGE"); return sb.ToString(); } #endregion #region MakeDelimitedString(str) /// /// Given a string, return it as a quoted comma-delimited string. /// /// /// The string value to delimit. /// /// /// String - the quoted comma-delimited string. /// private static String MakeDelimitedString(String str) { StringBuilder sb = new StringBuilder(); sb.Append(QUOTE); sb.Append(str); sb.Append(QUOTE_COMMA); return sb.ToString(); } #endregion #region ParseMsgid(input, jlm) /// /// Parse a line that begins with a MSGID, extract the /// MSGID and other data fields from the line, return /// in a string for a CSV file. /// /// /// The input line to parse. /// /// /// The JobLogMessage object used for the current message ID /// being processed. /// private static void ParseMsgId(String input, JobLogMessage jlm) { String[] s = input.Split(new Char[] {BLANK}, StringSplitOptions.RemoveEmptyEntries); // array s contains 11 elements from the job log MSGID line: // [ 0]: MSGID // [ 1]: TYPE // [ 2]: SEV // [ 3]: DATE // [ 4]: TIME // [ 5]: FROMPGM // [ 6]: FROMLIB // [ 7]: FROMINST // [ 8]: TOPGM // [ 9]: TOLIB // [10]: TOINST // // On some MSGID lines, the TOLIB value is blank. // The code in this section handles that and moves // the final value parsed back to the TOINST element. // process the split array, ignore the empty elements int i = 0; foreach(String str in s) { str.Trim(); if (i == 0) { jlm.MessageId = str; } if (i == 1) { jlm.MessageType = str; } if (i == 2) { jlm.MessageSeverity = str; } if (i == 3) { jlm.MessageDate = str; } if (i == 4) { jlm.MessageTime = str; } if (i == 5) { jlm.MessageFromProgram = str; } if (i == 6) { jlm.MessageFromProgramLibrary = str; } if (i == 7) { jlm.MessageFromInstruction = str; } if (i == 8) { jlm.MessageToProgram = str; } if (i == 9) { if (str == "*N") { jlm.MessageToProgramLibrary = BLANK.ToString(); jlm.MessageToInstruction = "*N"; } else { jlm.MessageToProgramLibrary = s[9]; jlm.MessageToInstruction = s[10]; } } i++; } } #endregion #region ProcessJobLogHeader(input, jobLog) /// /// Process the job log header. Extract header data and /// assign to JobLog object. /// /// /// The current input line from the job log text file. /// /// /// The JobLog object used in this class to contain the /// job log data. /// private static void ProcessJobLogHeader(String input, JobLog jobLog) { if (jobLog.JobName.Length == 0) { jobLog.JobName = ExtractKeywordValue(input, JOB_NAME); } if (jobLog.JobNumber.Length == 0) { jobLog.JobNumber = ExtractKeywordValue(input, JOB_NUMBER); } if (jobLog.JobUser.Length == 0) { jobLog.JobUser = ExtractKeywordValue(input, JOB_USER); } if (jobLog.JobDescription.Length == 0) { jobLog.JobDescription = ExtractKeywordValue(input, JOB_DESCRIPTION); } if (jobLog.JobDescriptionLibrary.Length == 0) { jobLog.JobDescriptionLibrary = ExtractKeywordValue(input, JOB_DESCRIPTION_LIBRARY); } } #endregion } }