/*
* Universal Media Server, for streaming any media to DLNA
* compatible renderers based on the http://www.ps3mediaserver.org.
* Copyright (C) 2012 UMS developers.
*
* This program is a free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.pms.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.net.SyslogAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.OutputStreamAppender;
import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.filter.Filter;
import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.util.FileUtil;
import org.apache.commons.configuration.ConfigurationException;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.LoggerFactory;
/**
* Test net.pms.logging package
*/
public class LoggingTest {
private static class TestAppender<E> extends AppenderBase<E> {
private final Object lastEventLock = new Object();
private E lastEvent = null;
public E getLastEvent() {
synchronized (lastEventLock) {
return lastEvent;
}
}
@Override
protected void append(E eventObject) {
synchronized (lastEventLock) {
lastEvent = eventObject;
}
}
}
private static class TestFileAppender<E> extends FileAppender<E> {
@Override
protected void append(E eventObject) {
}
}
@Before
public void setUp() {
// Silence all log messages from the UMS code that is being tested
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.OFF);
}
private static boolean findAppender(Iterator<Appender<ILoggingEvent>> iterator, Appender<ILoggingEvent> appender) {
boolean found = false;
while (iterator.hasNext()) {
Appender<ILoggingEvent> a = iterator.next();
if (a == appender) {
found = true;
}
}
return found;
}
private static boolean syslogAppenderFound(Iterator<Appender<ILoggingEvent>> iterator) {
while (iterator.hasNext()) {
Appender<ILoggingEvent> appender = iterator.next();
if (appender instanceof SyslogAppender) {
return true;
}
}
return false;
}
/**
* Test CacheAppender and it's utility class CacheLogger
*/
@Test
public void testCacheLogger() {
final String testMessage = "Test logging event";
// Set up logging framework for testing
assertTrue("LogBack", LoggerFactory.getILoggerFactory() instanceof LoggerContext);
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.reset();
Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
TestAppender<ILoggingEvent> testAppender = new TestAppender<>();
rootLogger.addAppender(testAppender);
testAppender.setContext(context);
testAppender.start();
rootLogger.setLevel(Level.ERROR);
// Test basic functionality
assertFalse("CacheLoggerInactive", CacheLogger.isActive());
CacheLogger.startCaching();
rootLogger.error(testMessage);
assertTrue("CacheLoggerActive", CacheLogger.isActive());
CacheLogger.stopAndFlush();
assertEquals("LoggedMessage", testAppender.getLastEvent().getMessage(), testMessage);
assertFalse("CacheLoggerInactive", CacheLogger.isActive());
rootLogger.setLevel(Level.OFF);
// Test other CacheLogger functions
CacheLogger.startCaching();
assertTrue("AppenderIterator", findAppender(CacheLogger.iteratorForAppenders(), testAppender));
CacheLogger.removeAppender(testAppender);
assertFalse("AppenderRemoval", findAppender(CacheLogger.iteratorForAppenders(), testAppender));
TestAppender<ILoggingEvent> testAppender2 = new TestAppender<>();
CacheLogger.addAppender(testAppender2);
assertTrue("AppenderAdding", findAppender(CacheLogger.iteratorForAppenders(), testAppender2));
CacheLogger.stopAndFlush();
assertTrue("AppenderTransferred", findAppender(rootLogger.iteratorForAppenders(), testAppender2));
assertFalse("RemovedAppenderNotTransferred", findAppender(rootLogger.iteratorForAppenders(), testAppender));
// Cleanup
rootLogger.detachAppender(testAppender2);
}
/**
* Test
*
*/
@Test
public void testDebugLogPropertyDefiner() throws ConfigurationException {
// Set up PMS configuration
PMS.get();
PMS.setConfiguration(new PmsConfiguration());
DebugLogPropertyDefiner propertyDefiner = new DebugLogPropertyDefiner();
// Test logFilePath
propertyDefiner.setKey("logFilePath");
File file = new File(propertyDefiner.getPropertyValue());
assertTrue("logFilePathIsDirectory", file.isDirectory());
assertFalse("logFilePathIsNotFile", file.isFile());
// Test rootLevel
propertyDefiner.setKey("rootLevel");
assertNotNull("ValidLevel", Level.toLevel(propertyDefiner.getPropertyValue(), null));
// Test logFileName
propertyDefiner.setKey("logFileName");
assertTrue("ValidLogFileName", FileUtil.isValidFileName(propertyDefiner.getPropertyValue()));
}
@Test
public void testLoggingConfig() throws ConfigurationException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
// Set up a test (default) configuration
PMS.get();
PmsConfiguration configuration = new PmsConfiguration(false);
PMS.setConfiguration(configuration);
// Load logback configuration
LoggingConfig.loadFile();
// Silence logger
LoggingConfig.setRootLevel(Level.OFF);
// Get access to logger
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
/* During UMS build a valid configuration should be accessible at least under "external resources"
* and thus testing for a valid configuration is considered OK to be able to do the other tests.
* "internal defaults" is returned if a valid configuration can't be found.
*/
// Test for a valid configuration
File file = new File(LoggingConfig.getConfigFilePath());
assertTrue("LoggingConfigIsFile", file.isFile());
assertFalse("LoggingConfigIsFile", file.isDirectory());
// Test getLogFilePaths() and LoggingConfigFileLoader.getLogFilePaths()
HashMap<String, String> logFilePaths = LoggingConfig.getLogFilePaths();
@SuppressWarnings("deprecation")
HashMap<String, String> compLogFilePaths = LoggingConfigFileLoader.getLogFilePaths();
Iterator<Appender<ILoggingEvent>> iterator = rootLogger.iteratorForAppenders();
while (iterator.hasNext()) {
Appender<ILoggingEvent> appender = iterator.next();
if (appender instanceof FileAppender) {
FileAppender<ILoggingEvent> fa = (FileAppender<ILoggingEvent>) appender;
assertTrue("LogFilePathsContainsKey", logFilePaths.containsKey(fa.getName()));
assertEquals("LogFilePathsHasPath", logFilePaths.get(fa.getName()), fa.getFile());
if (fa.getName().equals("default.log")) {
assertTrue("CompatibleLogFilePathsContainsKey", compLogFilePaths.containsKey("debug.log"));
assertEquals("CompatibleLogFilePathsHasPath", compLogFilePaths.get("debug.log"), fa.getFile());
} else {
assertTrue("CompatibleLogFilePathsContainsKey", compLogFilePaths.containsKey(fa.getName()));
assertEquals("CompatibleLogFilePathsHasPath", compLogFilePaths.get(fa.getName()), fa.getFile());
}
}
}
// Reset LogBack configuration and create a fake one to not rely on the existing configuration file
context.reset();
TestFileAppender<ILoggingEvent> testDefaultAppender = new TestFileAppender<>();
testDefaultAppender.setName("default.log");
testDefaultAppender.setContext(context);
PatternLayoutEncoder layoutEncoder = new PatternLayoutEncoder();
layoutEncoder.setPattern("%-5level %d{HH:mm:ss.SSS} [%thread] %msg%n");
layoutEncoder.setContext(context);
testDefaultAppender.setEncoder(layoutEncoder);
rootLogger.addAppender(testDefaultAppender);
TestFileAppender<ILoggingEvent> testGenericAppender = new TestFileAppender<>();
testGenericAppender.setName("SomeOtherFileAppender");
testGenericAppender.setContext(context);
layoutEncoder = new PatternLayoutEncoder();
layoutEncoder.setPattern("%-5level %d %msg%n");
layoutEncoder.setContext(context);
testGenericAppender.setEncoder(layoutEncoder);
rootLogger.addAppender(testGenericAppender);
TestAppender<ILoggingEvent> testNonFileAppender = new TestAppender<>();
testNonFileAppender.setName("SomeNonFileAppender");
testNonFileAppender.setContext(context);
rootLogger.addAppender(testNonFileAppender);
// Test setBuffered()
LoggingConfig.setBuffered(true);
iterator = rootLogger.iteratorForAppenders();
while (iterator.hasNext()) {
Appender<ILoggingEvent> appender = iterator.next();
if (appender instanceof OutputStreamAppender && !(appender instanceof ConsoleAppender<?>)) {
// Appender has ImmediateFlush property
assertFalse("LogFileIsBuffered", ((OutputStreamAppender<ILoggingEvent>) appender).isImmediateFlush());
}
}
LoggingConfig.setBuffered(false);
iterator = rootLogger.iteratorForAppenders();
while (iterator.hasNext()) {
Appender<ILoggingEvent> appender = iterator.next();
if (appender instanceof OutputStreamAppender && !(appender instanceof ConsoleAppender<?>)) {
assertTrue("LogFileIsNotBuffered", ((OutputStreamAppender<ILoggingEvent>) appender).isImmediateFlush());
// Appender has ImmediateFlush property
}
}
// Test getRootLevel()
assertEquals("GetRootLevel", LoggingConfig.getRootLevel(), rootLogger.getLevel());
// Test setRootLevel()
LoggingConfig.setRootLevel(Level.ALL);
assertEquals("SetRootLevel", LoggingConfig.getRootLevel(), Level.ALL);
LoggingConfig.setRootLevel(Level.INFO);
assertEquals("SetRootLevel", LoggingConfig.getRootLevel(), Level.INFO);
LoggingConfig.setRootLevel(Level.OFF);
// Test setConsoleFilter()
configuration.setLoggingFilterConsole(Level.WARN);
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
consoleAppender.setContext(context);
PatternLayoutEncoder patternEncoder = new PatternLayoutEncoder();
patternEncoder.setPattern("%msg%n");
patternEncoder.setContext(context);
patternEncoder.start();
consoleAppender.setEncoder(patternEncoder);
consoleAppender.start();
rootLogger.addAppender(consoleAppender);
LoggingConfig.setConsoleFilter();
List<Filter<ILoggingEvent>> filterList = consoleAppender.getCopyOfAttachedFiltersList();
assertEquals("NumberOfConsoleFilters", filterList.size(), 1);
assertTrue("ConsoleFilterIsThresholdFilter", filterList.get(0) instanceof ThresholdFilter);
ThresholdFilter thresholdFilter = (ThresholdFilter) filterList.get(0);
Field field = thresholdFilter.getClass().getDeclaredField("level");
field.setAccessible(true);
assertEquals("ConsoleFilterLevel", field.get(thresholdFilter), Level.WARN);
configuration.setLoggingFilterConsole(Level.TRACE);
LoggingConfig.setConsoleFilter();
filterList = consoleAppender.getCopyOfAttachedFiltersList();
assertEquals("NumberOfConsoleFilters", filterList.size(), 1);
assertTrue("ConsoleFilterIsThresholdFilter", filterList.get(0) instanceof ThresholdFilter);
thresholdFilter = (ThresholdFilter) filterList.get(0);
field = thresholdFilter.getClass().getDeclaredField("level");
field.setAccessible(true);
assertEquals("ConsoleFilterLevel", field.get(thresholdFilter), Level.TRACE);
rootLogger.detachAppender(consoleAppender);
// Test setTracesFilter()
configuration.setLoggingFilterLogsTab(Level.WARN);
FrameAppender<ILoggingEvent> frameAppender = new FrameAppender<>();
frameAppender.setContext(context);
patternEncoder = new PatternLayoutEncoder();
patternEncoder.setPattern("%msg%n");
patternEncoder.setContext(context);
patternEncoder.start();
frameAppender.setEncoder(patternEncoder);
frameAppender.start();
rootLogger.addAppender(frameAppender);
LoggingConfig.setTracesFilter();
filterList = frameAppender.getCopyOfAttachedFiltersList();
assertEquals("NumberOfTracesFilters", filterList.size(), 1);
assertTrue("TracesFilterIsThresholdFilter", filterList.get(0) instanceof ThresholdFilter);
thresholdFilter = (ThresholdFilter) filterList.get(0);
field = thresholdFilter.getClass().getDeclaredField("level");
field.setAccessible(true);
assertEquals("TracesFilterLevel", field.get(thresholdFilter), Level.WARN);
configuration.setLoggingFilterLogsTab(Level.TRACE);
LoggingConfig.setTracesFilter();
filterList = frameAppender.getCopyOfAttachedFiltersList();
assertEquals("NumberOfTracesFilters", filterList.size(), 1);
assertTrue("TracesFilterIsThresholdFilter", filterList.get(0) instanceof ThresholdFilter);
thresholdFilter = (ThresholdFilter) filterList.get(0);
field = thresholdFilter.getClass().getDeclaredField("level");
field.setAccessible(true);
assertEquals("TracesFilterLevel", field.get(thresholdFilter), Level.TRACE);
rootLogger.detachAppender(frameAppender);
// Test isSyslogDisabled()
if (syslogAppenderFound(rootLogger.iteratorForAppenders())) {
assertTrue("SyslogDisabledByConfiguration", LoggingConfig.isSyslogDisabled());
} else {
assertFalse("SyslogNotDisabledByConfiguration", LoggingConfig.isSyslogDisabled());
}
// Test setSyslog() if possible
if (!syslogAppenderFound(rootLogger.iteratorForAppenders())) {
configuration.setLoggingSyslogHost("localhost");
configuration.setLoggingUseSyslog(true);
LoggingConfig.setSyslog();
assertTrue("SyslogEnabled", syslogAppenderFound(rootLogger.iteratorForAppenders()));
configuration.setLoggingUseSyslog(false);
LoggingConfig.setSyslog();
assertFalse("SyslogDisabled", syslogAppenderFound(rootLogger.iteratorForAppenders()));
}
// Test forceVerboseFileEncoder() given that LogBack configuration
// contains at least one file appender with PatternLayoutEncoder
LoggingConfig.forceVerboseFileEncoder();
iterator = rootLogger.iteratorForAppenders();
while (iterator.hasNext()) {
Appender<ILoggingEvent> appender = iterator.next();
if (appender instanceof OutputStreamAppender && !(appender instanceof ConsoleAppender<?>)) {
// Appender has Encoder property
Encoder<ILoggingEvent> encoder = ((OutputStreamAppender<ILoggingEvent>) appender).getEncoder();
if (encoder instanceof PatternLayoutEncoder) {
// Encoder has pattern
patternEncoder = (PatternLayoutEncoder) encoder;
assertTrue("AppenderPatternHasCorrectTimestamp", patternEncoder.getPattern().matches(".*%(d|date)\\{yyyy-MM-dd HH:mm:ss.SSS\\}.*"));
assertTrue("AppenderPatternHasLogger", patternEncoder.getPattern().matches(".*%logger.*"));
}
}
}
context.reset();
}
}