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 019 020package org.apache.oozie.util; 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.apache.commons.lang.StringUtils; 032import org.apache.oozie.service.ConfigurationService; 033import org.apache.oozie.service.Services; 034 035import com.google.common.annotations.VisibleForTesting; 036 037/** 038 * Filter that will construct the regular expression that will be used to filter the log statement. And also checks if 039 * the given log message go through the filter. Filters that can be used are logLevel(Multi values separated by "|") 040 * jobId appName actionId token 041 */ 042public class XLogFilter { 043 044 private static final int LOG_TIME_BUFFER = 2; // in min 045 public static String MAX_ACTIONLIST_SCAN_DURATION = "oozie.service.XLogStreamingService.actionlist.max.log.scan.duration"; 046 public static String MAX_SCAN_DURATION = "oozie.service.XLogStreamingService.max.log.scan.duration"; 047 private Map<String, Integer> logLevels; 048 private final Map<String, String> filterParams; 049 private static List<String> parameters = new ArrayList<String>(); 050 private boolean noFilter; 051 private Pattern filterPattern; 052 private XLogUserFilterParam userLogFilter; 053 private Date endDate; 054 private Date startDate; 055 private boolean isActionList = false; 056 private String formattedEndDate; 057 private String formattedStartDate; 058 059 // TODO Patterns to be read from config file 060 private static final String DEFAULT_REGEX = "[^\\]]*"; 061 062 public static final String ALLOW_ALL_REGEX = "(.*)"; 063 private static final String TIMESTAMP_REGEX = "(\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d,\\d\\d\\d)"; 064 private static final String WHITE_SPACE_REGEX = "\\s+"; 065 private static final String LOG_LEVEL_REGEX = "(\\w+)"; 066 private static final String PREFIX_REGEX = TIMESTAMP_REGEX + WHITE_SPACE_REGEX + LOG_LEVEL_REGEX 067 + WHITE_SPACE_REGEX; 068 private static final Pattern SPLITTER_PATTERN = Pattern.compile(PREFIX_REGEX + ALLOW_ALL_REGEX); 069 070 public XLogFilter() { 071 this(new XLogUserFilterParam()); 072 } 073 074 public XLogFilter(XLogUserFilterParam userLogFilter) { 075 filterParams = new HashMap<String, String>(); 076 for (int i = 0; i < parameters.size(); i++) { 077 filterParams.put(parameters.get(i), DEFAULT_REGEX); 078 } 079 logLevels = null; 080 noFilter = true; 081 filterPattern = null; 082 setUserLogFilter(userLogFilter); 083 } 084 085 public void setLogLevel(String logLevel) { 086 if (logLevel != null && logLevel.trim().length() > 0) { 087 this.logLevels = new HashMap<String, Integer>(); 088 String[] levels = logLevel.split("\\|"); 089 for (int i = 0; i < levels.length; i++) { 090 String s = levels[i].trim().toUpperCase(); 091 try { 092 XLog.Level.valueOf(s); 093 } 094 catch (Exception ex) { 095 continue; 096 } 097 this.logLevels.put(levels[i].toUpperCase(), 1); 098 } 099 } 100 } 101 102 public void setParameter(String filterParam, String value) { 103 if (filterParams.containsKey(filterParam)) { 104 noFilter = false; 105 filterParams.put(filterParam, value); 106 } 107 } 108 109 public static void defineParameter(String filterParam) { 110 parameters.add(filterParam); 111 } 112 113 public boolean isFilterPresent() { 114 if (noFilter && logLevels == null) { 115 return false; 116 } 117 return true; 118 } 119 120 /** 121 * Checks if the logLevel and logMessage goes through the logFilter. 122 * 123 * @param logParts 124 * @return 125 */ 126 public boolean matches(ArrayList<String> logParts) { 127 if (getStartDate() != null) { 128 if (logParts.get(0).substring(0, 19).compareTo(getFormattedStartDate()) < 0) { 129 return false; 130 } 131 } 132 String logLevel = logParts.get(1); 133 String logMessage = logParts.get(2); 134 if (this.logLevels == null || this.logLevels.containsKey(logLevel.toUpperCase())) { 135 Matcher logMatcher = filterPattern.matcher(logMessage); 136 return logMatcher.matches(); 137 } 138 else { 139 return false; 140 } 141 } 142 143 /** 144 * Splits the log line into timestamp, logLevel and remaining log message. Returns array containing timestamp, 145 * logLevel, and logMessage if the pattern matches i.e A new log statement, else returns null. 146 * 147 * @param logLine 148 * @return Array containing log level and log message 149 */ 150 public ArrayList<String> splitLogMessage(String logLine) { 151 Matcher splitter = SPLITTER_PATTERN.matcher(logLine); 152 if (splitter.matches()) { 153 ArrayList<String> logParts = new ArrayList<String>(); 154 logParts.add(splitter.group(1));// timestamp 155 logParts.add(splitter.group(2));// log level 156 logParts.add(splitter.group(3));// Log Message 157 return logParts; 158 } 159 else { 160 return null; 161 } 162 } 163 164 /** 165 * Constructs the regular expression according to the filter and assigns it to fileterPattarn. ".*" will be assigned 166 * if no filters are set. 167 */ 168 public void constructPattern() { 169 if (noFilter && logLevels == null) { 170 filterPattern = Pattern.compile(ALLOW_ALL_REGEX); 171 return; 172 } 173 StringBuilder sb = new StringBuilder(); 174 if (noFilter) { 175 sb.append("(.*)"); 176 } 177 else { 178 sb.append("(.* "); 179 for (int i = 0; i < parameters.size(); i++) { 180 sb.append(parameters.get(i) + "\\["); 181 sb.append(filterParams.get(parameters.get(i)) + "\\] "); 182 } 183 sb.append(".*)"); 184 } 185 if (!StringUtils.isEmpty(userLogFilter.getSearchText())) { 186 sb.append(userLogFilter.getSearchText() + ".*"); 187 } 188 filterPattern = Pattern.compile(sb.toString()); 189 } 190 191 public static void reset() { 192 parameters.clear(); 193 } 194 195 @VisibleForTesting 196 public final Map<String, String> getFilterParams() { 197 return filterParams; 198 } 199 200 public XLogUserFilterParam getUserLogFilter() { 201 return userLogFilter; 202 } 203 204 public void setUserLogFilter(XLogUserFilterParam userLogFilter) { 205 this.userLogFilter = userLogFilter; 206 setLogLevel(userLogFilter.getLogLevel()); 207 } 208 209 public Date getEndDate() { 210 return endDate; 211 } 212 213 public String getFormattedEndDate() { 214 return formattedEndDate; 215 } 216 217 public String getFormattedStartDate() { 218 return formattedStartDate; 219 } 220 221 public Date getStartDate() { 222 return startDate; 223 } 224 225 public boolean isDebugMode() { 226 return userLogFilter.isDebug(); 227 } 228 229 public int getLogLimit() { 230 return userLogFilter.getLimit(); 231 } 232 233 public String getDebugMessage() { 234 return "Log start time = " + getStartDate() + ". Log end time = " + getEndDate() + ". User Log Filter = " 235 + getUserLogFilter() + System.getProperty("line.separator"); 236 } 237 238 public boolean isActionList() { 239 return isActionList; 240 } 241 242 public void setActionList(boolean isActionList) { 243 this.isActionList = isActionList; 244 } 245 246 private void calculateScanDate(Date jobStartTime, Date jobEndTime) throws IOException { 247 248 if (userLogFilter.getStartDate() != null) { 249 startDate = userLogFilter.getStartDate(); 250 } 251 else if (userLogFilter.getStartOffset() != -1) { 252 startDate = adjustOffset(jobStartTime, userLogFilter.getStartOffset()); 253 } 254 else { 255 startDate = jobStartTime; 256 } 257 258 if (userLogFilter.getEndDate() != null) { 259 endDate = userLogFilter.getEndDate(); 260 } 261 else if (userLogFilter.getEndOffset() != -1) { 262 // If user has specified startdate as absolute then end offset will be on user start date, 263 // else end offset will be calculated on job startdate. 264 if (userLogFilter.getStartDate() != null) { 265 endDate = adjustOffset(startDate, userLogFilter.getEndOffset()); 266 } 267 else { 268 endDate = adjustOffset(jobStartTime, userLogFilter.getEndOffset()); 269 } 270 } 271 else { 272 endDate = jobEndTime; 273 } 274 // if recent offset is specified then start time = endtime - offset 275 if (getUserLogFilter().getRecent() != -1) { 276 startDate = adjustOffset(endDate, userLogFilter.getRecent() * -1); 277 } 278 279 //add buffer iff dates are not asbsolute 280 if (userLogFilter.getStartDate() == null) { 281 startDate = adjustOffset(startDate, -LOG_TIME_BUFFER); 282 } 283 if (userLogFilter.getEndDate() == null) { 284 endDate = adjustOffset(endDate, LOG_TIME_BUFFER); 285 } 286 287 formattedEndDate = XLogUserFilterParam.dt.get().format(getEndDate()); 288 formattedStartDate = XLogUserFilterParam.dt.get().format(getStartDate()); 289 } 290 291 /** 292 * Calculate and validate date range. 293 * 294 * @param jobStartTime the job start time 295 * @param jobEndTime the job end time 296 * @throws IOException Signals that an I/O exception has occurred. 297 */ 298 public void calculateAndValidateDateRange(Date jobStartTime, Date jobEndTime) throws IOException { 299 // for testcase, otherwise jobStartTime and jobEndTime will be always set 300 if (jobStartTime == null || jobEndTime == null) { 301 return; 302 } 303 calculateScanDate(jobStartTime, jobEndTime); 304 305 if (startDate.after(endDate)) { 306 throw new IOException("Start time should be less than end time. startTime = " + startDate + " endtime = " 307 + endDate); 308 } 309 long diffHours = (endDate.getTime() - startDate.getTime()) / (60 * 60 * 1000); 310 if (isActionList) { 311 int actionLogDuration = ConfigurationService.getInt(MAX_ACTIONLIST_SCAN_DURATION); 312 if (actionLogDuration == -1) { 313 return; 314 } 315 if (diffHours > actionLogDuration) { 316 throw new IOException( 317 "Request log streaming time range with action list is higher than configured. Please reduce the scan " 318 + "time range. Input range (hours) = " + diffHours 319 + " system allowed (hours) with action list = " + actionLogDuration); 320 } 321 } 322 else { 323 int logDuration = ConfigurationService.getInt(MAX_SCAN_DURATION); 324 if (logDuration == -1) { 325 return; 326 } 327 if (diffHours > logDuration) { 328 throw new IOException( 329 "Request log streaming time range is higher than configured. Please reduce the scan time range. For coord" 330 + " jobs you can provide action list to reduce log scan time range. Input range (hours) = " 331 + diffHours + " system allowed (hours) = " + logDuration); 332 } 333 } 334 } 335 336 /** 337 * Adjust offset, offset will always be in min. 338 * 339 * @param date the date 340 * @param offset the offset 341 * @return the date 342 * @throws IOException Signals that an I/O exception has occurred. 343 */ 344 public Date adjustOffset(Date date, int offset) throws IOException { 345 return org.apache.commons.lang.time.DateUtils.addMinutes(date, offset); 346 } 347 348}