Java tutorial
/** * Orignal work: Copyright 2015 www.seleniumtests.com * Modified work: Copyright 2016 www.infotel.com * Copyright 2017-2019 B.Hecquet * * 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.seleniumtests.util.logging; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.log4j.Appender; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.FileAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import com.seleniumtests.util.helper.WaitHelper; public class SeleniumRobotLogger { private static final String LOG_PATTERN = " %-5p %d [%t] %C{1}: %m%n"; private static final String FILE_APPENDER_NAME = "FileLogger"; private static final Pattern LOG_FILE_PATTERN = Pattern.compile(".*?\\d \\[(.*?)\\](.*)"); private static Map<String, String> testLogs = Collections.synchronizedMap(new HashMap<>()); private static String outputDirectory; private static String defaultOutputDirectory; public static final String START_TEST_PATTERN = "Start method "; public static final String END_TEST_PATTERN = "Finish method "; public static final String METHOD_NAME = "methodName"; // name of the test method (or the cucumber scenario) public static final String UNIQUE_METHOD_NAME = "uniqueMethodName"; // unique name of the test (in case several tests have the same name) public static final String LOG_FILE_NAME = "seleniumRobot.log"; public static final String INTERNAL_DEBUG = "internalDebug"; private SeleniumRobotLogger() { // As a utility class, it is not meant to be instantiated. } public static Logger getLogger(final Class<?> cls) { boolean rootIsConfigured = Logger.getRootLogger().getAllAppenders().hasMoreElements(); if (!rootIsConfigured) { BasicConfigurator.configure(); Logger rootLogger = Logger.getRootLogger(); Appender appender = (Appender) rootLogger.getAllAppenders().nextElement(); appender.setLayout(new PatternLayout(SeleniumRobotLogger.LOG_PATTERN)); } // use System property instead of SeleniumTestsContext class as SeleniumrobotLogger class is used for grid extension package and // we do not want to depend on "SeleniumTestsContext" class here Logger rootLogger = Logger.getRootLogger(); if (System.getProperty(INTERNAL_DEBUG) != null && System.getProperty(INTERNAL_DEBUG).contains("core")) { rootLogger.setLevel(Level.DEBUG); } else { rootLogger.setLevel(Level.INFO); } return Logger.getLogger(cls); } /** * Update root logger so that logs are made available in a log file * This code is delayed so that SeleniumTestsContext is initialized * This is also not called for unit and integration tests * */ public static void updateLogger(String outputDir, String defaultOutputDir) { updateLogger(outputDir, defaultOutputDir, SeleniumRobotLogger.LOG_FILE_NAME); } public static void updateLogger(String outputDir, String defaultOutputDir, String logFileName) { updateLogger(outputDir, defaultOutputDir, logFileName, true); } public static void updateLogger(String outputDir, String defaultOutputDir, String logFileName, boolean doCleanResults) { outputDirectory = outputDir; defaultOutputDirectory = defaultOutputDir; Appender fileLoggerAppender = Logger.getRootLogger().getAppender(FILE_APPENDER_NAME); if (fileLoggerAppender == null) { Logger rootLogger = Logger.getRootLogger(); FileAppender fileAppender = new FileAppender(); // clean output dir if (doCleanResults) { cleanResults(); } for (int i = 0; i < 4; i++) { try { if (!new File(outputDir).exists()) { new File(outputDir).mkdirs(); } fileAppender.setName(FILE_APPENDER_NAME); fileAppender.setFile(outputDir + "/" + logFileName); fileAppender.setLayout(new PatternLayout(LOG_PATTERN)); if (System.getProperty(INTERNAL_DEBUG) != null && System.getProperty(INTERNAL_DEBUG).contains("core")) { fileAppender.setThreshold(Level.DEBUG); } else { fileAppender.setThreshold(Level.INFO); } fileAppender.activateOptions(); break; } catch (Exception e) { } } rootLogger.addAppender(fileAppender); } } /** * Clean result directories * Delete the directory that will be used to write these test results * Delete also directories in "test-output" which are older than 300 minutes. Especially useful when test is requested to write result * to a sub-directory of test-output with timestamp (for example). Without this mechanism, results would never be cleaned */ private static void cleanResults() { // clean output dir try { FileUtils.deleteDirectory(new File(outputDirectory)); WaitHelper.waitForSeconds(1); } catch (IOException e) { // do nothing } new File(outputDirectory).mkdirs(); WaitHelper.waitForSeconds(1); if (new File(defaultOutputDirectory).exists()) { for (File directory : new File(defaultOutputDirectory).listFiles(file -> file.isDirectory())) { try { if (Files.readAttributes(directory.toPath(), BasicFileAttributes.class).lastAccessTime() .toInstant().atZone(ZoneOffset.UTC).toLocalTime().isBefore(ZonedDateTime.now() .minusMinutes(300).withZoneSameInstant(ZoneOffset.UTC).toLocalTime())) { FileUtils.deleteDirectory(directory); } } catch (IOException e) { } } } } /** * Parses log file and store logs of each test in testLogs variable * @return * @throws IOException */ public static synchronized void parseLogFile() { Appender fileLoggerAppender = Logger.getRootLogger().getAppender(FILE_APPENDER_NAME); if (fileLoggerAppender == null) { return; } // read the file from appender directly List<String> logLines; try { logLines = FileUtils.readLines(new File(((FileAppender) fileLoggerAppender).getFile())); } catch (IOException e) { getLogger(SeleniumRobotLogger.class).error("cannot read log file", e); return; } // clean before reading file. correction of issue #100 SeleniumRobotLogger.testLogs.clear(); //store the name of the thread for each test Map<String, String> testPerThread = new HashMap<>(); for (String line : logLines) { Matcher matcher = SeleniumRobotLogger.LOG_FILE_PATTERN.matcher(line); if (matcher.matches()) { String thread = matcher.group(1); String content = matcher.group(2); if (content.contains(SeleniumRobotLogger.START_TEST_PATTERN)) { String testName = content.split(SeleniumRobotLogger.START_TEST_PATTERN)[1].trim(); testPerThread.put(thread, testName); // do not refresh content of logs in case test is retried if (!SeleniumRobotLogger.testLogs.containsKey(testName)) { SeleniumRobotLogger.testLogs.put(testName, ""); } } if (testPerThread.get(thread) != null) { String testName = testPerThread.get(thread); SeleniumRobotLogger.testLogs.put(testName, SeleniumRobotLogger.testLogs.get(testName).concat(line + "\n")); } } } } public static void reset() throws IOException { SeleniumRobotLogger.testLogs.clear(); // clear log file Appender fileAppender = Logger.getRootLogger().getAppender(FILE_APPENDER_NAME); if (fileAppender != null) { fileAppender.close(); // wait for handler to be closed WaitHelper.waitForMilliSeconds(200); Logger.getRootLogger().removeAppender(FILE_APPENDER_NAME); } Path logFilePath = Paths.get(outputDirectory, SeleniumRobotLogger.LOG_FILE_NAME).toAbsolutePath(); if (logFilePath.toFile().exists()) { Files.delete(logFilePath); } } public static Map<String, String> getTestLogs() { return testLogs; } public static void setOutputDirectory(String outputDirectory) { SeleniumRobotLogger.outputDirectory = outputDirectory; } }