1 /*
2 * Copyright (c) 2006-2007 Creative Sphere Limited.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *
10 * Creative Sphere - initial API and implementation
11 *
12 */
13 package org.abstracthorizon.danube.support.logging.util;
14
15 import java.io.BufferedOutputStream;
16 import java.io.File;
17 import java.io.FileOutputStream;
18 import java.io.IOException;
19 import java.io.OutputStream;
20 import java.util.ArrayList;
21 import java.util.Calendar;
22 import java.util.GregorianCalendar;
23
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 /**
28 * <p>Simple implementation of {@link LogFileRotator} interface. This implementation
29 * knows how to keep certain number of generations of log files,
30 * to rotate logs when size exceeds or age of file (age is calculated since file
31 * is created by an instance of this class) or at one predefined moment during the day.
32 * </p>
33 * <p>
34 * Each time this class is instantiated log rotation is enforced. That is done
35 * in the lazy manner - first call to {@link #logFile()} method will do so.
36 * </p>
37 * @author Daniel Sendula
38 */
39 public class LogFileRotatorImpl implements LogFileRotator {
40
41 /** Logger */
42 protected final Logger logger = LoggerFactory.getLogger(getClass());
43
44 /** Cached output stream */
45 protected OutputStream cachedOutputStream;
46
47 /** Log file */
48 protected File logFile;
49
50 /** Log file path */
51 protected File logPath;
52
53 /** Number of generations. -1 if unlimited or 0 if no generations of the file to be kept */
54 private int numberOfGenerations = -1;
55
56 /** Maximum size of the file in bytes or -1 if no maximum is defined */
57 private long maxSize = -1;
58
59 /** Maximum age of the fime in milliseconds or -1 if no maximum age is defined */
60 private long maxAge = -1;
61
62 /** Time of the day in the form of a calendar. May be null */
63 private Calendar timeOfDay = null;
64
65 /**
66 * Amount of time before check for log rotation is made. This is performance optimisation.
67 * Default is 100
68 */
69 private int checkDelayMillis = 100;
70
71 /** When is this object lass accessed through {@link #logFile()} method. */
72 protected long lastAccessed;
73
74 /** When is scheduled next rotation based on the time of the day or 0 if none */
75 protected long nextRotate;
76
77 /** When is log file created */
78 protected long logFileCreated;
79
80 /** Buffer len for output file or -1 if none. */
81 private int bufferLen = -1;
82
83 /**
84 * Constructor
85 */
86 public LogFileRotatorImpl() {
87 }
88
89 /**
90 * Constructor
91 *
92 * @param logFile log file handle
93 */
94 public LogFileRotatorImpl(File logFile) {
95 this.setLogFile(logFile);
96 }
97
98 /**
99 * This method first checks if we have accessed it too quickly since
100 * last time according to the {@link #checkDelayMillis} field. If so
101 * check should log be rotated or not wont be done.
102 *
103 * @returns output stream of the log file
104 * @throws IOException io exception
105 */
106 public synchronized OutputStream logFile() throws IOException {
107 if ((System.currentTimeMillis() - lastAccessed) > checkDelayMillis) {
108 lastAccessed = System.currentTimeMillis();
109 check();
110 }
111 return cachedOutputStream;
112 }
113
114 /**
115 * Returns file handle of the log file.
116 * @return file handle of the log file
117 */
118 public File getLogFile() {
119 return logFile;
120 }
121
122 /**
123 * Sets file handle of the log file
124 * @param logFile file handle of the log file
125 */
126 public void setLogFile(File logFile) {
127 this.logFile = logFile;
128 this.logPath = logFile.getParentFile();
129 }
130
131 /**
132 * Returns the direcotry of log file.
133 * .
134 * @return the direcotry of log file
135 * @see #setLogDirectory(File)
136 */
137 public File getLogDirectory() {
138 return logPath;
139 }
140
141 /**
142 * Sets log directory. This is only convenience setter to be used
143 * in conjuction with {@link #setLogFileName(String)} instead
144 * of {@link #setLogFile(File)}. There is no
145 * need using these two (this and {@link #setLogFileName(String)} in
146 * log file is set through {@link #setLogFile(File)}.
147 * Settin only log directory will assume log file name
148 * as "access.log". If you want
149 * to set log directory and use different name use {@link #setLogFileName(String)} to
150 * set the name after you have
151 * set the directory using this method
152 *
153 * @param logPath log file directory
154 */
155 public void setLogDirectory(File logPath) {
156 this.logPath = logPath;
157 this.logFile = new File(logPath, "access.log");
158 }
159
160 /**
161 * Returns last part of log file's path
162 * @return last part of log file's path
163 */
164 public String getLogFileName() {
165 return logFile.getName();
166 }
167
168 /**
169 * Sets last part of a path of log file. Seet {@link #setLogDirectory(File)} for more explanations.
170 * @param name
171 */
172 public void setLogFileName(String name) {
173 this.logFile = new File(logPath, name);
174 }
175
176 /**
177 * Returns maximum age of the file.
178 *
179 * @see #maxAge
180 * @return maximum age of the file
181 */
182 public long getMaxAge() {
183 return maxAge;
184 }
185
186 /**
187 * Sets maximum age of the file
188 *
189 * @see #maxAge
190 * @param maxAge maximum age of the file
191 */
192 public void setMaxAge(long maxAge) {
193 this.maxAge = maxAge;
194 }
195
196
197 /**
198 * Returns maximum size of the file.
199 *
200 * @see #maxSize
201 * @return maximum size of the file
202 */
203 public long getMaxSize() {
204 return maxSize;
205 }
206
207 /**
208 * Sets maximum size of the file
209 *
210 * @see #maxSize
211 * @param maxAge maximum size of the file
212 */
213 public void setMaxSize(long maxSize) {
214 this.maxSize = maxSize;
215 }
216
217 /**
218 * Returns defined number of generations
219 *
220 * @see #numberOfGenerations
221 * @return defined number of generations
222 */
223 public int getNumberOfGenerations() {
224 return numberOfGenerations;
225 }
226
227 /**
228 * Sets defined number of generations
229 *
230 * @see #numberOfGenerations
231 * @param numberOfGeneration defined number of generations
232 */
233 public void setNumberOfGenerations(int numberOfGeneration) {
234 this.numberOfGenerations = numberOfGeneration;
235 }
236
237 /**
238 * Returns time of day when rotation is going to happen.
239 *
240 * @see #timeOfDay
241 * @return time of day when rotation is going to happen
242 */
243 public Calendar getTimeOfDay() {
244 return timeOfDay;
245 }
246
247 /**
248 * Sets time of day when rotation is going to happen
249 *
250 * @see #timeOfDay
251 * @param timeOfDay time of day when rotation is going to happen
252 */
253 public void setTimeOfDay(Calendar timeOfDay) {
254 this.timeOfDay = timeOfDay;
255 if (timeOfDay == null) {
256 nextRotate = 0;
257 } else {
258 GregorianCalendar cal = new GregorianCalendar();
259 cal.set(Calendar.HOUR, timeOfDay.get(Calendar.HOUR));
260 cal.set(Calendar.MINUTE, timeOfDay.get(Calendar.MINUTE));
261 cal.set(Calendar.SECOND, timeOfDay.get(Calendar.SECOND));
262 cal.set(Calendar.MILLISECOND, timeOfDay.get(Calendar.MILLISECOND));
263 cal.add(Calendar.DAY_OF_YEAR, 1);
264 nextRotate = cal.getTimeInMillis();
265 }
266 }
267
268 /**
269 * Returns check delay in milliseconds
270 *
271 * @see #checkDelayMillis
272 * @return check delay in milliseconds
273 */
274 public int getCheckDelayMillis() {
275 return checkDelayMillis;
276 }
277
278 /**
279 * Sets check delay in milliseconds
280 *
281 * @see #checkDelayMillis
282 * @param checkDelayMillis check delay in milliseconds
283 */
284 public void setCheckDelayMillis(int checkDelayMillis) {
285 this.checkDelayMillis = checkDelayMillis;
286 }
287
288 /**
289 * Returns file buffer len
290 *
291 * @see #bufferLen
292 * @return file buffer len
293 */
294 public int getBufferLen() {
295 return bufferLen;
296 }
297
298 /**
299 * Sets file buffer len
300 *
301 * @see #bufferLen
302 * @param bufferLen file buffer len
303 */
304 public void setBufferLen(int bufferLen) {
305 this.bufferLen = bufferLen;
306 }
307
308 /**
309 * Checks if file needs to be rotated
310 *
311 * @throws IOException io exception
312 */
313 protected void check() throws IOException {
314 boolean doRotate = false;
315 if (cachedOutputStream == null) {
316 doRotate = true;
317 }
318 if (!doRotate) {
319 long maxAge = getMaxAge();
320 if ((maxAge > 0) && ((lastAccessed - logFileCreated) > maxAge)) {
321 doRotate = true;
322 }
323 }
324 if (!doRotate) {
325 long maxSize = getMaxSize();
326 if ((maxSize > 0) && (getLogFile().length()) > maxSize) {
327 doRotate = true;
328 }
329 }
330 if (!doRotate) {
331 if ((nextRotate > 0) && (lastAccessed > nextRotate)) {
332 doRotate = true;
333 GregorianCalendar cal = new GregorianCalendar();
334 cal.setTimeInMillis(nextRotate);
335 cal.add(Calendar.DAY_OF_YEAR, 1);
336 nextRotate = cal.getTimeInMillis();
337 }
338 }
339 if (doRotate) {
340 rotate();
341 }
342 }
343
344 /**
345 * Rotates the log files
346 *
347 * @throws IOException io exception
348 */
349 public void rotate() throws IOException {
350 if (cachedOutputStream != null) {
351 try {
352 cachedOutputStream.close();
353 } catch (IOException e) {
354 logger.error("Failed to close previous file", e);
355 }
356 }
357 ArrayList<File> files = new ArrayList<File>();
358 int i = 1;
359 File f = createFileName(i);
360 while (f.exists()) {
361 if ((numberOfGenerations >= 0) && (i >= numberOfGenerations)) {
362 f.delete();
363 }
364 if ((numberOfGenerations < 0) || (i <= numberOfGenerations)) {
365 files.add(f);
366 }
367 i++;
368 f = createFileName(i);
369 }
370 if ((numberOfGenerations < 0) || (i <= numberOfGenerations)) {
371 files.add(createFileName(i));
372 i++;
373 }
374 i = i - 1;
375 if (i > 1) {
376 for (int j = i-1; j >= 1; j--) {
377 File last = files.get(j);
378 File second = files.get(j-1);
379 second.renameTo(last);
380 }
381 }
382 if (numberOfGenerations != 0) {
383 getLogFile().renameTo(createFileName(1));
384 } else {
385 getLogFile().delete();
386 }
387 createLogFile();
388 }
389
390 /**
391 * Creates a file name from given generation number.
392 * If 0 is supplied for generation number, then {@link #logFile} i sreturned
393 *
394 * @param g generation number
395 * @return a file name from given generation number
396 */
397 protected File createFileName(int g) {
398 if (g == 0) {
399 return getLogFile();
400 } else {
401 File p = getLogFile().getParentFile();
402 if (p == null) {
403 p = new File(".");
404 }
405 String name = getLogFile().getName();
406 int i = name.lastIndexOf('.');
407 if (i >= 0) {
408 name = name.substring(0, i) + "." + g + name.substring(i);
409 } else {
410 name = name + "." + g;
411 }
412 return new File(p, name);
413 }
414 }
415
416 /**
417 * Creates log file
418 *
419 * @throws IOException io exception
420 */
421 protected void createLogFile() throws IOException {
422 File file = getLogFile();
423 FileOutputStream fos = new FileOutputStream(file);
424 if (getBufferLen() > 0) {
425 BufferedOutputStream out = new BufferedOutputStream(fos, getBufferLen());
426 cachedOutputStream = out;
427 } else {
428 cachedOutputStream = fos;
429 }
430 logFileCreated = file.lastModified();
431 }
432 }