blob: 494cfd5aeea6b7111bc6336f5343f31c4733e954 [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.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.oozie.service.ConfigurationService;
import org.apache.oozie.util.LogLine.MATCHED_PATTERN;
import com.google.common.annotations.VisibleForTesting;
/**
* Filter that will construct the regular expression that will be used to filter the log statement. And also checks if
* the given log message go through the filter. Filters that can be used are logLevel(Multi values separated by "|")
* jobId appName actionId token
*/
public class XLogFilter {
private static final int LOG_TIME_BUFFER = 2; // in min
public static String MAX_ACTIONLIST_SCAN_DURATION = "oozie.service.XLogStreamingService.actionlist.max.log.scan.duration";
public static String MAX_SCAN_DURATION = "oozie.service.XLogStreamingService.max.log.scan.duration";
private Map<String, Integer> logLevels;
private final Map<String, String> filterParams;
private static List<String> parameters = new ArrayList<String>();
private boolean noFilter;
private Pattern filterPattern;
private XLogUserFilterParam userLogFilter;
private Date endDate;
private Date startDate;
private boolean isActionList = false;
private String formattedEndDate;
private String formattedStartDate;
private String truncatedMessage;
// TODO Patterns to be read from config file
private static final String DEFAULT_REGEX = "[^\\]]*";
public static final String ALLOW_ALL_REGEX = "(.*)";
private static final String TIMESTAMP_REGEX = "(\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d,\\d\\d\\d)";
private static final String WHITE_SPACE_REGEX = "\\s+";
private static final String LOG_LEVEL_REGEX = "(\\w+)";
static final String PREFIX_REGEX = TIMESTAMP_REGEX + WHITE_SPACE_REGEX + LOG_LEVEL_REGEX
+ WHITE_SPACE_REGEX;
private static final Pattern SPLITTER_PATTERN = Pattern.compile(PREFIX_REGEX + ALLOW_ALL_REGEX);
public XLogFilter() {
this(new XLogUserFilterParam());
}
public XLogFilter(XLogUserFilterParam userLogFilter) {
filterParams = new HashMap<String, String>();
for (int i = 0; i < parameters.size(); i++) {
filterParams.put(parameters.get(i), DEFAULT_REGEX);
}
logLevels = null;
noFilter = true;
filterPattern = null;
setUserLogFilter(userLogFilter);
}
public void setLogLevel(String logLevel) {
if (logLevel != null && logLevel.trim().length() > 0) {
this.logLevels = new HashMap<String, Integer>();
String[] levels = logLevel.split("\\|");
for (int i = 0; i < levels.length; i++) {
String s = levels[i].trim().toUpperCase();
try {
XLog.Level.valueOf(s);
}
catch (Exception ex) {
continue;
}
this.logLevels.put(levels[i].toUpperCase(), 1);
}
}
}
public void setParameter(String filterParam, String value) {
if (filterParams.containsKey(filterParam)) {
noFilter = false;
filterParams.put(filterParam, value);
}
}
public static void defineParameter(String filterParam) {
parameters.add(filterParam);
}
public boolean isFilterPresent() {
if (noFilter && logLevels == null) {
return false;
}
return true;
}
/**
* Checks if the logLevel and logMessage goes through the logFilter.
* @param logLine the log line
* @return true if line contains the permitted logLevel
*/
public boolean splitsMatches(LogLine logLine) {
// Check whether logLine matched with filter
if (logLine.getMatchedPattern() != MATCHED_PATTERN.SPLIT) {
return false;
}
ArrayList<String> logParts = logLine.getLogParts();
if (getStartDate() != null) {
if (logParts.get(0).substring(0, 19).compareTo(getFormattedStartDate()) < 0) {
return false;
}
}
String logLevel = logParts.get(1);
if (this.logLevels == null || this.logLevels.containsKey(logLevel.toUpperCase(Locale.ENGLISH))) {
// line contains the permitted logLevel
return true;
}
else {
return false;
}
}
/**
* Checks if the logLevel and logMessage goes through the logFilter.
*
* @param logParts the arrayList of log parts
* @return true if the logLevel and logMessage goes through the logFilter
*/
public boolean matches(ArrayList<String> logParts) {
if (getStartDate() != null) {
if (logParts.get(0).substring(0, 19).compareTo(getFormattedStartDate()) < 0) {
return false;
}
}
String logLevel = logParts.get(1);
String logMessage = logParts.get(2);
if (this.logLevels == null || this.logLevels.containsKey(logLevel.toUpperCase())) {
Matcher logMatcher = filterPattern.matcher(logMessage);
return logMatcher.matches();
}
else {
return false;
}
}
/**
* Splits the log line into timestamp, logLevel and remaining log message.
* Returns array containing timestamp, logLevel, and logMessage if the
* pattern matches i.e A new log statement, else returns null.
*
* @param logLine the line
* @return Array containing log level and log message
*/
public ArrayList<String> splitLogMessage(String logLine) {
Matcher splitter = SPLITTER_PATTERN.matcher(logLine);
if (splitter.matches()) {
ArrayList<String> logParts = new ArrayList<String>();
logParts.add(splitter.group(1));// timestamp
logParts.add(splitter.group(2));// log level
logParts.add(splitter.group(3));// Log Message
return logParts;
}
else {
return null;
}
}
/**
* If <code>logLine</code> matches with <code>splitPattern</code>,
* <ol>
* <li>Split the log line into timestamp, logLevel and remaining log
* message.</li>
* <li>Record the parts of message in <code>logLine</code> to avoid regex
* matching in future.</li>
* <li>Record the pattern to which <code>logLine</code> has matched.</li>
* </ol>
* @param logLine the line to split
* @param splitPattern the pattern to use
*/
public void splitLogMessage(LogLine logLine, Pattern splitPattern) {
Matcher splitterWithJobId = splitPattern.matcher(logLine.getLine());
Matcher allowAll = SPLITTER_PATTERN.matcher(logLine.getLine());
if (splitterWithJobId.matches()) {
ArrayList<String> logParts = new ArrayList<String>(3);
logParts.add(splitterWithJobId.group(1));// timestamp
logParts.add(splitterWithJobId.group(2));// log level
logParts.add(splitterWithJobId.group(3));// log message
logLine.setLogParts(logParts);
logLine.setMatchedPattern(MATCHED_PATTERN.SPLIT);
}
else if (allowAll.matches()) {
logLine.setMatchedPattern(MATCHED_PATTERN.GENENRIC);
}
else {
logLine.setMatchedPattern(MATCHED_PATTERN.NONE);
}
}
/**
* Constructs the regular expression according to the filter and assigns it
* to fileterPattarn. ".*" will be assigned if no filters are set.
*/
public void constructPattern() {
if (noFilter && logLevels == null) {
filterPattern = Pattern.compile(ALLOW_ALL_REGEX);
return;
}
StringBuilder sb = new StringBuilder();
if (noFilter) {
sb.append("(.*)");
}
else {
sb.append("(.* ");
for (int i = 0; i < parameters.size(); i++) {
sb.append(parameters.get(i) + "\\[");
sb.append(filterParams.get(parameters.get(i)) + "\\] ");
}
sb.append(".*)");
}
if (!StringUtils.isEmpty(userLogFilter.getSearchText())) {
sb.append(userLogFilter.getSearchText() + ".*");
}
filterPattern = Pattern.compile(sb.toString());
}
public static void reset() {
parameters.clear();
}
public final Map<String, String> getFilterParams() {
return filterParams;
}
public XLogUserFilterParam getUserLogFilter() {
return userLogFilter;
}
public void setUserLogFilter(XLogUserFilterParam userLogFilter) {
this.userLogFilter = userLogFilter;
setLogLevel(userLogFilter.getLogLevel());
}
public Date getEndDate() {
return endDate;
}
public String getFormattedEndDate() {
return formattedEndDate;
}
public String getFormattedStartDate() {
return formattedStartDate;
}
public Date getStartDate() {
return startDate;
}
public boolean isDebugMode() {
return userLogFilter.isDebug();
}
public int getLogLimit() {
return userLogFilter.getLimit();
}
public String getDebugMessage() {
return new StringBuilder("Log start time = ").append(getStartDate()).append(". Log end time = ")
.append(getEndDate()).append(". User Log Filter = ").append(getUserLogFilter())
.append(System.getProperty("line.separator")).toString();
}
public boolean isActionList() {
return isActionList;
}
public void setActionList(boolean isActionList) {
this.isActionList = isActionList;
}
/**
* Calculate scan date
*
* @param jobStartTime the job start time
* @param jobEndTime the job end time
* @throws IOException Signals that an I/O exception has occurred.
*/
public void calculateAndCheckDates(Date jobStartTime, Date jobEndTime) throws IOException {
// for testcase, otherwise jobStartTime and jobEndTime will be always
// set
if (jobStartTime == null || jobEndTime == null) {
return;
}
if (userLogFilter.getStartDate() != null) {
startDate = userLogFilter.getStartDate();
}
else if (userLogFilter.getStartOffset() != -1) {
startDate = adjustOffset(jobStartTime, userLogFilter.getStartOffset());
}
else {
startDate = new Date(jobStartTime.getTime());
}
if (userLogFilter.getEndDate() != null) {
endDate = userLogFilter.getEndDate();
}
else if (userLogFilter.getEndOffset() != -1) {
// If user has specified startdate as absolute then end offset will
// be on user start date,
// else end offset will be calculated on job startdate.
if (userLogFilter.getStartDate() != null) {
endDate = adjustOffset(startDate, userLogFilter.getEndOffset());
}
else {
endDate = adjustOffset(jobStartTime, userLogFilter.getEndOffset());
}
}
else {
endDate = new Date(jobEndTime.getTime());
}
// if recent offset is specified then start time = endtime - offset
if (getUserLogFilter().getRecent() != -1) {
startDate = adjustOffset(endDate, userLogFilter.getRecent() * -1);
}
// add buffer if dates are not absolute
if (userLogFilter.getStartDate() == null) {
startDate = adjustOffset(startDate, -LOG_TIME_BUFFER);
}
if (userLogFilter.getEndDate() == null) {
endDate = adjustOffset(endDate, LOG_TIME_BUFFER);
}
formattedEndDate = XLogUserFilterParam.dt.get().format(getEndDate());
formattedStartDate = XLogUserFilterParam.dt.get().format(getStartDate());
if (startDate.after(endDate)) {
throw new IOException(
"Start time should be less than end time. startTime = " + startDate + " endTime = " + endDate);
}
}
/**
* validate date range.
*
* @param jobStartTime the job start time
* @param jobEndTime the job end time
* @throws IOException Signals that an I/O exception has occurred.
*/
public void validateDateRange(Date jobStartTime, Date jobEndTime) throws IOException {
// for testcase, otherwise jobStartTime and jobEndTime will be always
// set
if (jobStartTime == null || jobEndTime == null) {
return;
}
long diffHours = (endDate.getTime() - startDate.getTime()) / (60 * 60 * 1000);
if (isActionList) {
int actionLogDuration = ConfigurationService.getInt(MAX_ACTIONLIST_SCAN_DURATION);
if (actionLogDuration == -1) {
return;
}
if (diffHours > actionLogDuration) {
setTruncatedMessage("Truncated logs to max log scan duration " + actionLogDuration + " hrs");
startDate = adjustOffset(endDate, -1 * actionLogDuration * 60);
startDate = adjustOffset(startDate, -1 * LOG_TIME_BUFFER);
}
}
else {
int logDuration = ConfigurationService.getInt(MAX_SCAN_DURATION);
if (logDuration == -1) {
return;
}
if (diffHours > logDuration) {
setTruncatedMessage("Truncated logs to max log scan duration " + logDuration + " hrs");
startDate = adjustOffset(endDate, -1 * logDuration * 60);
startDate = adjustOffset(startDate, -1 * LOG_TIME_BUFFER);
}
}
}
protected void setTruncatedMessage(String message) {
truncatedMessage = message;
}
public String getTruncatedMessage() {
if (StringUtils.isEmpty(truncatedMessage)) {
return truncatedMessage;
}
else {
return truncatedMessage + System.getProperty("line.separator");
}
}
/**
* Adjust offset, offset will always be in min.
*
* @param date the date
* @param offset the offset
* @return the date
* @throws IOException Signals that an I/O exception has occurred.
*/
public Date adjustOffset(Date date, int offset) throws IOException {
return org.apache.commons.lang3.time.DateUtils.addMinutes(date, offset);
}
public void setFilterPattern(Pattern filterPattern) {
this.filterPattern = filterPattern;
}
public Pattern getFilterPattern() {
return this.filterPattern;
}
}