001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.log4j.lf5.util;
018
019import java.io.BufferedInputStream;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.io.InputStream;
025import java.text.ParseException;
026import java.text.SimpleDateFormat;
027import java.util.Date;
028
029import javax.swing.SwingUtilities;
030
031import org.apache.log4j.lf5.Log4JLogRecord;
032import org.apache.log4j.lf5.LogLevel;
033import org.apache.log4j.lf5.LogLevelFormatException;
034import org.apache.log4j.lf5.LogRecord;
035import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
036import org.apache.log4j.lf5.viewer.LogFactor5ErrorDialog;
037import org.apache.log4j.lf5.viewer.LogFactor5LoadingDialog;
038
039/**
040 * Provides utility methods for input and output streams.
041 *
042 * @author Brad Marlborough
043 * @author Richard Hurst
044 */
045
046// Contributed by ThoughtWorks Inc.
047
048public class LogFileParser implements Runnable {
049  //--------------------------------------------------------------------------
050  //   Constants:
051  //--------------------------------------------------------------------------
052  public static final String RECORD_DELIMITER = "[slf5s.start]";
053  public static final String ATTRIBUTE_DELIMITER = "[slf5s.";
054  public static final String DATE_DELIMITER = ATTRIBUTE_DELIMITER + "DATE]";
055  public static final String THREAD_DELIMITER = ATTRIBUTE_DELIMITER + "THREAD]";
056  public static final String CATEGORY_DELIMITER = ATTRIBUTE_DELIMITER + "CATEGORY]";
057  public static final String LOCATION_DELIMITER = ATTRIBUTE_DELIMITER + "LOCATION]";
058  public static final String MESSAGE_DELIMITER = ATTRIBUTE_DELIMITER + "MESSAGE]";
059  public static final String PRIORITY_DELIMITER = ATTRIBUTE_DELIMITER + "PRIORITY]";
060  public static final String NDC_DELIMITER = ATTRIBUTE_DELIMITER + "NDC]";
061
062  //--------------------------------------------------------------------------
063  //   Protected Variables:
064  //--------------------------------------------------------------------------
065
066  //--------------------------------------------------------------------------
067  //   Private Variables:
068  //--------------------------------------------------------------------------
069  private static SimpleDateFormat _sdf = new SimpleDateFormat("dd MMM yyyy HH:mm:ss,S");
070  private LogBrokerMonitor _monitor;
071  LogFactor5LoadingDialog _loadDialog;
072  private InputStream _in = null;
073
074  //--------------------------------------------------------------------------
075  //   Constructors:
076  //--------------------------------------------------------------------------
077  public LogFileParser(File file) throws IOException,
078      FileNotFoundException {
079    this(new FileInputStream(file));
080  }
081
082  public LogFileParser(InputStream stream) throws IOException {
083    _in = stream;
084  }
085  //--------------------------------------------------------------------------
086  //   Public Methods:
087  //--------------------------------------------------------------------------
088
089  /**
090   * Starts a new thread to parse the log file and create a LogRecord.
091   * See run().
092   * @param monitor LogBrokerMonitor
093   */
094  public void parse(LogBrokerMonitor monitor) throws RuntimeException {
095    _monitor = monitor;
096    Thread t = new Thread(this);
097    t.start();
098  }
099
100  /**
101   * Parses the file and creates new log records and adds the record
102   * to the monitor.
103   */
104  public void run() {
105
106    int index = 0;
107    int counter = 0;
108    LogRecord temp;
109    boolean isLogFile = false;
110
111    _loadDialog = new LogFactor5LoadingDialog(
112        _monitor.getBaseFrame(), "Loading file...");
113
114
115    try {
116      String logRecords = loadLogFile(_in);
117
118      while ((counter = logRecords.indexOf(RECORD_DELIMITER, index)) != -1) {
119        temp = createLogRecord(logRecords.substring(index, counter));
120        isLogFile = true;
121
122        if (temp != null) {
123          _monitor.addMessage(temp);
124        }
125
126        index = counter + RECORD_DELIMITER.length();
127      }
128
129      if (index < logRecords.length() && isLogFile) {
130        temp = createLogRecord(logRecords.substring(index));
131
132        if (temp != null) {
133          _monitor.addMessage(temp);
134        }
135      }
136
137      if (isLogFile == false) {
138        throw new RuntimeException("Invalid log file format");
139      }
140      SwingUtilities.invokeLater(new Runnable() {
141        public void run() {
142          destroyDialog();
143        }
144      });
145
146    } catch (RuntimeException e) {
147      destroyDialog();
148      displayError("Error - Invalid log file format.\nPlease see documentation"
149          + " on how to load log files.");
150    } catch (IOException e) {
151      destroyDialog();
152      displayError("Error - Unable to load log file!");
153    }
154
155    _in = null;
156  }
157
158  //--------------------------------------------------------------------------
159  //   Protected Methods:
160  //--------------------------------------------------------------------------
161  protected void displayError(String message) {
162    LogFactor5ErrorDialog error = new LogFactor5ErrorDialog(
163        _monitor.getBaseFrame(), message);
164
165  }
166
167  //--------------------------------------------------------------------------
168  //   Private Methods:
169  //--------------------------------------------------------------------------
170  private void destroyDialog() {
171    _loadDialog.hide();
172    _loadDialog.dispose();
173  }
174
175  /**
176   * Loads a log file from a web server into the LogFactor5 GUI.
177   */
178  private String loadLogFile(InputStream stream) throws IOException {
179    BufferedInputStream br = new BufferedInputStream(stream);
180
181    int count = 0;
182    int size = br.available();
183
184    StringBuffer sb = null;
185    if (size > 0) {
186      sb = new StringBuffer(size);
187    } else {
188      sb = new StringBuffer(1024);
189    }
190
191    while ((count = br.read()) != -1) {
192      sb.append((char) count);
193    }
194
195    br.close();
196    br = null;
197    return sb.toString();
198
199  }
200
201  private String parseAttribute(String name, String record) {
202
203    int index = record.indexOf(name);
204
205    if (index == -1) {
206      return null;
207    }
208
209    return getAttribute(index, record);
210  }
211
212  private long parseDate(String record) {
213    try {
214      String s = parseAttribute(DATE_DELIMITER, record);
215
216      if (s == null) {
217        return 0;
218      }
219
220      Date d = _sdf.parse(s);
221
222      return d.getTime();
223    } catch (ParseException e) {
224      return 0;
225    }
226  }
227
228  private LogLevel parsePriority(String record) {
229    String temp = parseAttribute(PRIORITY_DELIMITER, record);
230
231    if (temp != null) {
232      try {
233        return LogLevel.valueOf(temp);
234      } catch (LogLevelFormatException e) {
235        return LogLevel.DEBUG;
236      }
237
238    }
239
240    return LogLevel.DEBUG;
241  }
242
243  private String parseThread(String record) {
244    return parseAttribute(THREAD_DELIMITER, record);
245  }
246
247  private String parseCategory(String record) {
248    return parseAttribute(CATEGORY_DELIMITER, record);
249  }
250
251  private String parseLocation(String record) {
252    return parseAttribute(LOCATION_DELIMITER, record);
253  }
254
255  private String parseMessage(String record) {
256    return parseAttribute(MESSAGE_DELIMITER, record);
257  }
258
259  private String parseNDC(String record) {
260    return parseAttribute(NDC_DELIMITER, record);
261  }
262
263  private String parseThrowable(String record) {
264    return getAttribute(record.length(), record);
265  }
266
267  private LogRecord createLogRecord(String record) {
268    if (record == null || record.trim().length() == 0) {
269      return null;
270    }
271
272    LogRecord lr = new Log4JLogRecord();
273    lr.setMillis(parseDate(record));
274    lr.setLevel(parsePriority(record));
275    lr.setCategory(parseCategory(record));
276    lr.setLocation(parseLocation(record));
277    lr.setThreadDescription(parseThread(record));
278    lr.setNDC(parseNDC(record));
279    lr.setMessage(parseMessage(record));
280    lr.setThrownStackTrace(parseThrowable(record));
281
282    return lr;
283  }
284
285
286  private String getAttribute(int index, String record) {
287    int start = record.lastIndexOf(ATTRIBUTE_DELIMITER, index - 1);
288
289    if (start == -1) {
290      return record.substring(0, index);
291    }
292
293    start = record.indexOf("]", start);
294
295    return record.substring(start + 1, index).trim();
296  }
297  //--------------------------------------------------------------------------
298  //   Nested Top-Level Classes or Interfaces
299  //--------------------------------------------------------------------------
300
301}