1 29 30 package com.caucho.log; 31 32 import com.caucho.config.ConfigException; 33 import com.caucho.config.types.Bytes; 34 import com.caucho.config.types.Period; 35 import com.caucho.util.Alarm; 36 import com.caucho.util.L10N; 37 import com.caucho.util.QDate; 38 import com.caucho.util.ThreadPool; 39 import com.caucho.vfs.Path; 40 import com.caucho.vfs.ReadStream; 41 import com.caucho.vfs.TempStream; 42 import com.caucho.vfs.Vfs; 43 import com.caucho.vfs.WriteStream; 44 45 import java.io.IOException ; 46 import java.io.OutputStream ; 47 import java.util.ArrayList ; 48 import java.util.Collections ; 49 import java.util.regex.Matcher ; 50 import java.util.regex.Pattern ; 51 import java.util.zip.GZIPOutputStream ; 52 import java.util.zip.ZipOutputStream ; 53 54 57 public class AbstractRolloverLog { 58 protected static final L10N L = new L10N(AbstractRolloverLog.class); 59 60 private static final long HOUR = 3600L * 1000L; 62 private static final long DAY = 24L * 3600L * 1000L; 64 65 private static final long DEFAULT_ROLLOVER_SIZE = Bytes.INFINITE; 67 private static final long DEFAULT_ROLLOVER_CHECK_PERIOD = 600L * 1000L; 69 70 private String _rolloverPrefix; 72 73 private String _archiveFormat; 75 private String _archiveSuffix = ""; 77 78 private long _rolloverPeriod = Period.INFINITE; 80 81 private long _rolloverSize = DEFAULT_ROLLOVER_SIZE; 83 84 private long _rolloverCheckPeriod = DEFAULT_ROLLOVER_CHECK_PERIOD; 86 87 private int _rolloverCount; 89 90 private QDate _calendar = QDate.createLocal(); 91 92 private Path _pwd = Vfs.lookup(); 93 94 protected Path _path; 95 96 protected String _pathFormat; 97 98 private String _format; 99 100 private long _nextPeriodEnd = -1; 102 private long _nextRolloverCheckTime = -1; 103 104 private boolean _isRollingOver; 105 private Path _savedPath; 106 private TempStream _tempStream; 107 private ArchiveTask _archiveTask = new ArchiveTask(); 108 109 private WriteStream _os; 110 private WriteStream _zipOut; 111 112 115 public Path getPath() 116 { 117 return _path; 118 } 119 120 123 public void setPath(Path path) 124 { 125 _path = path; 126 } 127 128 131 public Path getPwd() 132 { 133 return _pwd; 134 } 135 136 139 public String getPathFormat() 140 { 141 return _pathFormat; 142 } 143 144 147 public void setPathFormat(String pathFormat) 148 throws ConfigException 149 { 150 _pathFormat = pathFormat; 151 152 if (pathFormat.endsWith(".zip")) { 153 throw new ConfigException(L.l(".zip extension to path-format is not supported.")); 154 } 155 } 156 157 160 public void setArchiveFormat(String format) 161 { 162 if (format.endsWith(".gz")) { 163 _archiveFormat = format.substring(0, format.length() - ".gz".length()); 164 _archiveSuffix = ".gz"; 165 } 166 else if (format.endsWith(".zip")) { 167 _archiveFormat = format.substring(0, format.length() - ".zip".length()); 168 _archiveSuffix = ".zip"; 169 } 170 else { 171 _archiveFormat = format; 172 _archiveSuffix = ""; 173 } 174 } 175 176 179 public String getArchiveFormat() 180 { 181 if (_archiveFormat == null) 182 return _rolloverPrefix + ".%Y%m%d.%H%M"; 183 else 184 return _archiveFormat; 185 } 186 187 192 public void setRolloverPeriod(Period period) 193 { 194 _rolloverPeriod = period.getPeriod(); 195 196 if (_rolloverPeriod > 0) { 197 _rolloverPeriod += 3600000L - 1; 198 _rolloverPeriod -= _rolloverPeriod % 3600000L; 199 } 200 else 201 _rolloverPeriod = Period.INFINITE; 202 } 203 204 209 public long getRolloverPeriod() 210 { 211 return _rolloverPeriod; 212 } 213 214 219 public void setRolloverSize(Bytes bytes) 220 { 221 long size = bytes.getBytes(); 222 223 if (size < 0) 224 _rolloverSize = Bytes.INFINITE; 225 else 226 _rolloverSize = size; 227 } 228 229 234 public long getRolloverSize() 235 { 236 return _rolloverSize; 237 } 238 239 244 public void setRolloverCheckPeriod(long period) 245 { 246 if (period > 1000) 247 _rolloverCheckPeriod = period; 248 else if (period > 0) 249 _rolloverCheckPeriod = 1000; 250 } 251 252 257 public long getRolloverCheckPeriod() 258 { 259 return _rolloverCheckPeriod; 260 } 261 262 265 public void setRolloverCount(int count) 266 { 267 _rolloverCount = count; 268 } 269 270 273 public void init() 274 throws IOException 275 { 276 long now = Alarm.getCurrentTime(); 277 278 _nextRolloverCheckTime = now + _rolloverCheckPeriod; 279 280 Path path = getPath(); 281 282 if (path != null) { 283 path.getParent().mkdirs(); 284 285 _rolloverPrefix = path.getTail(); 286 287 long lastModified = path.getLastModified(); 288 if (lastModified <= 0) 289 lastModified = now; 290 291 _calendar.setGMTTime(lastModified); 292 293 _nextPeriodEnd = Period.periodEnd(lastModified, getRolloverPeriod()); 294 } 295 else 296 _nextPeriodEnd = Period.periodEnd(now, getRolloverPeriod()); 297 298 if (_nextPeriodEnd < _nextRolloverCheckTime && _nextPeriodEnd > 0) 299 _nextRolloverCheckTime = _nextPeriodEnd; 300 301 if (_archiveFormat != null || getRolloverPeriod() <= 0) { 302 } 303 else if (getRolloverPeriod() % DAY == 0) 304 _archiveFormat = _rolloverPrefix + ".%Y%m%d"; 305 else if (getRolloverPeriod() % HOUR == 0) 306 _archiveFormat = _rolloverPrefix + ".%Y%m%d.%H"; 307 else 308 _archiveFormat = _rolloverPrefix + ".%Y%m%d.%H%M"; 309 310 rolloverLog(now); 311 } 312 313 public long getNextRolloverCheckTime() 314 { 315 if (_nextPeriodEnd < _nextRolloverCheckTime) 316 return _nextPeriodEnd; 317 else 318 return _nextRolloverCheckTime; 319 } 320 321 public boolean isRollover() 322 { 323 long now = Alarm.getCurrentTime(); 324 325 return _nextPeriodEnd <= now || _nextRolloverCheckTime <= now; 326 } 327 328 public boolean rollover() 329 { 330 long now = Alarm.getCurrentTime(); 331 332 if (_nextPeriodEnd <= now || _nextRolloverCheckTime <= now) { 333 rolloverLog(now); 334 return true; 335 } 336 else 337 return false; 338 } 339 340 343 protected void write(byte []buffer, int offset, int length) 344 throws IOException 345 { 346 synchronized (this) { 347 if (! _isRollingOver) { 348 if (_os == null) 349 openLog(); 350 351 if (_os != null) 352 _os.write(buffer, offset, length); 353 } 354 else { 355 if (_tempStream == null) 356 _tempStream = new TempStream(); 357 358 _tempStream.write(buffer, offset, length, false); 359 } 360 } 361 } 362 363 366 protected void flush() 367 throws IOException 368 { 369 synchronized (this) { 370 if (_os != null) 371 _os.flush(); 372 } 373 } 374 375 380 protected void rolloverLog(long now) 381 { 382 boolean isRollingOver = false; 383 384 try { 385 Path savedPath = null; 386 387 synchronized (this) { 388 if (_isRollingOver || now <= _nextRolloverCheckTime) 389 return; 390 391 _isRollingOver = isRollingOver = true; 392 393 _nextRolloverCheckTime = now + _rolloverCheckPeriod; 394 395 long lastPeriodEnd = _nextPeriodEnd; 396 _nextPeriodEnd = Period.periodEnd(now, getRolloverPeriod()); 397 398 Path path = getPath(); 399 400 if (lastPeriodEnd < now) { 401 closeLogStream(); 402 403 if (getPathFormat() == null) { 404 savedPath = getArchivePath(lastPeriodEnd - 1); 405 } 406 407 412 } 413 else if (path != null && getRolloverSize() <= path.getLength()) { 414 closeLogStream(); 415 416 if (getPathFormat() == null) { 417 savedPath = getArchivePath(now); 418 } 419 } 420 421 long nextPeriodEnd = _nextPeriodEnd; 422 if (_nextPeriodEnd < _nextRolloverCheckTime && _nextPeriodEnd > 0) 423 _nextRolloverCheckTime = _nextPeriodEnd; 424 } 425 426 if (savedPath != null) { 429 _savedPath = savedPath; 430 isRollingOver = false; 431 ThreadPool.getThreadPool().start(_archiveTask); 432 Thread.yield(); 433 } 434 } finally { 435 synchronized (this) { 436 if (isRollingOver) { 437 _isRollingOver = false; 438 flushTempStream(); 439 } 440 } 441 } 442 } 443 444 447 private void openLog() 448 { 449 closeLogStream(); 450 451 try { 452 WriteStream os = _os; 453 _os = null; 454 455 if (os != null) 456 os.close(); 457 } catch (Throwable e) { 458 } 460 461 Path path = getPath(); 462 463 if (path == null) { 464 path = getPath(Alarm.getCurrentTime()); 465 } 466 467 try { 468 if (! path.getParent().isDirectory()) 469 path.getParent().mkdirs(); 470 } catch (Throwable e) { 471 logWarning(L.l("Can't create log directory {0}", path.getParent()), e); 472 } 473 474 Exception exn = null; 475 476 for (int i = 0; i < 3 && _os == null; i++) { 477 try { 478 _os = path.openAppend(); 479 } catch (IOException e) { 480 exn = e; 481 } 482 } 483 484 String pathName = path.getPath(); 485 486 try { 487 if (pathName.endsWith(".gz")) { 488 _zipOut = _os; 489 _os = Vfs.openWrite(new GZIPOutputStream (_zipOut)); 490 } 491 else if (pathName.endsWith(".zip")) { 492 throw new ConfigException("Can't support .zip in path-format"); 493 } 494 } catch (Exception e) { 495 if (exn == null) 496 exn = e; 497 } 498 499 if (exn != null) 500 logWarning(L.l("Can't create log directory {0}", path), exn); 501 } 502 503 private void movePathToArchive(Path savedPath) 504 { 505 if (savedPath == null) 506 return; 507 508 closeLogStream(); 509 510 Path path = getPath(); 511 512 String savedName = savedPath.getTail(); 513 514 logInfo(L.l("Archiving access log to {0}.", savedName)); 515 516 try { 517 WriteStream os = _os; 518 _os = null; 519 if (os != null) 520 os.close(); 521 } catch (IOException e) { 522 } 524 525 try { 526 if (! savedPath.getParent().isDirectory()) 527 savedPath.getParent().mkdirs(); 528 } catch (Throwable e) { 529 logWarning(L.l("Can't open archive directory {0}", 530 savedPath.getParent()), 531 e); 532 } 533 534 try { 535 WriteStream os = savedPath.openWrite(); 536 OutputStream out; 537 538 if (savedName.endsWith(".gz")) 539 out = new GZIPOutputStream (os); 540 else if (savedName.endsWith(".zip")) 541 out = new ZipOutputStream (os); 542 else 543 out = os; 544 545 try { 546 path.writeToStream(out); 547 } finally { 548 try { 549 out.close(); 550 } catch (Throwable e) { 551 } 553 554 try { 555 if (out != os) 556 os.close(); 557 } catch (Throwable e) { 558 } 560 561 try { 562 if (! path.truncate()) 563 path.remove(); 564 } catch (IOException e) { 565 path.remove(); 566 567 throw e; 568 } 569 } 570 } catch (Throwable e) { 571 logWarning(L.l("Error rotating logs"), e); 572 } 573 574 if (_rolloverCount > 0) 575 removeOldLogs(); 576 } 577 578 581 private void removeOldLogs() 582 { 583 try { 584 Path path = getPath(); 585 Path parent = path.getParent(); 586 587 String []list = parent.list(); 588 589 ArrayList <String > matchList = new ArrayList <String >(); 590 591 Pattern archiveRegexp = getArchiveRegexp(); 592 for (int i = 0; i < list.length; i++) { 593 Matcher matcher = archiveRegexp.matcher(list[i]); 594 595 if (matcher.matches()) 596 matchList.add(list[i]); 597 } 598 599 Collections.sort(matchList); 600 601 if (_rolloverCount <= 0 || matchList.size() < _rolloverCount) 602 return; 603 604 for (int i = 0; i + _rolloverCount < matchList.size(); i++) { 605 try { 606 parent.lookup(matchList.get(i)).remove(); 607 } catch (Throwable e) { 608 } 609 } 610 } catch (Throwable e) { 611 } 612 } 613 614 private Pattern getArchiveRegexp() 615 { 616 StringBuilder sb = new StringBuilder (); 617 618 String archiveFormat = getArchiveFormat(); 619 620 for (int i = 0; i < archiveFormat.length(); i++) { 621 char ch = archiveFormat.charAt(i); 622 623 switch (ch) { 624 case '.': case '\\': case '*': case '?': case '+': 625 case '(': case ')': case '{': case '}': case '|': 626 sb.append("\\"); 627 sb.append(ch); 628 break; 629 case '%': 630 sb.append(".+"); 631 i++; 632 break; 633 default: 634 sb.append(ch); 635 break; 636 } 637 } 638 639 return Pattern.compile(sb.toString()); 640 } 641 642 647 protected Path getPath(long time) 648 { 649 String formatString = getPathFormat(); 650 651 if (formatString == null) 652 throw new IllegalStateException (L.l("getPath requires a format path")); 653 654 String pathString = getFormatName(formatString, time); 655 656 return getPwd().lookup(pathString); 657 } 658 659 664 protected Path getArchivePath(long time) 665 { 666 Path path = getPath(); 667 668 String archiveFormat = getArchiveFormat(); 669 670 String name = getFormatName(archiveFormat + _archiveSuffix, time); 671 Path newPath = path.getParent().lookup(name); 672 673 if (newPath.exists()) { 674 if (archiveFormat.indexOf("%H") < 0) 675 archiveFormat = archiveFormat + ".%H%M"; 676 else if (archiveFormat.indexOf("%M") < 0) 677 archiveFormat = archiveFormat + ".%M"; 678 679 for (int i = 0; i < 100; i++) { 680 String suffix; 681 682 if (i == 0) 683 suffix = _archiveSuffix; 684 else 685 suffix = "." + i + _archiveSuffix; 686 687 name = getFormatName(archiveFormat + suffix, time); 688 689 newPath = path.getParent().lookup(name); 690 691 if (! newPath.exists()) 692 break; 693 } 694 } 695 696 return newPath; 697 } 698 699 704 protected String getFormatName(String format, long time) 705 { 706 if (time <= 0) 707 time = Alarm.getCurrentTime(); 708 709 if (format != null) 710 return _calendar.formatLocal(time, format); 711 else if (getRolloverPeriod() % (24 * 3600 * 1000L) == 0) 712 return _rolloverPrefix + "." + _calendar.formatLocal(time, "%Y%m%d"); 713 else 714 return _rolloverPrefix + "." + _calendar.formatLocal(time, "%Y%m%d.%H"); 715 } 716 717 720 private void logInfo(String msg) 721 { 722 EnvironmentStream.logStderr(msg); 723 } 724 725 728 private void logWarning(String msg, Throwable e) 729 { 730 EnvironmentStream.logStderr(msg, e); 731 } 732 733 736 public synchronized void close() 737 throws IOException 738 { 739 closeLogStream(); 740 } 741 742 745 private void closeLogStream() 746 { 747 try { 748 WriteStream os = _os; 749 _os = null; 750 751 if (os != null) 752 os.close(); 753 } catch (Throwable e) { 754 } 756 757 try { 758 WriteStream zipOut = _zipOut; 759 _zipOut = null; 760 761 if (zipOut != null) 762 zipOut.close(); 763 } catch (Throwable e) { 764 } 766 } 767 768 void flushTempStream() 769 { 770 TempStream ts = _tempStream; 771 _tempStream = null; 772 773 if (ts != null) { 774 if (_os == null) 775 openLog(); 776 777 try { 778 ReadStream is = ts.openRead(true); 779 780 try { 781 is.writeToStream(_os); 782 } finally { 783 is.close(); 784 } 785 } catch (IOException e) { 786 e.printStackTrace(); 787 } 788 } 789 } 790 791 class ArchiveTask implements Runnable { 792 public void run() 793 { 794 try { 795 movePathToArchive(_savedPath); 796 } finally { 797 synchronized (this) { 799 _isRollingOver = false; 800 801 flushTempStream(); 802 } 803 } 804 } 805 } 806 } 807 | Popular Tags |