All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.seleniumtests.util.logging.SeleniumRobotLogger Maven / Gradle / Ivy

There is a newer version: 5.1.13
Show newest version
/**
 * 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 com.seleniumtests.util.helper.WaitHelper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.FileAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.builder.api.*;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
import org.apache.logging.log4j.core.layout.PatternLayout;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
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.Map;

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 Map testLogs = Collections.synchronizedMap(new HashMap<>());
	private static ThreadLocal loggerNames = new ThreadLocal();
	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 LOG_FILE_NAME = "seleniumRobot.log";
	public static final String INTERNAL_DEBUG = "internalDebug";
	public static final String MAVEN_EXECUTION = "mavenExecution";
	private static boolean rootIsConfigured = false;
	
	private SeleniumRobotLogger() {
		// As a utility class, it is not meant to be instantiated.
	}
	
	/**
	 * Removes the logger for specific test
	 */
	public static void removeLoggerForTest() {
		LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
		Configuration config = ctx.getConfiguration();
		
		if (loggerNames.get() != null) {
			String previousName = loggerNames.get();
			Appender appender = ((BuiltConfiguration) config).getLogger(previousName).getAppenders().get(FILE_APPENDER_NAME + "-" + previousName);
			if (appender != null) {
				appender.stop();
				((BuiltConfiguration) config).getLogger(previousName).removeAppender(FILE_APPENDER_NAME + "-" + previousName);
			}
			loggerNames.remove();
			
			// remove the logger so that a new one for the same name can be added (useful during integration tests)
			config.removeLogger(previousName);
		}
	}
	
	/**
	 * Creates a logger for a test method, so that all logs goes to a specific file
	 * @param outputDir
	 * @param loggerName
	 */
	public static void createLoggerForTest(String outputDir, String loggerName) {

		LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
		Configuration config = ctx.getConfiguration();
		Level level = (System.getProperty(INTERNAL_DEBUG) != null && System.getProperty(INTERNAL_DEBUG).contains("core")) ? Level.DEBUG: Level.INFO;
		
		LoggerConfig loggerConfig = new LoggerConfig(loggerName, level, false);
		PatternLayout.Builder layoutBuilder = PatternLayout.newBuilder();
		layoutBuilder.withPattern(LOG_PATTERN);
		PatternLayout patternLayout = layoutBuilder.build();
		
		File logFile = new File(outputDir + "/execution.log");
		FileAppender.Builder fileAppenderBuilder = FileAppender.newBuilder()
				.withFileName(logFile.getAbsolutePath());
		fileAppenderBuilder.setLayout(patternLayout);
		fileAppenderBuilder.setName(FILE_APPENDER_NAME + "-" + loggerName);
		
		Appender fileAppender = fileAppenderBuilder.build();
		fileAppender.start();

		loggerConfig.addAppender(fileAppender, level, null);
		loggerConfig.addFilter(RepeatFilter.createFilter(Level.ERROR, true, Filter.Result.ACCEPT, Filter.Result.DENY));
		
		config.addLogger(loggerName, loggerConfig);
		
		ctx.updateLoggers();
		loggerNames.set(loggerName);
		testLogs.put(loggerName, logFile);
	}
	
	public static Logger getLoggerForTest() {
		if (loggerNames.get() == null) {
			return null;
		}
		return LogManager.getLogger(loggerNames.get());
	}
	
	private static void configureLogger() {
		ConfigurationBuilder builder = ConfigurationBuilderFactory.newConfigurationBuilder()
				.setPackages("com.seleniumtests.util.logging"); // to be able to load custom filters
		AppenderComponentBuilder consoleAppenderBuilder  = builder.newAppender("stdout", "CONSOLE"); 
		
		LayoutComponentBuilder layout = builder
				.newLayout("PatternLayout")
				.addAttribute("pattern", LOG_PATTERN);
		consoleAppenderBuilder.add(layout);
		
		consoleAppenderBuilder.add(builder
				.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
			    .addAttribute("marker", "FLOW"));

		builder.add(consoleAppenderBuilder);

		RootLoggerComponentBuilder rootLogger;
		// 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
		if (System.getProperty(INTERNAL_DEBUG) != null && System.getProperty(INTERNAL_DEBUG).contains("core")) {
			rootLogger = builder.newRootLogger(Level.DEBUG);
        } else {
        	rootLogger = builder.newRootLogger(Level.INFO);
        }
		rootLogger.add(builder.newAppenderRef("stdout"));
		rootLogger.add(builder.newFilter("RepeatFilter", Filter.Result.ACCEPT, Filter.Result.DENY));

		
		builder.add(rootLogger);
//		Uncomment to debug configuration
//		try {
//			builder.writeXmlConfiguration(System.out);
//		} catch (IOException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		Configurator.reconfigure(builder.build());
		
		
	}
	
	public static Logger getLogger(final Class cls) {
		
		
	    if (!rootIsConfigured) {
	    	configureLogger();

			if (System.getProperty(MAVEN_EXECUTION) == null || System.getProperty(MAVEN_EXECUTION).equals("false")) {
				System.out.println("streams redirected to logger");
		        // redirect standard output and error to logger so that all logs are written to log file
		        System.setErr(new PrintStream(new Sys.Error(LogManager.getRootLogger()), true));
		        System.setOut(new PrintStream(new Sys.Out(LogManager.getRootLogger()), true));
			}
	
	        rootIsConfigured = true;
	    }

	    return new LoggerWrapper(LogManager.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 = isFileAppenderPresent();
		if (fileLoggerAppender == null) {

			// clean output dir
			if (doCleanResults) {
				cleanResults();
			}
			
			for (int i=0; i < 4; i++) {
				try {
					if (!new File(outputDir).exists()) {
						new File(outputDir).mkdirs();
					}
					 
			        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
			        final Configuration config = ctx.getConfiguration();

					PatternLayout.Builder layoutBuilder = PatternLayout.newBuilder();
					layoutBuilder.withPattern(LOG_PATTERN);
					PatternLayout patternLayout = layoutBuilder.build();
					
					FileAppender.Builder fileAppenderBuilder = FileAppender.newBuilder()
							.withFileName(outputDir + "/" + logFileName);
					fileAppenderBuilder.setLayout(patternLayout);
					fileAppenderBuilder.setName(FILE_APPENDER_NAME);
					
					Appender fileAppender = fileAppenderBuilder.build();
					fileAppender.start();

			        ctx.getRootLogger().addAppender(fileAppender);

			        if (System.getProperty(INTERNAL_DEBUG) != null && System.getProperty(INTERNAL_DEBUG).contains("core")) {
			        	config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.DEBUG);
			        } else {
			        	config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).setLevel(Level.INFO);
			        }
			        ctx.updateLoggers();
			        
			        break;
				} catch (Exception e) {
				}
			}

		}
	}
	
	/**
	 * 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::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) {
				}
	    	
	    	}
		}
	}
	
	/**
	 * Remove all execution files as they are not needed anymore, data written to log files
	 * This method MUST be called once all tests has terminated, else, loggers will try to write to closed streams
	 */
	public static void cleanTestLogs() {
		
		// stop all file loggers
		LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
		Configuration config = ctx.getConfiguration();
		for (LoggerConfig loggerConfig: config.getLoggers().values()) {
			// do not stop root logger
			if (loggerConfig.getParent() == null) {
				continue;
			}
			for (Appender appender: loggerConfig.getAppenders().values()) {
				System.out.println(String.format("Stopping appender %s for logger %s", loggerConfig.getName(), appender.getName()));
				appender.stop();
			}
		}

		for (File file: FileUtils.listFiles(new File(defaultOutputDirectory),
				FileFilterUtils.nameFileFilter("execution.log"),
				TrueFileFilter.INSTANCE )) {
			try {
				Files.delete(file.toPath());
			} catch (IOException e) {
				getLogger(SeleniumRobotLogger.class).warn(String.format("Cannot delete %s: %s", file.getAbsolutePath(), e.getMessage()));
			}
		}
	}
	
	public static void reset() throws IOException {
		SeleniumRobotLogger.testLogs.clear();
		
		// clear log file
		Appender fileLoggerAppender = isFileAppenderPresent();
		if (fileLoggerAppender != null) {
			fileLoggerAppender.stop();
			
			// wait for handler to be closed
			WaitHelper.waitForMilliSeconds(200);
			((LoggerContext)LogManager.getContext(false)).getConfiguration().getRootLogger().removeAppender(FILE_APPENDER_NAME);
			((LoggerContext)LogManager.getContext(false)).updateLoggers();

		}
		if (outputDirectory != null) {
			Path logFilePath = Paths.get(outputDirectory, SeleniumRobotLogger.LOG_FILE_NAME).toAbsolutePath();
			if (logFilePath.toFile().exists()) {
				Files.delete(logFilePath);

			}
		}
	}
	
	private static  Appender isFileAppenderPresent() {
		return ((LoggerContext)LogManager.getContext(false)).getConfiguration().getRootLogger().getAppenders().get(FILE_APPENDER_NAME);
	}
	
	/**
	 * Returns the file containing logs for requested test name (unique test name)
	 * or else, null
	 * @param testName
	 * @return
	 */
	public static String getTestLogs(String testName) {
		File logFile = testLogs.get(testName);
		if (logFile == null) {
			return "";
		}
		
		try {
			return FileUtils.readFileToString(logFile, StandardCharsets.UTF_8);
		} catch (IOException e) {
			return "";
		}
	}
	
	/**
	 * Returns the log file (execution.log) for the given test name
	 * @param testName
	 * @return
	 */
	public static File getTestLogsFile(String testName) {
		return testLogs.get(testName);
	}



	public static void setOutputDirectory(String outputDirectory) {
		SeleniumRobotLogger.outputDirectory = outputDirectory;
	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy