1 package org.grlea.log.rollover; 2 3 6 18 import java.io.BufferedReader ; 19 import java.io.File ; 20 import java.io.FileOutputStream ; 21 import java.io.FileReader ; 22 import java.io.FileWriter ; 23 import java.io.IOException ; 24 import java.io.RandomAccessFile ; 25 import java.io.StringWriter ; 26 import java.io.Writer ; 27 import java.nio.channels.Channels ; 28 import java.nio.channels.FileChannel ; 29 import java.text.Format ; 30 import java.text.MessageFormat ; 31 import java.text.ParseException ; 32 import java.util.Arrays ; 33 import java.util.Collections ; 34 import java.util.Date ; 35 import java.util.Properties ; 36 import java.util.Timer ; 37 import java.util.TimerTask ; 38 39 49 public class 50 RolloverManager 51 extends Writer 52 { 53 54 private static final String KEY_PREFIX = "simplelog."; 55 56 57 private static final String KEY_ROLLOVER_STRATEGY = KEY_PREFIX + "rollover"; 58 59 60 private static final String ROLLOVER_STRATEGY_FILESIZE = "fileSize"; 61 62 63 private static final String ROLLOVER_STRATEGY_TIMEOFDAY = "timeOfDay"; 64 65 66 private static final String KEY_ACTIVE_LOG_FILE = KEY_PREFIX + "logFile"; 67 68 69 private static final String KEY_ROLLOVER_LOG_FILE = KEY_PREFIX + "rollover.filename"; 70 71 72 private static final String KEY_ROLLOVER_DIRECTORY = KEY_PREFIX + "rollover.directory"; 73 74 75 private static final String KEY_ROLLOVER_PERIOD = KEY_PREFIX + "rollover.period"; 76 77 78 private static final String DEFAULT_ROLLOVER_PERIOD = "60"; 79 80 84 private final Object PRINTERS_SEMAPHORE = new Object (); 85 86 87 private int printerCount = 0; 88 89 94 private final Object WRITER_CHANGE_GATE = new Object (); 95 96 97 private Writer writer; 98 99 100 private StringWriter tempWriter = new StringWriter (1024); 101 102 103 private RolloverStrategy strategy; 104 105 106 private String currentRolloverStrategyName; 107 108 109 private File currentActiveLogFile; 110 111 112 private File currentActiveLogFileDirectory; 113 114 115 private File rolloverDirectory; 116 117 118 private MessageFormat rolloverLogFileFormat; 119 120 121 private RandomAccessFile fileOut; 122 123 124 private boolean strategySetProgramatically = false; 125 126 127 private long rolloverPeriod; 128 129 130 private Timer timer; 131 132 133 private int uniqueFileId = 0; 134 135 136 private ErrorReporter errorReporter; 137 138 153 public 154 RolloverManager(Properties properties, ErrorReporter errorReporter) 155 throws IOException 156 { 157 if (properties == null) 158 throw new IllegalArgumentException ("properties cannot be null."); 159 160 this.errorReporter = errorReporter; 161 configure(properties); 162 } 163 164 172 public void 173 configure(Properties properties) 174 throws IOException 175 { 176 configureStrategy(properties); 177 configureWriter(properties); 178 } 179 180 183 private void 184 configureStrategy(Properties properties) 185 throws IOException 186 { 187 if (strategySetProgramatically) 188 { 189 return; 190 } 191 192 String rolloverStrategyString = properties.getProperty(KEY_ROLLOVER_STRATEGY); 194 195 if (rolloverStrategyString == null) 196 { 197 throw new IOException ("RolloverManger created, but rollover property not specified."); 198 } 199 200 rolloverStrategyString = rolloverStrategyString.trim(); 201 if (rolloverStrategyString.length() == 0) 202 { 203 throw new IOException ("RolloverManger created, but rollover property not specified."); 204 } 205 206 boolean strategyChanged = !rolloverStrategyString.equals(currentRolloverStrategyName); 207 RolloverStrategy newStrategy; 208 if (strategyChanged) 209 { 210 try 211 { 212 if (ROLLOVER_STRATEGY_FILESIZE.equals(rolloverStrategyString)) 213 { 214 newStrategy = new FileSizeRolloverStrategy(); 215 } 216 else if (ROLLOVER_STRATEGY_TIMEOFDAY.equals(rolloverStrategyString)) 217 { 218 newStrategy = new TimeOfDayRolloverStrategy(); 219 } 220 else 221 { 222 try 223 { 224 Class strategyClass = Class.forName(rolloverStrategyString); 225 Object strategyObject = strategyClass.newInstance(); 226 if (!(strategyObject instanceof RolloverStrategy)) 227 { 228 throw new IOException (strategyClass.getName() + " is not a RolloverStrategy"); 229 } 230 231 newStrategy = (RolloverStrategy) strategyObject; 232 } 233 catch (ClassNotFoundException e) 234 { 235 throw new IOException ("Class '" + rolloverStrategyString + "' not found: " + e); 236 } 237 catch (InstantiationException e) 238 { 239 throw new IOException ("Failed to create an instance of " + 240 rolloverStrategyString + ": " + e); 241 } 242 catch (IllegalAccessException e) 243 { 244 throw new IOException ("Failed to create an instance of " + 245 rolloverStrategyString + ": " + e); 246 } 247 } 248 } 249 catch (IOException e) 250 { 251 throw new IOException ("Error creating RolloverStrategy: " + e); 252 } 253 } 254 else 255 { 256 newStrategy = strategy; 257 } 258 259 try 260 { 261 newStrategy.configure(Collections.unmodifiableMap(properties)); 262 } 263 catch (IOException e) 264 { 265 throw new IOException ("Error configuring RolloverStrategy: " + e); 266 } 267 268 this.strategy = newStrategy; 269 currentRolloverStrategyName = rolloverStrategyString; 270 } 271 272 275 private void 276 configureWriter(Properties properties) 277 throws IOException 278 { 279 String newActiveLogFileName = properties.getProperty(KEY_ACTIVE_LOG_FILE); 281 if (newActiveLogFileName == null) 282 throw new IOException ("RolloverManager created but no active log file name specified."); 283 284 File newActiveLogFile = new File (newActiveLogFileName.trim()).getAbsoluteFile(); 286 287 if (newActiveLogFile.isDirectory()) 288 throw new IOException ("The specified active log file name already exists as a directory."); 289 290 File newActiveLogFileDirectory = newActiveLogFile.getParentFile(); 291 if (newActiveLogFileDirectory != null) 292 newActiveLogFileDirectory.mkdirs(); 293 else 294 throw new IOException ("Caanot access the active log file's parent directory."); 295 296 String newRolloverLogFileNamePattern = properties.getProperty(KEY_ROLLOVER_LOG_FILE); 298 if (newRolloverLogFileNamePattern == null) 299 newRolloverLogFileNamePattern = "{1}-" + newActiveLogFile.getName(); 300 301 newRolloverLogFileNamePattern = newRolloverLogFileNamePattern.trim(); 302 303 MessageFormat newRolloverLogFileNameFormat; 305 try 306 { 307 newRolloverLogFileNameFormat = new MessageFormat (newRolloverLogFileNamePattern); 308 } 309 catch (IllegalArgumentException e) 310 { 311 throw new IOException ("Illegal pattern provided for rollover log file name: " + 312 e.getMessage()); 313 } 314 315 String newRolloverDirectoryName = properties.getProperty(KEY_ROLLOVER_DIRECTORY); 317 File newRolloverDirectory; 318 if (newRolloverDirectoryName != null) 319 newRolloverDirectory = new File (newRolloverDirectoryName.trim()).getAbsoluteFile(); 320 else 321 newRolloverDirectory = newActiveLogFileDirectory; 322 323 if (newRolloverDirectory.exists() && !newRolloverDirectory.isDirectory()) 324 { 325 throw new IOException ( 326 "The location specified for storing rolled log files is not a directory: " + 327 newRolloverDirectory); 328 } 329 330 String rolloverPeriodString = (String ) properties.get(KEY_ROLLOVER_PERIOD); 332 if (rolloverPeriodString == null) 333 rolloverPeriodString = DEFAULT_ROLLOVER_PERIOD; 334 335 rolloverPeriodString = rolloverPeriodString.trim(); 336 if (rolloverPeriodString.length() == 0) 337 rolloverPeriodString = DEFAULT_ROLLOVER_PERIOD; 338 339 long newRolloverPeriod; 340 try 341 { 342 newRolloverPeriod = Long.parseLong(rolloverPeriodString); 343 if (newRolloverPeriod < 1) 344 throw new NumberFormatException ("Must be greater than 0"); 345 } 346 catch (NumberFormatException e) 347 { 348 throw new IOException ("Invalid rollover period specified: " + rolloverPeriodString + " (" + 349 e.getMessage() + ")"); 350 } 351 352 Format [] formats = newRolloverLogFileNameFormat.getFormatsByArgumentIndex(); 354 boolean uniqueIdUsedInPattern = formats.length > 1; 355 if (uniqueIdUsedInPattern) 356 { 357 String [] filenames = newRolloverDirectory.list(); 358 int maximumFileId = 0; 359 for (int i = 0; filenames != null && i < filenames.length; i++) 360 { 361 String filename = filenames[i]; 362 try 363 { 364 Object [] parameters = newRolloverLogFileNameFormat.parse(filename); 365 if (parameters.length > 1 && parameters[1] != null) 366 { 367 String uniqueFileIdString = (String ) parameters[1]; 368 int parsedFileId = Integer.parseInt(uniqueFileIdString, 16); 369 if (parsedFileId > maximumFileId) 370 maximumFileId = parsedFileId; 371 } 372 } 373 catch (NumberFormatException e) 374 { 375 } 378 catch (ParseException e) 379 { 380 } 382 } 383 uniqueFileId = maximumFileId + 1; 384 } 385 386 rolloverDirectory = newRolloverDirectory; 387 rolloverLogFileFormat = newRolloverLogFileNameFormat; 388 389 if (!newActiveLogFile.equals(currentActiveLogFile)) 391 { 392 openWriter(newActiveLogFile, newActiveLogFileDirectory); 393 currentActiveLogFile = newActiveLogFile; 394 currentActiveLogFileDirectory = newActiveLogFileDirectory; 395 } 396 397 if (timer == null || newRolloverPeriod != rolloverPeriod) 399 { 400 if (timer != null) 401 timer.cancel(); 402 403 timer = new Timer (true); 404 timer.schedule(new RolloverTask(), 0, newRolloverPeriod * 1000L); 405 rolloverPeriod = newRolloverPeriod; 406 } 407 } 408 409 417 private void 418 openWriter(File newActiveLogFile, File activeLogFileDirectory) 419 throws IOException 420 { 421 synchronized (WRITER_CHANGE_GATE) 422 { 423 synchronized (PRINTERS_SEMAPHORE) 424 { 425 if (printerCount != 0) 426 { 427 try 428 { 429 PRINTERS_SEMAPHORE.wait(); 430 } 431 catch (InterruptedException e) 432 { 433 throw new IllegalStateException ("Interrupted while attempting to configure writer"); 434 } 435 } 436 437 if (writer != tempWriter) 438 { 439 if (writer != null) 440 { 441 try 442 { 443 writer.close(); 444 } 445 catch (IOException e) 446 { 447 throw new IOException ("Failed to close open file: " + e); 448 } 449 } 450 451 RandomAccessFile newFileOut = new RandomAccessFile (newActiveLogFile, "rws"); 452 FileChannel channel = newFileOut.getChannel(); 453 long initialChannelSize = channel.size(); 454 newFileOut.seek(initialChannelSize); 455 Writer newFileWriter = Channels.newWriter(newFileOut.getChannel(), "UTF-8"); 456 storeFileCreationTimeIfNecessary(activeLogFileDirectory, newActiveLogFile); 458 fileOut = newFileOut; 459 writer = newFileWriter; 460 } 461 } 462 } 463 } 464 465 473 private void 474 storeFileCreationTimeIfNecessary(File activeLogFileDirectory, File newActiveLogFile) 475 throws IOException 476 { 477 long creationTime = System.currentTimeMillis(); 478 479 try 480 { 481 File creationTimeFile = getCreationTimeFile(activeLogFileDirectory, newActiveLogFile); 482 483 boolean recordCreationTime = !creationTimeFile.exists(); 485 if (recordCreationTime) 486 { 487 FileWriter creationTimeFileOut = new FileWriter (creationTimeFile); 488 creationTimeFileOut.write(String.valueOf(creationTime)); 489 creationTimeFileOut.close(); 490 } 491 } 492 catch (IOException e) 493 { 494 throw new IOException ("Failed to write file creation time: " + e); 495 } 496 } 497 498 508 private long 509 readCreationTime() 510 throws IOException 511 { 512 try 513 { 514 File creationTimeFile = 515 getCreationTimeFile(currentActiveLogFileDirectory, currentActiveLogFile); 516 517 if (!creationTimeFile.exists()) 518 storeFileCreationTimeIfNecessary(currentActiveLogFileDirectory, currentActiveLogFile); 519 520 FileReader fileIn = new FileReader (creationTimeFile); 521 BufferedReader in = new BufferedReader (fileIn); 522 String firstLine = in.readLine(); 523 in.close(); 524 return Long.parseLong(firstLine); 525 } 526 catch (IOException e) 527 { 528 throw new IOException ("Error reading creation time file: " + e); 529 } 530 catch (NumberFormatException e) 531 { 532 throw new IOException ("Error reading creation time: " + e); 533 } 534 } 535 536 546 private File 547 getCreationTimeFile(File activeLogFileDirectory, File newActiveLogFile) 548 { 549 return new File (activeLogFileDirectory, newActiveLogFile.getName() + "-CREATED"); 550 } 551 552 553 558 public void 559 close() 560 { 561 synchronized (WRITER_CHANGE_GATE) 562 { 563 synchronized (PRINTERS_SEMAPHORE) 564 { 565 if (printerCount != 0) 566 { 567 try 568 { 569 PRINTERS_SEMAPHORE.wait(); 570 } 571 catch (InterruptedException e) 572 { 573 throw new IllegalStateException ("Interrupted while attempting to configure writer"); 574 } 575 } 576 577 writer = tempWriter; 578 } 579 580 timer.cancel(); 581 582 try 583 { 584 writer.close(); 585 } 586 catch (IOException e) 587 { 588 reportError("Error closing RolloverManager's writer", e, true); 589 } 590 591 try 592 { 593 fileOut.close(); 594 } 595 catch (IOException e) 596 { 597 reportError("Error closing RolloverManager's file", e, true); 598 } 599 } 600 } 601 602 612 private void 613 reportError(String description, IOException e, boolean printExceptionType) 614 { 615 if (errorReporter != null) 616 errorReporter.error(description, e, printExceptionType); 617 } 618 619 622 public void 623 flush() 624 { 625 } 626 627 632 public void 633 write(char cbuf[], int off, int len) 634 throws IOException 635 { 636 synchronized (WRITER_CHANGE_GATE) 639 {} 640 641 synchronized (PRINTERS_SEMAPHORE) 643 { 644 printerCount++; 645 } 646 647 try 648 { 649 writer.write(cbuf, off, len); 650 writer.flush(); 651 } 652 finally 653 { 654 synchronized (PRINTERS_SEMAPHORE) 656 { 657 printerCount--; 658 if (printerCount == 0) 659 { 660 PRINTERS_SEMAPHORE.notifyAll(); 661 } 662 } 663 } 664 } 665 666 672 public void 673 rolloverIfNecessary() 674 throws IOException 675 { 676 long creationTime = readCreationTime(); 677 FileChannel activeFileChannel = fileOut.getChannel(); 678 long fileLength = activeFileChannel.size(); 679 Date creationDate = new Date (creationTime); 680 681 boolean rolloverNow = strategy.rolloverNow(creationDate, fileLength); 682 683 if (rolloverNow) 684 { 685 synchronized (WRITER_CHANGE_GATE) 687 { 688 synchronized (PRINTERS_SEMAPHORE) 689 { 690 if (printerCount != 0) 691 { 692 try 693 { 694 PRINTERS_SEMAPHORE.wait(); 695 } 696 catch (InterruptedException e) 697 { 698 throw new IllegalStateException ("Interrupted while attempting to configure writer"); 699 } 700 } 701 702 writer = tempWriter; 703 } 704 } 705 706 int fileIdNumber = uniqueFileId++; 708 StringBuffer fileIdString = new StringBuffer (); 709 fileIdString.append(Integer.toHexString(fileIdNumber).toUpperCase()); 710 char[] zeroes = new char[8 - fileIdString.length()]; 711 Arrays.fill(zeroes, '0'); 712 fileIdString.insert(0, zeroes); 713 String rolloverFileName = 714 rolloverLogFileFormat.format(new Object [] {new Date (), fileIdString.toString()}); 715 if (!rolloverDirectory.exists()) 716 rolloverDirectory.mkdirs(); 717 File rolloverFile = new File (rolloverDirectory, rolloverFileName); 718 FileOutputStream rolloverOut = new FileOutputStream (rolloverFile, false); 719 720 FileChannel rolloverChannel = rolloverOut.getChannel(); 722 activeFileChannel.transferTo(0, activeFileChannel.size(), rolloverChannel); 723 724 rolloverOut.close(); 726 727 activeFileChannel.truncate(0); 729 fileOut.seek(0); 730 Writer newFileWriter = Channels.newWriter(activeFileChannel, "UTF-8"); 731 732 File creationTimeFile = 734 getCreationTimeFile(currentActiveLogFileDirectory, currentActiveLogFile); 735 creationTimeFile.delete(); 736 storeFileCreationTimeIfNecessary(currentActiveLogFileDirectory, currentActiveLogFile); 737 738 synchronized (WRITER_CHANGE_GATE) 740 { 741 synchronized (PRINTERS_SEMAPHORE) 742 { 743 if (printerCount != 0) 744 { 745 try 746 { 747 PRINTERS_SEMAPHORE.wait(); 748 } 749 catch (InterruptedException e) 750 { 751 throw new IllegalStateException ("Interrupted while attempting to configure writer"); 752 } 753 } 754 755 writer = newFileWriter; 757 StringBuffer tempBuffer = tempWriter.getBuffer(); 759 newFileWriter.write(tempBuffer.toString()); 760 tempBuffer.delete(0, tempBuffer.length()); 761 } 762 } 763 } 764 } 765 766 770 public RolloverStrategy 771 getStrategy() 772 { 773 return strategy; 774 } 775 776 785 public void 786 setStrategy(RolloverStrategy strategy) 787 { 788 if (strategy == null) 789 { 790 throw new IllegalArgumentException ("strategy cannot be null."); 791 } 792 793 this.strategy = strategy; 794 strategySetProgramatically = true; 795 } 796 797 800 private final class 801 RolloverTask 802 extends TimerTask 803 { 804 public void 805 run() 806 { 807 try 808 { 809 rolloverIfNecessary(); 810 } 811 catch (IOException e) 812 { 813 reportError("SimpleLog ERROR: Failed to check or attempt rollover", e, true); 814 } 815 } 816 } 817 818 public static Writer 819 createRolloverManager(Properties properties, ErrorReporter errorReporter) 820 throws IOException 821 { 822 return new RolloverManager(properties, errorReporter); 823 } 824 825 829 public static interface 830 ErrorReporter 831 { 832 public void 833 error(String description, Throwable t, boolean printExceptionType); 834 } 835 } | Popular Tags |