All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gemstone.gemfire.internal.MergeLogFiles Maven / Gradle / Ivy

/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import com.gemstone.gemfire.SystemFailure;
import com.gemstone.gemfire.internal.concurrent.CFactory;
import com.gemstone.gemfire.internal.concurrent.BQ;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import com.gemstone.gemfire.internal.Assert;


/**
 * This program merges entries from multiple GemFire log files (those
 * written using a {@link com.gemstone.gemfire.i18n.LogWriterI18n} together,
 * sorting them by their timestamp.  Note that this program assumes
 * that the entries in the individual log files are themselves sorted
 * by timestamp.

* * MergeLogFiles has several command line options:
*
* -pids tells the program to look for hydra-style naming and pick out * process identifiers, then use these to distinguish log entries instead of * full log file names.
*
* -dirCount x when -pids is not being used, or it is being used * and a process ID can't be found, the -dirCount option instructs the * program as to how many parent directory names should be included in the name * of each log file.
*
* -noblanks tells the program to suppress blank lines found in log files
*
* -align tells the program to align log content not having timestamps with * log content that does have timestamps.
*
* -threads tells the program to attempt to make use of multiple CPUs

* * The -pids option will cause a file nickname table to be built and * emitted at the beginning of the merged log files. Nicknames are of the * form pid-x, where pid is the process ID that emitted the log entry, and * x is the position of this log file in the ordered set of log files * created by that process.

* * Normally, one log file reader is created per log file, and they are iterated * over repeatedly to find and write the next earliest timestamped line to * the output stream. Text without a timestamp is associated with the prior * timestamped line in the file.

* * The -threads option will cause the program to also create threads * for each reader that are backed by bounded {@link BQ queues}, * as outlined in the diagram below. This can consume more memory, so it is * wise to increase the Xmx of the java virtual machine if you are going to * use this option.

* *

* *
* * @see SortLogFile * @see LogFileParser * * @author David Whitlock * @author Bruce Schuchardt * * * @since 2.0 (-pids, -threads, -align, and -noblanks added in 5.1) */ public class MergeLogFiles { private static PrintStream out = System.out; private static PrintStream err = System.err; /** * Merges the log files from a given set of * InputStreams into a PrinWriter. * * @param logFiles * The log files to be merged * @param logFileNames * The names of the log files to be printed in the merged log * @param mergedFile * Where the merged logs are printed to * * @return Whether or not problems occurred while merging the log * files. * * @throws IllegalArgumentException * If the length of logFiles is not the same * as the length of logFileNames * * @see Sorter#mergeLogFiles */ public static boolean mergeLogFiles(InputStream[] logFiles, String[] logFileNames, PrintWriter mergedFile) { return mergeLogFiles(logFiles, logFileNames, mergedFile, false, false, false, new LinkedList()); } /** * Merges the log files from a given set of * InputStreams into a PrinWriter. * * @param logFiles * The log files to be merged * @param logFileNames * The names of the log files to be printed in the merged log * @param mergedFile * Where the merged logs are printed to * @param tabOut * Whether to align non-timestamped lines with timestamped lines * @param suppressBlanks * Whether to omit blank lines * @return Whether or not problems occurred while merging the log * files. * * @throws IllegalArgumentException * If the length of logFiles is not the same * as the length of logFileNames * * @see Sorter#mergeLogFiles */ public static boolean mergeLogFiles(InputStream[] logFiles, String[] logFileNames, PrintWriter mergedFile, boolean tabOut, boolean suppressBlanks, boolean multithreaded) { return mergeLogFiles(logFiles, logFileNames, mergedFile, tabOut, suppressBlanks, multithreaded, new LinkedList()); } /** * Merges the log files from a given set of * InputStreams into a PrinWriter. * * @param logFiles * The log files to be merged * @param logFileNames * The names of the log files to be printed in the merged log * @param mergedFile * Where the merged logs are printed to * @param tabOut * Whether to align non-timestamped lines with timestamped lines * @param suppressBlanks * Whether to omit blank lines * @param patterns * Regular expression patterns that lines must match to be included * @return Whether or not problems occurred while merging the log * files. * * @throws IllegalArgumentException * If the length of logFiles is not the same * as the length of logFileNames * * @see Sorter#mergeLogFiles */ public static boolean mergeLogFiles(InputStream[] logFiles, String[] logFileNames, PrintWriter mergedFile, boolean tabOut, boolean suppressBlanks, boolean multithreaded, List patterns) { return Sorter.mergeLogFiles(logFiles, logFileNames, mergedFile, tabOut, suppressBlanks, multithreaded, patterns); } // ////////////////// Main Program //////////////////// /** * Prints usage information about this program */ private static void usage(String s) { // note that we don't document the -pids switch because it is tailored // to how hydra works and would not be useful for customers err.println("\n** " + s + "\n"); err.println(LocalizedStrings.MergeLogFiles_USAGE.toLocalizedString() + ": java MergeLogFiles [(directory | logFile)]+"); err.println("-dirCount n " + LocalizedStrings.MergeLogFiles_NUMBER_OF_PARENT_DIRS_TO_PRINT.toLocalizedString()); err.println("-mergeFile file " + LocalizedStrings.MergeLogFiles_FILE_IN_WHICH_TO_PUT_MERGED_LOGS.toLocalizedString()); err.println("-pids " + LocalizedStrings.MergeLogFiles_SEARCH_FOR_PIDS_IN_FILE_NAMES_AND_USE_THEM_TO_IDENTIFY_FILES.toLocalizedString()); err.println("-align " + LocalizedStrings.MergeLogFiles_ALIGN_NONTIMESTAMPED_LINES_WITH_OTHERS.toLocalizedString()); err.println("-noblanks " + LocalizedStrings.MergeLogFiles_SUPPRESS_OUTPUT_OF_BLANK_LINES.toLocalizedString()); err.println("-threaded " + LocalizedStrings.MergeLogFiles_USE_MULTITHREADING_TO_TAKE_ADVANTAGE_OF_MULTIPLE_CPUS.toLocalizedString()); // err.println("-regex pattern Case-insensitive search for a regular expression."); // err.println(" May be used multiple times. Use Java regular "); // err.println(" expression syntax (see java.util.regex.Pattern)."); err.println(""); err.println(LocalizedStrings.MergeLogFiles_MERGES_MULTIPLE_GEMFIRE_LOG_FILES_AND_SORTS_THEM_BY_TIMESTAMP.toLocalizedString()); err.println(LocalizedStrings.MergeLogFiles_THE_MERGED_LOG_FILE_IS_WRITTEN_TO_SYSTEM_OUT_OR_A_FILE.toLocalizedString()); err.println(""); err.println(LocalizedStrings.MergeLogFiles_IF_A_DIRECTORY_IS_SPECIFIED_ALL_LOG_FILES_IN_THAT_DIRECTORY_ARE_MERGED.toLocalizedString()); err.println(""); System.exit(1); } /** * Find all of the .log files in the given directory * @param dirName directory to search * @return all of the .log files found (Files) */ static ArrayList getLogFiles(String dirName) { ArrayList result = new ArrayList(); File dir = new File(dirName); File names[] = FileUtil.listFiles(dir); for (int i = 0; i < names.length; i ++) { String n = names[i].getAbsolutePath(); if (n.endsWith(".log") || n.endsWith(".log.gz")) { result.add(names[i]); } } // for return result; } public static void main(String[] args) throws IOException { File mergeFile = null; ArrayList files = new ArrayList(); List nickNames = null; int dirCount = 0; boolean findPIDs = false; boolean tabOut = false; boolean suppressBlanks = false; boolean multithreaded = false; List patterns = new LinkedList(); // Parse command line for (int i = 0; i < args.length; i++) { if (args[i].equals("-align")) { tabOut = true; } else if (args[i].equals("-noblanks")) { suppressBlanks = true; } else if (args[i].equals("-pids")) { findPIDs = true; } else if (args[i].equals("-threaded")) { multithreaded = true; } else if (args[i].equals("-regex")) { if (i+1 >= args.length) { usage("missing pattern for -regex option"); } patterns.add(args[i+1]); i++; } else if (args[i].equals("-dirCount")) { if (++i >= args.length) { usage(LocalizedStrings.MergeLogFiles_MISSING_NUMBER_OF_PARENT_DIRECTORIES.toLocalizedString()); } try { dirCount = Integer.parseInt(args[i]); } catch (NumberFormatException ex) { usage(LocalizedStrings.MergeLogFiles_NOT_A_NUMBER_0.toLocalizedString(args[i])); } } else if (args[i].equals("-mergeFile")) { if (++i >= args.length) { usage(LocalizedStrings.MergeLogFiles_MISSING_MERGE_FILE_NAME.toLocalizedString()); } mergeFile = new File(args[i]); } else { File file = new File(args[i]); if (!file.exists()) { usage(LocalizedStrings.MergeLogFiles_FILE_0_DOES_NOT_EXIST.toLocalizedString(file)); } files.add(file.getAbsoluteFile()); } } // for if ( files.isEmpty() ) { usage(LocalizedStrings.MergeLogFiles_MISSING_FILENAME.toLocalizedString()); } // Expand directory names found in list ArrayList expandedFiles = new ArrayList(); for (int i = 0; i < files.size(); i ++) { File f = (File)files.get(i); String n = f.getAbsolutePath(); if (!f.exists()) { usage(LocalizedStrings.MergeLogFiles_FILE_0_DOES_NOT_EXIST.toLocalizedString(n)); } if (f.isFile()) { expandedFiles.add(f); continue; } if (f.isDirectory()) { ArrayList moreFiles = getLogFiles(n); expandedFiles.addAll(moreFiles); continue; } usage(LocalizedStrings.MergeLogFiles_FILE_0_IS_NEITHER_A_FILE_NOR_A_DIRECTORY.toLocalizedString(n)); } Collections.sort(expandedFiles); files = expandedFiles; // Create output stream PrintStream ps; if (mergeFile != null) { ps = new PrintStream(new FileOutputStream(mergeFile), true); } else { ps = out; } PrintWriter mergedFile = new PrintWriter(ps, true); ps.println("Merged files (count = " + expandedFiles.size() + ") input list:"); for (int i = 0; i < expandedFiles.size(); i ++) { ps.println(" " + expandedFiles.get(i)); } ps.println(""); if (findPIDs) { nickNames = findPIDs(files, mergedFile); } InputStream[] logFiles = new InputStream[files.size()]; String[] logFileNames = new String[files.size()]; for (int i = 0; i < files.size(); i++) { File file = (File) files.get(i); logFiles[i] = new FileInputStream(file); if (findPIDs && (nickNames.get(i) != null)) { if (file.getCanonicalPath().toLowerCase().endsWith("gz")) { logFileNames[i] = (String)nickNames.get(i)+".gz"; } else { logFileNames[i] = (String)nickNames.get(i); } } else { StringBuffer sb = new StringBuffer(); File parent = file.getParentFile(); for (int j = 0; j < dirCount && parent != null; j++) { String parentName = parent.getName() + "/"; // don't add dot-slash if (parentName.equals("./")) { parent = null; } else { sb.insert(0, parentName); parent = parent.getParentFile(); } } sb.append(file.getName()); logFileNames[i] = sb.toString(); } } mergeLogFiles(logFiles, logFileNames, mergedFile, tabOut, suppressBlanks, multithreaded, patterns); System.exit(0); // In case we're running JProbe } ////////////////////// Inner Classes ////////////////////// /** * hydra log files usually have the process's PID in their path name. * This method extracts the PID number and assigns the corresponding File * a nickname using the PID and the position of the File in the list of those * also having this PID.

* e.g., bgexec32414_1043.log --> 1043-1
* gemfire_1043/system.log --> 1043-2
* gemfire_1043/system_01_00.log --> 1043-3
*/ private static ArrayList findPIDs(ArrayList files, PrintWriter output) { int pidTable[] = new int[files.size()]; int pidTableCounter[] = new int[pidTable.length]; ArrayList nickNames = new ArrayList(); char sep = File.separatorChar; // System.out.println("findPids() invoked"); for (Iterator it=files.iterator(); it.hasNext(); ) { File f = (File)it.next(); String name = f.getPath(); // System.out.println("considering " + name); String slashdotslash = "" + sep + "." + sep; int startIdx = name.lastIndexOf(slashdotslash); // get rid of the parent directories and any /./ in the path if (startIdx > 0) { name = name.substring(startIdx + slashdotslash.length()); } startIdx = name.lastIndexOf(sep); // first see if there's a number at the end of the file's directory name if (startIdx > 0) { startIdx--; char c = name.charAt(startIdx); if ( ! ('0' <= c && c <= '9') ) { // System.out.println("no number found in directory name"); startIdx = 0; } else { // see if this is a hydra-generated test directory name, like // parReg-0504-161349 int testIdx = startIdx-1; while (testIdx > 0 && '0' <= name.charAt(testIdx) && name.charAt(testIdx) <= '9') { testIdx--; } if (testIdx < 1 || name.charAt(testIdx) == '-') { startIdx = 0; } // System.out.println("using directory name: '" + name.substring(0, startIdx+1) + "'"); } } // if there's no number in the directory name, use the file name if (startIdx <= 0) { startIdx = name.length() - 1; if (startIdx > 3 && name.charAt(startIdx) == 'g' && name.charAt(startIdx-1) == 'o' && name.charAt(startIdx-2) == 'l' && name.charAt(startIdx-3) == '.') { startIdx -= 4; // System.out.println("using file name: '" + name.substring(0,startIdx+1) + "'"); } // else { // System.out.println("could not find a PID"); // } } // find the string of numbers at the end of the test area and use it // as a PID String PID = null; for (int i=startIdx; i>=0; i--) { char c = name.charAt(i); // System.out.println("charAt("+i+")="+c); if ( ! ('0' <= c && c <= '9') ) { if (i < (name.length()-1)) { // have a number // there's a number - assume it's a PID if it's not zero PID = name.substring(i+1, startIdx+1); // System.out.println("parsing '" + PID + "'"); try { int iPID = Integer.valueOf(PID).intValue(); if (iPID > 0) { // System.out.println("Found PID " + iPID); int p = 0; // find the PID in the table of those seen so far, or assign it // a new slot. increment the number of files for this PID and // assign a nickname for the file for ( ; p patterns; //////////////////// Constructors //////////////////// /** * Creates a new Reader that reads from the given log * file with the given name. Invoking this constructor will start * this reader thread. * @param patterns java regular expressions that an entry must match one or more of */ public NonThreadedReader(InputStream logFile, String logFileName, ThreadGroup group, boolean tabOut, boolean suppressBlanks, List patterns) { // super(group, "Reader for " + ((logFileName != null) ? logFileName : logFile.toString())); if (logFileName.endsWith(".gz")) { try { this.logFile = new BufferedReader(new InputStreamReader(new GZIPInputStream(logFile))); } catch (IOException e) { System.err.println(logFileName + " does not appear to be in gzip format"); this.logFile = new BufferedReader(new InputStreamReader(logFile)); } } else { this.logFile = new BufferedReader(new InputStreamReader(logFile)); } this.logFileName = logFileName; this.patterns = patterns; // this.suppressBlanks = suppressBlanks; // this.tabOut = tabOut; this.parser = new LogFileParser(this.logFileName, this.logFile, tabOut, suppressBlanks); } /** returns the file name being read */ public String getFileName() { return this.logFileName; } /** unique identifier, used for sorting instead of file name */ private int uniqueId; /** set the unique identifier for this reader */ public void setUniqueId(int id) { uniqueId = id; } /** retrieve the unique identifier for this reader */ public int getUniqueId() { return uniqueId; } /** * Peeks at the oldest log entry read from the log file, waits for * a log entry to become available. * * @return null if interrupted while waiting */ public synchronized LogFileParser.LogEntry peek() { while (this.nextEntry == null) { try { nextEntry = parser.getNextEntry(); if (nextEntry == null) { return null; } if (!nextEntry.isLast() && !patternMatch(nextEntry)) { continue; } } catch (IOException ioe) { ioe.printStackTrace(System.err); } } return nextEntry; } /** return true if the entry matches one or more regex patterns */ private boolean patternMatch(LogFileParser.LogEntry entry) { if (this.patterns == null || this.patterns.isEmpty()) { return true; } for (Pattern p: patterns) { if (p.matcher(entry.getContents()).matches()) { return true; } } return false; } /** * Removes the old log entry read from the log file */ public LogFileParser.LogEntry poll() { LogFileParser.LogEntry returnValue = null; if (this.nextEntry != null) { returnValue = this.nextEntry; nextEntry = null; } else { while (returnValue == null) { try { returnValue = parser.getNextEntry(); if (!returnValue.isLast() && !patternMatch(returnValue)) { returnValue = null; continue; } } catch (IOException ioe) { ioe.printStackTrace(System.err); break; } } } return returnValue; } } /** * Thread that reads an entry from a GemFire log file and adds it a * bounded queue. The entries are consumed by a {@link * MergeLogFiles.Sorter}. */ static class ThreadedReader extends Thread implements Reader { /** The maximum size of the entry queue */ private static int QUEUE_CAPACITY = 1000; //////////////////// Instance Methods //////////////////// /** The log file */ private BufferedReader logFile; /** The name of the log file */ private String logFileName; /** The queue containing log entries */ private BQ queue; /** whether to suppress blank lines */ private boolean suppressBlanks; /** whether to align non-timestamped lines with timestamped lines */ private boolean tabOut; private List patterns; //////////////////// Constructors //////////////////// /** * Creates a new Reader that reads from the given log * file with the given name. Invoking this constructor will start * this reader thread. * @param patterns TODO * * @see #run */ public ThreadedReader(InputStream logFile, String logFileName, ThreadGroup group, boolean tabOut, boolean suppressBlanks, List patterns) { // super(group, "Reader for " + ((logFileName != null) ? logFileName : logFile.toString())); super(group, LocalizedStrings.MergeLogFiles_LOG_FILE_READER.toLocalizedString()); this.logFile = new BufferedReader(new InputStreamReader(logFile)); this.logFileName = logFileName; this.queue = CFactory.createLBQ(QUEUE_CAPACITY); // new UnsharedMessageQueue(QUEUE_CAPACITY, // (75 * QUEUE_CAPACITY) / 100); this.suppressBlanks = suppressBlanks; this.tabOut = tabOut; this.patterns = patterns; this.start(); } /** returns the file name being read */ public String getFileName() { return this.logFileName; } /** unique identifier, used for sorting instead of file name */ private int uniqueId; /** set the unique identifier for this reader */ public void setUniqueId(int id) { uniqueId = id; } /** retrieve the unique identifier for this reader */ public int getUniqueId() { return uniqueId; } /** * Reads the log file and places {@link LogFileParser.LogEntry} * objects into the queue. When it is finished, it places a * LogEntry that whose {@link * LogFileParser.LogEntry#isLast isLast} method will return * true. */ @Override public void run() { LogFileParser parser = new LogFileParser(this.logFileName, this.logFile, tabOut, suppressBlanks); try { while (true) { SystemFailure.checkFailure(); LogFileParser.LogEntry entry = parser.getNextEntry(); if (entry.isLast() || patternMatch(entry)) { this.queue.put(entry); synchronized (this) { this.notify(); } } if (entry.isLast()) { break; } } } catch (IOException ex) { ex.printStackTrace(System.err); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } /** return true if the entry matches one or more regex patterns */ private boolean patternMatch(LogFileParser.LogEntry entry) { if (this.patterns == null || this.patterns.isEmpty()) { return true; } for (Pattern p: patterns) { if (p.matcher(entry.getContents()).matches()) { return true; } } return false; } /** * Peeks at the oldest log entry read from the log file, waits for * a log entry to become available. * * @return null if interrupted while waiting */ public LogFileParser.LogEntry peek() { // out.println(this.getName() + " size " + this.queue.size()); LogFileParser.LogEntry entry = (LogFileParser.LogEntry) this.queue.peek(); if (entry == null) { synchronized (this) { entry = (LogFileParser.LogEntry) this.queue.peek(); while (entry == null) { boolean interrupted = Thread.interrupted(); try { this.wait(); entry = (LogFileParser.LogEntry) this.queue.peek(); } catch (InterruptedException e) { interrupted = true; } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } // while } // synchronized } return entry; } /** * Removes the old log entry read from the log file */ public LogFileParser.LogEntry poll() { return (LogFileParser.LogEntry) this.queue.poll(); } } /** * A thread group that contains the reader threads and logs uncaught * exceptions to standard error. */ static class ReaderGroup extends ThreadGroup { /** Did an uncaught exception occur? */ private boolean exceptionOccurred; ReaderGroup(String groupName) { super(groupName); this.exceptionOccurred = false; } @Override public void uncaughtException(Thread t, Throwable e) { if (e instanceof Error && SystemFailure.isJVMFailureError((Error)e)) { SystemFailure.setFailure((Error)e); // don't throw } this.exceptionOccurred = true; System.err.println(LocalizedStrings.MergeLogFiles_EXCEPTION_IN_0.toLocalizedString(t)); e.printStackTrace(System.err); } /** * Returns whether or not an uncaught exception occurred in one of * the threads in this group. */ public boolean exceptionOccurred() { return this.exceptionOccurred; } } /** * Examines the {@link MergeLogFiles.Reader#peek oldest entry} in * each log file and writes it to a merged log file. */ static class Sorter { /** * Merges the log files from a given set of * InputStreams into a PrintWriter. * * @param logFiles * The log files to be merged * @param logFileNames * The names of the log files to be printed in the merged log * @param mergedFile * Where the merged logs are printed to * @param tabOut * Whether to align non-timestamped lines with others * @param suppressBlanks * Whether to suppress output of blank lines * @param patterns TODO * @return Whether or not problems occurred while merging the log * files. * * @throws IllegalArgumentException * If the length of logFiles is not the same * as the length of logFileNames */ public static boolean mergeLogFiles(InputStream[] logFiles, String[] logFileNames, PrintWriter mergedFile, boolean tabOut, boolean suppressBlanks, boolean multithreaded, List patterns) { if (logFiles.length != logFileNames.length) { throw new IllegalArgumentException(LocalizedStrings.MergeLogFiles_NUMBER_OF_LOG_FILES_0_IS_NOT_THE_SAME_AS_THE_NUMBER_OF_LOG_FILE_NAMES_1.toLocalizedString(new Object[] {Integer.valueOf(logFiles.length), Integer.valueOf(logFileNames.length)})); } List compiledPatterns = new LinkedList(); for (String pattern: patterns) { compiledPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)); } // First start the Reader threads ReaderGroup group = new ReaderGroup(LocalizedStrings.MergeLogFiles_READER_THREADS.toLocalizedString()); Collection readers = new ArrayList(logFiles.length); for (int i = 0; i < logFiles.length; i++) { if (multithreaded) { readers.add(new ThreadedReader(logFiles[i], logFileNames[i], group, tabOut, suppressBlanks, compiledPatterns)); } else { readers.add(new NonThreadedReader(logFiles[i], logFileNames[i], group, tabOut, suppressBlanks, compiledPatterns)); } } // Merge the log files together Reader lastOldest = null; // sort readers by their next time-stamp Set sorted = sortReaders(readers); while (!readers.isEmpty()) { Reader oldest = null; Iterator sortedIt = sorted.iterator(); if (!sortedIt.hasNext()) { break; } oldest = (Reader)sortedIt.next(); sortedIt.remove(); String nextReaderTimestamp = null; Reader nextInLine = null; if (sortedIt.hasNext()) { nextInLine = (Reader)sortedIt.next(); nextReaderTimestamp = nextInLine.peek().getTimestamp(); } // if we've switched to a different reader, emit a blank line // for readability if (oldest != lastOldest) { mergedFile.println(); lastOldest = oldest; } LogFileParser.LogEntry entry = null; // write until we hit the next file's time-stamp do { entry = oldest.peek(); String timestamp = entry.getTimestamp(); if (nextReaderTimestamp != null) { if (nextReaderTimestamp.compareTo(timestamp) < 0) { sorted.add(oldest); entry = null; break; } } entry = oldest.poll(); entry.writeTo(mergedFile); } while (!entry.isLast()); if (entry != null && entry.isLast()) { readers.remove(oldest); } } return group.exceptionOccurred(); } private static Set sortReaders(Collection readers) { Set sorted = new TreeSet(new ReaderComparator()); int uniqueId = 1; for (Iterator iter = readers.iterator(); iter.hasNext(); ) { Reader reader = (Reader)iter.next(); if (reader == null) { continue; } reader.setUniqueId(uniqueId++); sorted.add(reader); } return sorted; } } protected static class ReaderComparator implements Comparator { public int compare(Object o1, Object o2) { Reader reader1 = (Reader)o1; int id1 = reader1.getUniqueId(); Reader reader2 = (Reader)o2; int id2 = reader2.getUniqueId(); LogFileParser.LogEntry entry1 = reader1.peek(); LogFileParser.LogEntry entry2 = reader2.peek(); if (entry1 == null) { if (entry2 == null) { if (id1 < id2) { return -1; } // IDs are unique, so no need for == test return 1; } // sort readers with no entries before readers with entries so they'll // be removed quickly return -1; } if (entry2 == null) { return 1; } String timestamp1 = entry1.getTimestamp(); String timestamp2 = entry2.getTimestamp(); int compare = timestamp1.compareTo(timestamp2); if (compare == 0) { if (id1 < id2) { return -1; } return 1; } return compare; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy