001: /*
002: * $Header: /export/home/cvsroot/MyPersonalizerRepository/MyPersonalizer/Subsystems/Kernel/Sources/es/udc/mypersonalizer/kernel/log/PlainTextLog.java,v 1.1.1.1 2004/03/25 12:08:38 fbellas Exp $
003: * $Revision: 1.1.1.1 $
004: * $Date: 2004/03/25 12:08:38 $
005: *
006: * =============================================================================
007: *
008: * Copyright (c) 2003, The MyPersonalizer Development Group
009: * (http://www.tic.udc.es/~fbellas/mypersonalizer/index.html) at
010: * University Of A Coruna
011: * All rights reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions are met:
015: *
016: * - Redistributions of source code must retain the above copyright notice,
017: * this list of conditions and the following disclaimer.
018: *
019: * - Redistributions in binary form must reproduce the above copyright notice,
020: * this list of conditions and the following disclaimer in the documentation
021: * and/or other materials provided with the distribution.
022: *
023: * - Neither the name of the University Of A Coruna nor the names of its
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
028: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
029: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
030: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
031: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
032: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
033: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
034: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
035: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
036: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
037: * POSSIBILITY OF SUCH DAMAGE.
038: *
039: */
040:
041: package es.udc.mypersonalizer.kernel.log;
042:
043: import java.io.File;
044: import java.io.OutputStream;
045: import java.io.FileOutputStream;
046: import java.io.PrintStream;
047: import java.io.FilenameFilter;
048: import java.io.IOException;
049: import java.util.Date;
050: import java.util.SortedMap;
051: import java.util.TreeMap;
052:
053: /**
054: * This class writes log messages and/or throwable stack traces on a file.
055: * Please see constructors for possible configurations.
056: * <p>
057: * WARNING: If no backups are allowed there will be just one log file that
058: * will be deleted each time that the maximum log size is reached. (This is
059: * not true if the maximum log size is set to -1, then the log will grow for
060: * ever).
061: *
062: * @version $Revision: 1.1.1.1 $ $Date: 2004/03/25 12:08:38 $
063: * @since 1.0
064: */
065: public class PlainTextLog implements Log {
066:
067: /** The default log size is 1 MB. */
068: private static final long DEFAULT_LOG_SIZE = 1024 * 1024;
069:
070: /** The default number of backup files is 10. */
071: private static final int DEFAULT_BACKUP_FILES = 10;
072:
073: /** The default mode is not to display messages on console. */
074: private static final boolean DEFAULT_CONSOLE_OUTPUT = false;
075:
076: /** The log file. */
077: private File file;
078:
079: /** The maximum log size in bytes. */
080: private long maxSizeBytes;
081:
082: /** The maximum number of backup files. */
083: private int maxBackupFiles;
084:
085: /**
086: * The console output state. <code>true</code> for displaying also on
087: * the console.
088: */
089: private boolean consoleOutput;
090:
091: /** The log's output stream. */
092: private OutputStream outputStream;
093:
094: /** The log's print stream. */
095: private PrintStream printStream;
096:
097: /**
098: * Constructs an instance of this class. The remaining configuration
099: * parameters are set to their default values.
100: *
101: * @param file the log file
102: * @throws IOException if an i/o exception occured
103: */
104: public PlainTextLog(File file) throws IOException {
105: this .file = file;
106: consoleOutput = DEFAULT_CONSOLE_OUTPUT;
107: maxSizeBytes = DEFAULT_LOG_SIZE;
108: maxBackupFiles = DEFAULT_BACKUP_FILES;
109:
110: openLog();
111: }
112:
113: /**
114: * Constructs an instance of this class. The remaining configuration
115: * parameters are set to their default values.
116: *
117: * @param file the log file
118: * @param consoleOutput <code>true</code> if messages are also to be
119: * displayed on the console
120: * @throws IOException if an i/o exception occured
121: */
122: public PlainTextLog(File file, boolean consoleOutput)
123: throws IOException {
124:
125: this .file = file;
126: this .consoleOutput = consoleOutput;
127: maxSizeBytes = DEFAULT_LOG_SIZE;
128: maxBackupFiles = DEFAULT_BACKUP_FILES;
129:
130: openLog();
131: }
132:
133: /**
134: * Constructs an instance of this class. The remaining configuration
135: * parameters are set to their default values.
136: *
137: * @param file the log file
138: * @param maxSizeBytes the maximum size in bytes. -1 means unlimited. 0
139: * means log inactive
140: * @param consoleOutput <code>true</code> if messages are also to be
141: * displayed on the console
142: * @throws IOException if an i/o exception occured
143: */
144: public PlainTextLog(File file, long maxSizeBytes,
145: boolean consoleOutput) throws IOException {
146:
147: this .file = file;
148: this .maxSizeBytes = maxSizeBytes;
149: this .consoleOutput = consoleOutput;
150: maxBackupFiles = DEFAULT_BACKUP_FILES;
151:
152: openLog();
153: }
154:
155: /**
156: * Constructs an instance of this class.
157: *
158: * @param file the log file
159: * @param maxSizeBytes the maximum size in bytes. -1 means unlimited. 0
160: * means log inactive
161: * @param maxBackupFiles the maximum number of backup files. -1 means
162: * unlimited. 0 means no backup files
163: * @param consoleOutput <code>true</code> if messages are also to be
164: * displayed on the console
165: * @throws IOException if an i/o exception occured
166: */
167: public PlainTextLog(File file, long maxSizeBytes,
168: int maxBackupFiles, boolean consoleOutput)
169: throws IOException {
170:
171: this .file = file;
172: this .maxSizeBytes = maxSizeBytes;
173: this .maxBackupFiles = maxBackupFiles;
174: this .consoleOutput = consoleOutput;
175:
176: openLog();
177: }
178:
179: public synchronized void write(String message, Throwable throwable,
180: Class classType) {
181:
182: if (maxSizeBytes == 0) { // inactive
183: return;
184: }
185:
186: try {
187: if (maximumLogSizeReached()) {
188: closeLog();
189: createBackupFile();
190: openLog();
191: }
192:
193: printStream.println(new Date().toString());
194: printStream.println(classType.getName());
195:
196: if (message != null) {
197: printStream.println(message);
198:
199: if (consoleOutput) {
200: System.err.println(message);
201: }
202: }
203:
204: if (throwable != null) {
205: throwable.printStackTrace(printStream);
206:
207: if (consoleOutput) {
208: throwable.printStackTrace();
209: }
210: }
211:
212: printStream.println();
213: printStream.flush(); // ensures that all data is in the file
214:
215: } catch (IOException e) {
216: System.err
217: .println("ERROR: unable to write on the log file: "
218: + file);
219: e.printStackTrace();
220: }
221: }
222:
223: /**
224: * Gets the log file.
225: *
226: * @return the log file
227: */
228: public File getFile() {
229: return file;
230: }
231:
232: /**
233: * Gets the maximum log size.
234: *
235: * @return the maximum size in bytes
236: */
237: public long getMaxSizeBytes() {
238: return maxSizeBytes;
239: }
240:
241: /**
242: * Gets the maximum number of backup files.
243: *
244: * @return the maximum size in bytes
245: */
246: public int getMaxBackupFiles() {
247: return maxBackupFiles;
248: }
249:
250: /**
251: * Gets if the messages are displayed on the console.
252: *
253: * @return <code>true</code> if messages are displayed, <code>false</code>
254: * otherwise
255: */
256: public boolean getConsoleOutput() {
257: return consoleOutput;
258: }
259:
260: /**
261: * Opens the log.
262: *
263: * @throws IOException if an i/o exception occured.
264: */
265: private void openLog() throws IOException {
266:
267: if (maxSizeBytes == 0) { // inactive
268: return;
269: }
270:
271: /* Opens the file for appending. */
272: outputStream = new FileOutputStream(file.toString(), true);
273: printStream = new PrintStream(outputStream);
274: }
275:
276: /**
277: * Closes the log.
278: *
279: * @throws IOException if an i/o exception occured.
280: */
281: private void closeLog() throws IOException {
282:
283: if (maxSizeBytes == 0) { // inactive
284: return;
285: }
286:
287: if (printStream != null) {
288: printStream.close();
289: }
290:
291: if (outputStream != null) {
292: outputStream.close();
293: }
294: }
295:
296: /**
297: * Checks if the maximum log size has been reached.
298: *
299: * @return <code>true</code> if the maximum log size has been reached
300: * @throws IOException if an i/o exception occured.
301: */
302: private boolean maximumLogSizeReached() {
303: /*
304: * Never called by the method write if inactive. If called, this
305: * method might return "true" for a inactive log.
306: */
307:
308: if (maxSizeBytes == -1) { // unlimited
309: return false;
310: }
311:
312: if (file.length() >= maxSizeBytes) {
313: return true;
314: }
315:
316: return false;
317: }
318:
319: /**
320: * Creates a backup file for the log.
321: */
322: private void createBackupFile() {
323:
324: if (maxBackupFiles == 0) { // no backups
325: file.delete();
326: return;
327: }
328:
329: /* Filter for files like <file>.log.X */
330: FilenameFilter logFilter = new LogFilenameFilter(file
331: .getParentFile(), file.getName());
332:
333: /* Sorts files in the log directory for the above filter. */
334: String[] backupFiles = file.getParentFile().list(logFilter);
335: int totalBackups = backupFiles.length;
336: SortedMap sortedBackups = new TreeMap();
337: for (int i = 0; i < totalBackups; i++) {
338: File backupFile = new File(file.getParentFile(),
339: backupFiles[i]);
340: Long backupKey = new Long(backupFile.lastModified());
341: sortedBackups.put(backupKey, backupFile);
342: }
343:
344: /* Creates the backup. */
345: int lastSequenceNumber;
346: if (totalBackups != 0) {
347: Long lastKey = (Long) sortedBackups.lastKey();
348: lastSequenceNumber = getSequenceNumber((File) sortedBackups
349: .get(lastKey));
350: } else {
351: lastSequenceNumber = 0;
352: }
353:
354: File backupFile = new File(file + "."
355: + (lastSequenceNumber + 1));
356: file.renameTo(backupFile);
357: sortedBackups.put(new Long(backupFile.lastModified()),
358: backupFile);
359: totalBackups++;
360:
361: /* Deletes excess of backups. */
362: if (maxBackupFiles != -1 // backups not limited
363: && totalBackups >= maxBackupFiles) { // limit reached
364:
365: int backupsToBeDeleted = totalBackups - maxBackupFiles;
366: Long firstKey = (Long) sortedBackups.firstKey();
367: for (int i = 0; i < backupsToBeDeleted; i++) {
368: File backupToBeDeleted = (File) sortedBackups
369: .get(firstKey);
370: backupToBeDeleted.delete();
371: sortedBackups.remove(firstKey);
372: firstKey = (Long) sortedBackups.firstKey();
373: }
374: }
375: }
376:
377: /**
378: * Gets the log file sequence number.
379: *
380: * @param file the log file
381: * @return the sequence number
382: */
383: private int getSequenceNumber(File file) {
384: String fileName = file.toString();
385: int lastIndexOfDot = fileName.lastIndexOf(".");
386: return Integer.parseInt(fileName.substring(lastIndexOfDot + 1));
387: }
388:
389: public void finalize() {
390: try {
391: closeLog();
392:
393: } catch (IOException e) {
394: e.printStackTrace();
395: }
396: }
397:
398: }
|