View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2011, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling.helper;
15  
16  import java.io.File;
17  import java.util.Date;
18  
19  import ch.qos.logback.core.CoreConstants;
20  import ch.qos.logback.core.pattern.Converter;
21  import ch.qos.logback.core.pattern.LiteralConverter;
22  import ch.qos.logback.core.spi.ContextAwareBase;
23  
24  abstract public class DefaultArchiveRemover extends ContextAwareBase implements
25          ArchiveRemover {
26  
27    static protected final long UNINITIALIZED = -1;
28    // aim for 64 days, except in case of hourly rollover
29    static protected final long INACTIVITY_TOLERANCE_IN_MILLIS = 64L * (long) CoreConstants.MILLIS_IN_ONE_DAY;
30    static final int MAX_VALUE_FOR_INACTIVITY_PERIODS = 14 * 24; // 14 days in case of hourly rollover
31  
32    final FileNamePattern fileNamePattern;
33    final RollingCalendar rc;
34    int periodOffsetForDeletionTarget;
35    final boolean parentClean;
36    long lastHeartBeat = UNINITIALIZED;
37  
38    public DefaultArchiveRemover(FileNamePattern fileNamePattern,
39                                 RollingCalendar rc) {
40      this.fileNamePattern = fileNamePattern;
41      this.rc = rc;
42      this.parentClean = computeParentCleaningFlag(fileNamePattern);
43    }
44  
45  
46    int computeElapsedPeriodsSinceLastClean(long nowInMillis) {
47      long periodsElapsed = 0;
48      if (lastHeartBeat == UNINITIALIZED) {
49        addInfo("first clean up after appender initialization");
50        periodsElapsed = rc.periodsElapsed(nowInMillis, nowInMillis + INACTIVITY_TOLERANCE_IN_MILLIS);
51        if (periodsElapsed > MAX_VALUE_FOR_INACTIVITY_PERIODS)
52          periodsElapsed = MAX_VALUE_FOR_INACTIVITY_PERIODS;
53      } else {
54        periodsElapsed = rc.periodsElapsed(lastHeartBeat, nowInMillis);
55        if (periodsElapsed < 1) {
56          addWarn("Unexpected periodsElapsed value " + periodsElapsed);
57          periodsElapsed = 1;
58        }
59      }
60      return (int) periodsElapsed;
61    }
62  
63    public void clean(Date now) {
64      long nowInMillis = now.getTime();
65      int periodsElapsed = computeElapsedPeriodsSinceLastClean(nowInMillis);
66      lastHeartBeat = nowInMillis;
67      if (periodsElapsed > 1) {
68        addInfo("periodsElapsed = " + periodsElapsed);
69      }
70      for (int i = 0; i < periodsElapsed; i++) {
71        cleanByPeriodOffset(now, periodOffsetForDeletionTarget - i);
72      }
73    }
74  
75    abstract void cleanByPeriodOffset(Date now, int periodOffset);
76  
77    boolean computeParentCleaningFlag(FileNamePattern fileNamePattern) {
78      DateTokenConverter dtc = fileNamePattern.getPrimaryDateTokenConverter();
79      // if the date pattern has a /, then we need parent cleaning
80      if (dtc.getDatePattern().indexOf('/') != -1) {
81        return true;
82      }
83      // if the literal string subsequent to the dtc contains a /, we also
84      // need parent cleaning
85  
86      Converter<Object> p = fileNamePattern.headTokenConverter;
87  
88      // find the date converter
89      while (p != null) {
90        if (p instanceof DateTokenConverter) {
91          break;
92        }
93        p = p.getNext();
94      }
95  
96      while (p != null) {
97        if (p instanceof LiteralConverter) {
98          String s = p.convert(null);
99          if (s.indexOf('/') != -1) {
100           return true;
101         }
102       }
103       p = p.getNext();
104     }
105 
106     // no /, so we don't need parent cleaning
107     return false;
108   }
109 
110   void removeFolderIfEmpty(File dir) {
111     removeFolderIfEmpty(dir, 0);
112   }
113 
114   /**
115    * Will remove the directory passed as parameter if empty. After that, if the
116    * parent is also becomes empty, remove the parent dir as well but at most 3
117    * times.
118    *
119    * @param dir
120    * @param depth
121    */
122   private void removeFolderIfEmpty(File dir, int depth) {
123     // we should never go more than 3 levels higher
124     if (depth >= 3) {
125       return;
126     }
127     if (dir.isDirectory() && FileFilterUtil.isEmptyDirectory(dir)) {
128       addInfo("deleting folder [" + dir + "]");
129       dir.delete();
130       removeFolderIfEmpty(dir.getParentFile(), depth + 1);
131     }
132   }
133 
134   public void setMaxHistory(int maxHistory) {
135     this.periodOffsetForDeletionTarget = -maxHistory - 1;
136   }
137 
138 }