001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.oozie.util; 019 020 import java.io.File; 021 import java.util.ArrayList; 022 import java.util.Calendar; 023 import java.util.Collections; 024 import java.util.concurrent.Semaphore; 025 import java.util.regex.Matcher; 026 import org.apache.log4j.Appender; 027 import org.apache.log4j.rolling.RollingPolicyBase; 028 import org.apache.log4j.rolling.RolloverDescription; 029 import org.apache.log4j.rolling.TimeBasedRollingPolicy; 030 import org.apache.log4j.rolling.TriggeringPolicy; 031 import org.apache.log4j.spi.LoggingEvent; 032 import org.apache.oozie.service.Services; 033 import org.apache.oozie.service.XLogService; 034 035 /** 036 * Has the same behavior as the TimeBasedRollingPolicy. Additionally, it will delete older logs (MaxHistory determines how many 037 * older logs are retained). 038 */ 039 public class OozieRollingPolicy extends RollingPolicyBase implements TriggeringPolicy { 040 041 /** 042 * Unfortunately, TimeBasedRollingPolicy is declared final, so we can't subclass it; instead, we have to wrap it 043 */ 044 private TimeBasedRollingPolicy tbrp; 045 046 private Semaphore deleteSem; 047 048 private Thread deleteThread; 049 050 private int maxHistory = 720; // (720 hours / 24 hours per day = 30 days) as default 051 052 public int getMaxHistory() { 053 return maxHistory; 054 } 055 056 public void setMaxHistory(int maxHistory) { 057 this.maxHistory = maxHistory; 058 } 059 060 public OozieRollingPolicy() { 061 deleteSem = new Semaphore(1); 062 deleteThread = new Thread(); 063 tbrp = new TimeBasedRollingPolicy(); 064 } 065 066 @Override 067 public void activateOptions() { 068 super.activateOptions(); 069 tbrp.setFileNamePattern(getFileNamePattern()); 070 tbrp.activateOptions(); 071 } 072 073 @Override 074 public RolloverDescription initialize(String file, boolean append) throws SecurityException { 075 return tbrp.initialize(file, append); 076 } 077 078 @Override 079 public RolloverDescription rollover(final String activeFile) throws SecurityException { 080 return tbrp.rollover(activeFile); 081 } 082 083 @Override 084 public boolean isTriggeringEvent(final Appender appender, final LoggingEvent event, final String filename, 085 final long fileLength) { 086 if (maxHistory >= 0) { // -1 = disable 087 // Only delete old logs if we're not already deleting logs and another thread hasn't already started setting up to delete 088 // the old logs 089 if (deleteSem.tryAcquire()) { 090 if (!deleteThread.isAlive()) { 091 // Do the actual deleting in a new thread in case its slow so we don't bottleneck anything else 092 deleteThread = new Thread() { 093 @Override 094 public void run() { 095 deleteOldFiles(); 096 } 097 }; 098 deleteThread.start(); 099 } 100 deleteSem.release(); 101 } 102 } 103 return tbrp.isTriggeringEvent(appender, event, filename, fileLength); 104 } 105 106 private void deleteOldFiles() { 107 ArrayList<FileInfo> fileList = new ArrayList<FileInfo>(); 108 XLogService xls = getXLogService(); 109 if (xls != null) { // We need this to get the paths 110 String oozieLogPath = xls.getOozieLogPath(); 111 String logFile = xls.getOozieLogName(); 112 if (oozieLogPath != null && logFile != null) { 113 String[] children = new File(oozieLogPath).list(); 114 if (children != null) { 115 for (String child : children) { 116 if (child.startsWith(logFile) && !child.equals(logFile)) { 117 File childFile = new File(new File(oozieLogPath).getAbsolutePath(), child); 118 if (child.endsWith(".gz")) { 119 long gzFileCreationTime = getGZFileCreationTime(child); 120 if (gzFileCreationTime != -1) { 121 fileList.add(new FileInfo(childFile.getAbsolutePath(), gzFileCreationTime)); 122 } 123 } else{ 124 long modTime = childFile.lastModified(); 125 fileList.add(new FileInfo(childFile.getAbsolutePath(), modTime)); 126 } 127 } 128 } 129 } 130 } 131 } 132 133 if (fileList.size() > maxHistory) { 134 Collections.sort(fileList); 135 136 for (int i = maxHistory; i < fileList.size(); i++) { 137 new File(fileList.get(i).getFileName()).delete(); 138 } 139 } 140 } 141 142 private long getGZFileCreationTime(String fileName) { 143 // Default return value of -1 to exclude the file 144 long returnVal = -1; 145 Matcher m = XLogStreamer.gzTimePattern.matcher(fileName); 146 if (m.matches() && m.groupCount() == 4) { 147 int year = Integer.parseInt(m.group(1)); 148 int month = Integer.parseInt(m.group(2)); 149 int day = Integer.parseInt(m.group(3)); 150 int hour = Integer.parseInt(m.group(4)); 151 int minute = 0; 152 Calendar calendarEntry = Calendar.getInstance(); 153 calendarEntry.set(year, month - 1, day, hour, minute); // give month-1(Say, 7 for August) 154 long logFileStartTime = calendarEntry.getTimeInMillis(); 155 returnVal = logFileStartTime; 156 } 157 return returnVal; 158 } 159 160 class FileInfo implements Comparable<FileInfo> { 161 String fileName; 162 long modTime; 163 164 public FileInfo(String fileName, long modTime) { 165 this.fileName = fileName; 166 this.modTime = modTime; 167 } 168 169 public String getFileName() { 170 return fileName; 171 } 172 173 public long getModTime() { 174 return modTime; 175 } 176 177 public int compareTo(FileInfo fileInfo) { 178 // Note: the order is the reverse of XLogStreamer.FileInfo 179 long diff = fileInfo.modTime - this.modTime; 180 if (diff > 0) { 181 return 1; 182 } 183 else if (diff < 0) { 184 return -1; 185 } 186 else { 187 return 0; 188 } 189 } 190 } 191 192 // Needed for TestOozieRollingPolicy tests to be able to override getOozieLogPath() and getOozieLogName() 193 // by overriding getXLogService() 194 XLogService getXLogService() { 195 if (Services.get() != null) { 196 return Services.get().get(XLogService.class); 197 } 198 return null; 199 } 200 }