/* * Copyright (c) 2010-2015 Evolveum * * Licensed 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 com.evolveum.midpoint.test.util; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.evolveum.midpoint.util.aspect.MidpointInterceptor; import com.evolveum.midpoint.util.aspect.ProfilingDataManager; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; /** * @author semancik * */ public class LogfileTestTailer { public static final File TEST_LOG_FILE = new File("target/test.log"); public static final String MARKER = "_M_A_R_K_E_R_"; public static final String LEVEL_ERROR = "ERROR"; public static final String LEVEL_WARN = "WARN"; public static final String LEVEL_INFO = "INFO"; public static final String LEVEL_DEBUG = "DEBUG"; public static final String LEVEL_TRACE = "TRACE"; // see also the 'optimization' at the beginning of processLogLine() - interfering with these patterns private static final Pattern PATTERN_MARKER = Pattern.compile(".*\\[[^]]*\\](\\s+\\[\\w+\\])?\\s+(\\S+)\\s+\\(\\S+\\):\\s+"+MARKER+"\\s+(\\w+).*"); private static final Pattern PATTERN_MARKER_PREFIX = Pattern.compile(".*\\[[^]]*\\](\\s+\\[\\w+\\])?\\s+(\\S+)\\s+.*"+MARKER+"\\s+(\\w+).*"); public static final Pattern PATTERN_LEVEL = Pattern.compile(".*\\[[^]]*\\](\\s+\\[\\w+\\])?\\s+(\\S+)\\s+(.*)"); final static Trace LOGGER = TraceManager.getTrace(LogfileTestTailer.class); private String auditLoggerName; public Pattern auditPattern; private File logFile; private Reader fileReader; private BufferedReader reader; private boolean seenMarker; private Set<String> loggedMarkers; private List<String> auditMessages = new ArrayList<String>(); private String expectedMessage; private String expectedMessageLine; private boolean allowPrefix = false; private Collection<String> errors = new ArrayList<String>(); private Collection<String> warnings = new ArrayList<String>(); public LogfileTestTailer(String auditLoggerName) throws IOException { this(TEST_LOG_FILE, auditLoggerName, true); } public LogfileTestTailer(String auditLoggerName, boolean skipCurrentContent) throws IOException { this(TEST_LOG_FILE, auditLoggerName, skipCurrentContent); } public LogfileTestTailer(File logFile, String auditLoggerName, boolean skipCurrentContent) throws IOException { this.logFile = logFile; this.auditLoggerName = auditLoggerName; auditPattern = Pattern.compile(".*\\[[^]]*\\](\\s+\\[[^]]*\\])?\\s+(\\w+)\\s+\\("+auditLoggerName+"\\):\\s*(.*)"); reset(); // doing skipping on FileInputStream instead of BufferedReader, hoping it is faster // as the sequential scanning of the file is eliminated FileInputStream fileInputStream = new FileInputStream(logFile); if (skipCurrentContent) { long skipped = fileInputStream.skip(logFile.length()); LOGGER.info("Skipped = {}", skipped); } fileReader = new InputStreamReader(fileInputStream); reader = new BufferedReader(fileReader); } public boolean isAllowPrefix() { return allowPrefix; } public void setAllowPrefix(boolean allowPrefix) { this.allowPrefix = allowPrefix; } public Collection<String> getErrors() { return errors; } public Collection<String> getWarnings() { return warnings; } public void close() throws IOException { reader.close(); fileReader.close(); } public void reset() { seenMarker = false; loggedMarkers = new HashSet<String>(); auditMessages = new ArrayList<String>(); expectedMessageLine = null; } public boolean isSeenMarker() { return seenMarker; } public void setSeenMarker(boolean seenMarker) { this.seenMarker = seenMarker; } public void tail() throws IOException { LOGGER.trace("Start tailing file {}", logFile); while (true) { String line = reader.readLine(); if (line == null) { break; } processLogLine(line); } LOGGER.trace("End tailing file {}", logFile); } private void processLogLine(String line) { if (line.length() > 0 && Character.isWhitespace(line.charAt(0))) { return; // ugly hack: getting rid of long 'continuation' lines that are not matched by any patterns but terribly slow down the processing } // Match marker Pattern pattern = PATTERN_MARKER; if (allowPrefix) { pattern = PATTERN_MARKER_PREFIX; } Matcher matcher = pattern.matcher(line); while (matcher.find()) { seenMarker = true; String level; String subsystemName; if (matcher.groupCount() == 2) { level = matcher.group(1); subsystemName = matcher.group(2); } else { level = matcher.group(2); subsystemName = matcher.group(3); } recordMarker(level,subsystemName); } // Match audit matcher = auditPattern.matcher(line); if (!line.contains("Details of event")) { while (matcher.find()) { String level; String message; if (matcher.groupCount() == 2) { level = matcher.group(1); message = matcher.group(2); } else { level = matcher.group(2); message = matcher.group(3); } recordAuditMessage(level,message); } } if (expectedMessage != null && line.contains(expectedMessage)) { expectedMessageLine = line; } // Match errors and warnings matcher = PATTERN_LEVEL.matcher(line); while (matcher.find()) { String level = matcher.group(1); String message = matcher.group(2); if ("ERROR".equals(level)) { errors.add(message); } if ("WARN".equals(line)) { warnings.add(message); } } } private void recordMarker(String level, String subsystemName) { LOGGER.trace("Found marker ({}): {}", level, subsystemName); String key = constructKey(level, subsystemName); loggedMarkers.add(key); } private void recordAuditMessage(String level, String message) { LOGGER.trace("Found audit message ({})", level); auditMessages.add(message); } public List<String> getAuditMessages() { return auditMessages; } private String constructKey(String level, String subsystemName) { return level+":"+subsystemName; } public void assertMarkerLogged(String level, String subsystemName) { assert loggedMarkers.contains(constructKey(level, subsystemName)) : level + " in " + subsystemName + " was not logged"; } public void assertMarkerNotLogged(String level, String subsystemName) { assert !loggedMarkers.contains(constructKey(level, subsystemName)) : level + " in " + subsystemName + " was logged (while not expecting it)"; } public void setExpecteMessage(String expectedMessage) { this.expectedMessage = expectedMessage; this.expectedMessageLine = null; } public void assertExpectedMessage() { assert expectedMessageLine != null : "The expected message was not seen"; } public void assertNoAudit() { assert auditMessages.isEmpty() : "Audit messages not empty: "+auditMessages; } public void assertAudit() { assert !auditMessages.isEmpty() : "No audit message"; } public void assertAudit(String message) { assert auditMessages.contains(message) : "No audit message: "+message; } public void assertAuditRequest() { for (String message: auditMessages) { if (message.contains("stage REQUEST")) { return; } if (message.contains("es=REQUEST")) { return; } } assert false: "No request audit message"; } public void assertAuditExecution() { for (String message: auditMessages) { if (message.contains("stage EXECUTION")) { return; } if (message.contains("es=EXECUTION")) { return; } } assert false: "No execution audit message"; } public void assertAudit(int messageCount) { assert auditMessages.size() == messageCount : "Wrong number of audit messages, expected "+messageCount+", was "+auditMessages.size(); } /** * Log all levels in all subsystems. */ public void log() { for (ProfilingDataManager.Subsystem subsystem: ProfilingDataManager.subsystems) { logAllLevels(LOGGER, subsystem.name()); } logAllLevels(LOGGER, null); } private void logAllLevels(Trace logger, String subsystemName) { String message = MARKER+" "+subsystemName; String previousSubsystem = MidpointInterceptor.swapSubsystemMark(subsystemName); logger.trace(message); logger.debug(message); logger.info(message); logger.warn(message); logger.error(message); MidpointInterceptor.swapSubsystemMark(previousSubsystem); } public void logAndTail() throws IOException { log(); // Some pause here? tail(); } }