blob: ceae9a8838195e69c6d12caf10ef31aa37e37be4 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.oozie.util;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Semaphore;
import org.apache.log4j.Appender;
import org.apache.log4j.pattern.ExtrasPatternParser;
import org.apache.log4j.rolling.RollingPolicyBase;
import org.apache.log4j.rolling.RolloverDescription;
import org.apache.log4j.rolling.TimeBasedRollingPolicy;
import org.apache.log4j.rolling.TriggeringPolicy;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.pattern.LiteralPatternConverter;
import org.apache.oozie.service.Services;
import org.apache.oozie.service.XLogService;
/**
* Has the same behavior as the TimeBasedRollingPolicy. Additionally, it will delete older logs (MaxHistory determines how many
* older logs are retained).
*/
public class OozieRollingPolicy extends RollingPolicyBase implements TriggeringPolicy {
/**
* Unfortunately, TimeBasedRollingPolicy is declared final, so we can't subclass it; instead, we have to wrap it
*/
private TimeBasedRollingPolicy tbrp;
private Semaphore deleteSem;
private Thread deleteThread;
private int maxHistory = 720; // (720 hours / 24 hours per day = 30 days) as default
String oozieLogDir;
String logFileName;
public int getMaxHistory() {
return maxHistory;
}
public void setMaxHistory(int maxHistory) {
this.maxHistory = maxHistory;
}
public OozieRollingPolicy() {
deleteSem = new Semaphore(1);
deleteThread = new Thread();
tbrp = new TimeBasedRollingPolicy();
}
@SuppressWarnings("rawtypes")
public void setFileNamePattern(String fnp) {
super.setFileNamePattern(fnp);
List converters = new ArrayList();
StringBuffer file = new StringBuffer();
ExtrasPatternParser
.parse(fnp, converters, new ArrayList(), null, ExtrasPatternParser.getFileNamePatternRules());
if (!converters.isEmpty()) {
((LiteralPatternConverter) converters.get(0)).format(null, file);
File f = new File(file.toString());
oozieLogDir = f.getParent();
logFileName = f.getName();
}
else {
XLogService xls = Services.get().get(XLogService.class);
oozieLogDir = xls.getOozieLogPath();
logFileName = xls.getOozieLogName();
}
}
@Override
public void activateOptions() {
super.activateOptions();
tbrp.setFileNamePattern(getFileNamePattern());
tbrp.activateOptions();
}
@Override
public RolloverDescription initialize(String file, boolean append) throws SecurityException {
return tbrp.initialize(file, append);
}
@Override
public RolloverDescription rollover(final String activeFile) throws SecurityException {
return tbrp.rollover(activeFile);
}
@Override
public boolean isTriggeringEvent(final Appender appender, final LoggingEvent event, final String filename,
final long fileLength) {
if (maxHistory >= 0) { // -1 = disable
// Only delete old logs if we're not already deleting logs and another thread hasn't already started setting
// up to delete
// the old logs
if (deleteSem.tryAcquire()) {
if (!deleteThread.isAlive()) {
// Do the actual deleting in a new thread in case its slow so we don't bottleneck anything else
deleteThread = new Thread() {
@Override
public void run() {
deleteOldFiles();
}
};
deleteThread.start();
}
deleteSem.release();
}
}
return tbrp.isTriggeringEvent(appender, event, filename, fileLength);
}
private void deleteOldFiles() {
ArrayList<FileInfo> fileList = new ArrayList<FileInfo>();
if (oozieLogDir != null && logFileName != null) {
String[] children = new File(oozieLogDir).list();
if (children != null) {
for (String child : children) {
if (child.startsWith(logFileName) && !child.equals(logFileName)) {
File childFile = new File(new File(oozieLogDir).getAbsolutePath(), child);
if (child.endsWith(".gz")) {
long gzFileCreationTime = getGZFileCreationTime(child);
if (gzFileCreationTime != -1) {
fileList.add(new FileInfo(childFile.getAbsolutePath(), gzFileCreationTime));
}
}
else {
long modTime = childFile.lastModified();
fileList.add(new FileInfo(childFile.getAbsolutePath(), modTime));
}
}
}
}
}
if (fileList.size() > maxHistory) {
Collections.sort(fileList);
for (int i = maxHistory; i < fileList.size(); i++) {
new File(fileList.get(i).getFileName()).delete();
}
}
}
private long getGZFileCreationTime(String fileName) {
SimpleDateFormat formatter;
String date = fileName.substring(logFileName.length(), fileName.length() - 3); //3 for .gz
if (date.length() == 10) {
formatter = new SimpleDateFormat("yyyy-MM-dd");
}
else {
formatter = new SimpleDateFormat("yyyy-MM-dd-HH");
}
try {
return formatter.parse(date).getTime();
}
catch (ParseException e) {
return -1;
}
}
class FileInfo implements Comparable<FileInfo> {
String fileName;
long modTime;
public FileInfo(String fileName, long modTime) {
this.fileName = fileName;
this.modTime = modTime;
}
public String getFileName() {
return fileName;
}
public long getModTime() {
return modTime;
}
public int compareTo(FileInfo fileInfo) {
// Note: the order is the reverse of XLogStreamer.FileInfo
long diff = fileInfo.modTime - this.modTime;
if (diff > 0) {
return 1;
}
else if (diff < 0) {
return -1;
}
else {
return 0;
}
}
}
}