| /** |
| * 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; |
| } |
| |
| } |