1
17
18
19 package org.apache.juli;
20
21 import java.io.BufferedOutputStream;
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.io.OutputStreamWriter;
27 import java.io.PrintWriter;
28 import java.io.UnsupportedEncodingException;
29 import java.nio.file.DirectoryStream;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.security.AccessController;
33 import java.security.PrivilegedAction;
34 import java.sql.Timestamp;
35 import java.time.DateTimeException;
36 import java.time.LocalDate;
37 import java.time.format.DateTimeFormatter;
38 import java.time.temporal.ChronoUnit;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.ThreadFactory;
42 import java.util.concurrent.atomic.AtomicInteger;
43 import java.util.concurrent.locks.ReadWriteLock;
44 import java.util.concurrent.locks.ReentrantReadWriteLock;
45 import java.util.logging.ErrorManager;
46 import java.util.logging.Filter;
47 import java.util.logging.Formatter;
48 import java.util.logging.Handler;
49 import java.util.logging.Level;
50 import java.util.logging.LogManager;
51 import java.util.logging.LogRecord;
52 import java.util.regex.Pattern;
53
54
97 public class FileHandler extends Handler {
98
99 public static final int DEFAULT_MAX_DAYS = -1;
100 public static final int DEFAULT_BUFFER_SIZE = -1;
101
102
103 private static final ExecutorService DELETE_FILES_SERVICE =
104 Executors.newSingleThreadExecutor(new ThreadFactory() {
105 private static final String NAME_PREFIX = "FileHandlerLogFilesCleaner-";
106 private final boolean isSecurityEnabled;
107 private final ThreadGroup group;
108 private final AtomicInteger threadNumber = new AtomicInteger(1);
109
110 {
111 SecurityManager s = System.getSecurityManager();
112 if (s == null) {
113 this.isSecurityEnabled = false;
114 this.group = Thread.currentThread().getThreadGroup();
115 } else {
116 this.isSecurityEnabled = true;
117 this.group = s.getThreadGroup();
118 }
119 }
120
121 @Override
122 public Thread newThread(Runnable r) {
123 ClassLoader loader = Thread.currentThread().getContextClassLoader();
124 try {
125
126 if (isSecurityEnabled) {
127 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
128 Thread.currentThread()
129 .setContextClassLoader(getClass().getClassLoader());
130 return null;
131 });
132 } else {
133 Thread.currentThread()
134 .setContextClassLoader(getClass().getClassLoader());
135 }
136 Thread t = new Thread(group, r,
137 NAME_PREFIX + threadNumber.getAndIncrement());
138 t.setDaemon(true);
139 return t;
140 } finally {
141 if (isSecurityEnabled) {
142 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
143 Thread.currentThread().setContextClassLoader(loader);
144 return null;
145 });
146 } else {
147 Thread.currentThread().setContextClassLoader(loader);
148 }
149 }
150 }
151 });
152
153
154
155
156 public FileHandler() {
157 this(null, null, null);
158 }
159
160
161 public FileHandler(String directory, String prefix, String suffix) {
162 this(directory, prefix, suffix, null);
163 }
164
165
166 public FileHandler(String directory, String prefix, String suffix, Integer maxDays) {
167 this(directory, prefix, suffix, maxDays, null, null);
168 }
169
170
171 public FileHandler(String directory, String prefix, String suffix, Integer maxDays,
172 Boolean rotatable, Integer bufferSize) {
173 this.directory = directory;
174 this.prefix = prefix;
175 this.suffix = suffix;
176 this.maxDays = maxDays;
177 this.rotatable = rotatable;
178 this.bufferSize = bufferSize;
179 configure();
180 openWriter();
181 clean();
182 }
183
184
185
186
187
188
192 private volatile String date = "";
193
194
195
198 private String directory;
199
200
201
204 private String prefix;
205
206
207
210 private String suffix;
211
212
213
216 private Boolean rotatable;
217
218
219
222 private Integer maxDays;
223
224
225
228 private volatile PrintWriter writer = null;
229
230
231
234 protected final ReadWriteLock writerLock = new ReentrantReadWriteLock();
235
236
237
240 private Integer bufferSize;
241
242
243
247 private Pattern pattern;
248
249
250
251
252
253
258 @Override
259 public void publish(LogRecord record) {
260
261 if (!isLoggable(record)) {
262 return;
263 }
264
265
266 Timestamp ts = new Timestamp(System.currentTimeMillis());
267 String tsDate = ts.toString().substring(0, 10);
268
269 writerLock.readLock().lock();
270 try {
271
272 if (rotatable.booleanValue() && !date.equals(tsDate)) {
273
274 writerLock.readLock().unlock();
275 writerLock.writeLock().lock();
276 try {
277
278 if (!date.equals(tsDate)) {
279 closeWriter();
280 date = tsDate;
281 openWriter();
282 clean();
283 }
284 } finally {
285
286
287 writerLock.readLock().lock();
288 writerLock.writeLock().unlock();
289 }
290 }
291
292 String result = null;
293 try {
294 result = getFormatter().format(record);
295 } catch (Exception e) {
296 reportError(null, e, ErrorManager.FORMAT_FAILURE);
297 return;
298 }
299
300 try {
301 if (writer != null) {
302 writer.write(result);
303 if (bufferSize.intValue() < 0) {
304 writer.flush();
305 }
306 } else {
307 reportError("FileHandler is closed or not yet initialized, unable to log ["
308 + result + "]", null, ErrorManager.WRITE_FAILURE);
309 }
310 } catch (Exception e) {
311 reportError(null, e, ErrorManager.WRITE_FAILURE);
312 }
313 } finally {
314 writerLock.readLock().unlock();
315 }
316 }
317
318
319
320
321
322
325 @Override
326 public void close() {
327 closeWriter();
328 }
329
330 protected void closeWriter() {
331
332 writerLock.writeLock().lock();
333 try {
334 if (writer == null) {
335 return;
336 }
337 writer.write(getFormatter().getTail(this));
338 writer.flush();
339 writer.close();
340 writer = null;
341 date = "";
342 } catch (Exception e) {
343 reportError(null, e, ErrorManager.CLOSE_FAILURE);
344 } finally {
345 writerLock.writeLock().unlock();
346 }
347 }
348
349
350
353 @Override
354 public void flush() {
355
356 writerLock.readLock().lock();
357 try {
358 if (writer == null) {
359 return;
360 }
361 writer.flush();
362 } catch (Exception e) {
363 reportError(null, e, ErrorManager.FLUSH_FAILURE);
364 } finally {
365 writerLock.readLock().unlock();
366 }
367
368 }
369
370
371
374 private void configure() {
375
376 Timestamp ts = new Timestamp(System.currentTimeMillis());
377 date = ts.toString().substring(0, 10);
378
379 String className = this.getClass().getName();
380
381 ClassLoader cl = Thread.currentThread().getContextClassLoader();
382
383
384 if (rotatable == null) {
385 rotatable = Boolean.valueOf(getProperty(className + ".rotatable", "true"));
386 }
387 if (directory == null) {
388 directory = getProperty(className + ".directory", "logs");
389 }
390 if (prefix == null) {
391 prefix = getProperty(className + ".prefix", "juli.");
392 }
393 if (suffix == null) {
394 suffix = getProperty(className + ".suffix", ".log");
395 }
396
397
398 boolean shouldCheckForRedundantSeparator =
399 !rotatable.booleanValue() && !prefix.isEmpty() && !suffix.isEmpty();
400
401
402 if (shouldCheckForRedundantSeparator &&
403 (prefix.charAt(prefix.length() - 1) == suffix.charAt(0))) {
404 suffix = suffix.substring(1);
405 }
406
407 pattern = Pattern.compile("^(" + Pattern.quote(prefix) + ")\\d{4}-\\d{1,2}-\\d{1,2}("
408 + Pattern.quote(suffix) + ")$");
409
410 if (maxDays == null) {
411 String sMaxDays = getProperty(className + ".maxDays", String.valueOf(DEFAULT_MAX_DAYS));
412 try {
413 maxDays = Integer.valueOf(sMaxDays);
414 } catch (NumberFormatException ignore) {
415 maxDays = Integer.valueOf(DEFAULT_MAX_DAYS);
416 }
417 }
418
419 if (bufferSize == null) {
420 String sBufferSize = getProperty(className + ".bufferSize",
421 String.valueOf(DEFAULT_BUFFER_SIZE));
422 try {
423 bufferSize = Integer.valueOf(sBufferSize);
424 } catch (NumberFormatException ignore) {
425 bufferSize = Integer.valueOf(DEFAULT_BUFFER_SIZE);
426 }
427 }
428
429
430 String encoding = getProperty(className + ".encoding", null);
431 if (encoding != null && encoding.length() > 0) {
432 try {
433 setEncoding(encoding);
434 } catch (UnsupportedEncodingException ex) {
435
436 }
437 }
438
439
440 setLevel(Level.parse(getProperty(className + ".level", "" + Level.ALL)));
441
442
443 String filterName = getProperty(className + ".filter", null);
444 if (filterName != null) {
445 try {
446 setFilter((Filter) cl.loadClass(filterName).getConstructor().newInstance());
447 } catch (Exception e) {
448
449 }
450 }
451
452
453 String formatterName = getProperty(className + ".formatter", null);
454 if (formatterName != null) {
455 try {
456 setFormatter((Formatter) cl.loadClass(
457 formatterName).getConstructor().newInstance());
458 } catch (Exception e) {
459
460 setFormatter(new OneLineFormatter());
461 }
462 } else {
463 setFormatter(new OneLineFormatter());
464 }
465
466
467 setErrorManager(new ErrorManager());
468 }
469
470
471 private String getProperty(String name, String defaultValue) {
472 String value = LogManager.getLogManager().getProperty(name);
473 if (value == null) {
474 value = defaultValue;
475 } else {
476 value = value.trim();
477 }
478 return value;
479 }
480
481
482
485 protected void open() {
486 openWriter();
487 }
488
489 protected void openWriter() {
490
491
492 File dir = new File(directory);
493 if (!dir.mkdirs() && !dir.isDirectory()) {
494 reportError("Unable to create [" + dir + "]", null, ErrorManager.OPEN_FAILURE);
495 writer = null;
496 return;
497 }
498
499
500 writerLock.writeLock().lock();
501 FileOutputStream fos = null;
502 OutputStream os = null;
503 try {
504 File pathname = new File(dir.getAbsoluteFile(), prefix
505 + (rotatable.booleanValue() ? date : "") + suffix);
506 File parent = pathname.getParentFile();
507 if (!parent.mkdirs() && !parent.isDirectory()) {
508 reportError("Unable to create [" + parent + "]", null, ErrorManager.OPEN_FAILURE);
509 writer = null;
510 return;
511 }
512 String encoding = getEncoding();
513 fos = new FileOutputStream(pathname, true);
514 os = bufferSize.intValue() > 0 ? new BufferedOutputStream(fos, bufferSize.intValue()) : fos;
515 writer = new PrintWriter(
516 (encoding != null) ? new OutputStreamWriter(os, encoding)
517 : new OutputStreamWriter(os), false);
518 writer.write(getFormatter().getHead(this));
519 } catch (Exception e) {
520 reportError(null, e, ErrorManager.OPEN_FAILURE);
521 writer = null;
522 if (fos != null) {
523 try {
524 fos.close();
525 } catch (IOException e1) {
526
527 }
528 }
529 if (os != null) {
530 try {
531 os.close();
532 } catch (IOException e1) {
533
534 }
535 }
536 } finally {
537 writerLock.writeLock().unlock();
538 }
539 }
540
541 private void clean() {
542 if (maxDays.intValue() <= 0) {
543 return;
544 }
545 DELETE_FILES_SERVICE.submit(() -> {
546 try (DirectoryStream<Path> files = streamFilesForDelete()) {
547 for (Path file : files) {
548 Files.delete(file);
549 }
550 } catch (IOException e) {
551 reportError("Unable to delete log files older than [" + maxDays + "] days", null,
552 ErrorManager.GENERIC_FAILURE);
553 }
554 });
555 }
556
557 private DirectoryStream<Path> streamFilesForDelete() throws IOException {
558 LocalDate maxDaysOffset = LocalDate.now().minus(maxDays.intValue(), ChronoUnit.DAYS);
559 return Files.newDirectoryStream(new File(directory).toPath(), path -> {
560 boolean result = false;
561 String date = obtainDateFromPath(path);
562 if (date != null) {
563 try {
564 LocalDate dateFromFile = LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(date));
565 result = dateFromFile.isBefore(maxDaysOffset);
566 } catch (DateTimeException e) {
567
568 }
569 }
570 return result;
571 });
572 }
573
574 private String obtainDateFromPath(Path path) {
575 Path fileName = path.getFileName();
576 if (fileName == null) {
577 return null;
578 }
579 String date = fileName.toString();
580 if (pattern.matcher(date).matches()) {
581 date = date.substring(prefix.length());
582 return date.substring(0, date.length() - suffix.length());
583 } else {
584 return null;
585 }
586 }
587 }
588