This module bridges logback configuration over config-subsystem, allowing to reconfigure logback via JMX.
Change-Id: I52644b628d384d8ca015885cc9f016f4429238d4
Signed-off-by: Tomas Olvecky <tolvecky@cisco.com>
--- /dev/null
+<?xml version="1.0"?>
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>config-subsystem</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>logback-config</artifactId>
+ <name>${project.artifactId}</name>
+ <packaging>bundle</packaging>
+ <prerequisites>
+ <maven>3.0.4</maven>
+ </prerequisites>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-api</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>concepts</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+
+ <!-- test dependencies -->
+
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-manager</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-manager</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>config-util</artifactId>
+ <version>0.2.1-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.bgpcep</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ <version>0.2.0-SNAPSHOT</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Private-Package>
+ </Private-Package>
+ <Import-Package>
+ ch.qos.logback.classic,
+ ch.qos.logback.classic.encoder,
+ ch.qos.logback.classic.filter,
+ ch.qos.logback.classic.spi,
+ ch.qos.logback.core,
+ ch.qos.logback.core.status,
+ ch.qos.logback.core.encoder,
+ ch.qos.logback.core.rolling,
+ org.opendaylight.protocol.concepts,
+ org.opendaylight.controller.config.api,
+ org.opendaylight.controller.config.api.runtime,
+ org.opendaylight.controller.config.api.annotations,
+ org.opendaylight.controller.config.spi,
+ com.google.common.base,
+ com.google.common.collect,
+ org.apache.commons.lang3,
+ org.slf4j
+ </Import-Package>
+ <Export-Package>
+ org.opendaylight.controller.config.yang.logback.config,
+ </Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test yang module local name: testing
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 14:06:33 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+/**
+ * Updates current state of Logback configuration.
+ */
+public interface ContextSetter {
+
+ public void updateContext(LogbackModule module);
+
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test yang module local name: testing
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 14:06:33 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.*;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+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.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
+import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+
+/**
+ * Implementation of {@link ContextSetter}. Resets running logback
+ * configuration.
+ */
+public class ContextSetterImpl implements ContextSetter, Closeable {
+
+ private final LogbackStatusListener statusListener;
+ private static final org.slf4j.Logger classLogger = LoggerFactory
+ .getLogger(ContextSetterImpl.class);
+
+ public ContextSetterImpl(
+ LogbackRuntimeRegistrator rootRuntimeBeanRegistratorWrapper) {
+ statusListener = new LogbackStatusListener(
+ rootRuntimeBeanRegistratorWrapper);
+ statusListener.register();
+ }
+
+ public void updateContext(LogbackModule module) {
+ LoggerContext context = (LoggerContext) LoggerFactory
+ .getILoggerFactory();
+
+ List<ch.qos.logback.classic.Logger> loggersBefore = context
+ .getLoggerList();
+
+ createLoggers(context, module, Sets.newHashSet(loggersBefore));
+ }
+
+ private Map<String, Appender<ILoggingEvent>> createConsoleAppenders(
+ LoggerContext context, LogbackModule module) {
+ Map<String, Appender<ILoggingEvent>> appendersMap = new HashMap<>();
+ for (ConsoleAppenderTO appender : module.getConsoleAppenderTO()) {
+ Preconditions.checkState(
+ appendersMap.containsKey(appender.getName()) == false,
+ "Duplicate appender name %s", appender.getName());
+ ch.qos.logback.core.ConsoleAppender app = new ch.qos.logback.core.ConsoleAppender();
+ app.setContext(context);
+ PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+ encoder.setContext(context);
+ encoder.setPattern(appender.getEncoderPattern());
+ encoder.start();
+ app.setEncoder(encoder);
+ ThresholdFilter filter = new ThresholdFilter();
+ filter.setContext(context);
+ filter.setLevel(appender.getThresholdFilter());
+ filter.start();
+ app.getCopyOfAttachedFiltersList().add(filter);
+ app.setName(appender.getName());
+ app.start();
+ appendersMap.put(app.getName(), app);
+ }
+ return appendersMap;
+ }
+
+ private void createLoggers(LoggerContext context, LogbackModule module,
+ Set<ch.qos.logback.classic.Logger> loggersBefore) {
+
+ Map<String, Appender<ILoggingEvent>> appendersMap = getAppenders(
+ module, context);
+
+ for (LoggerTO logger : module.getLoggerTO()) {
+ classLogger.trace("Setting configuration for logger {}",
+ logger.getLoggerName());
+ final ch.qos.logback.classic.Logger logbackLogger = context
+ .getLogger(logger.getLoggerName());
+
+ Optional<Set<Appender<ILoggingEvent>>> appendersBefore = getAppendersBefore(
+ loggersBefore, logbackLogger);
+ classLogger.trace("Logger {}: Appenders registered before: {}",
+ logger.getLoggerName(),
+ appendersBefore.isPresent() ? appendersBefore.get()
+ : "NO APPENDERS BEFORE");
+
+ logbackLogger.setLevel(Level.toLevel(logger.getLevel()));
+
+ addNewAppenders(appendersMap, logger, logbackLogger,
+ appendersBefore);
+ removeBeforeAppenders(loggersBefore, logger, logbackLogger,
+ appendersBefore);
+ }
+ }
+
+ private void addNewAppenders(
+ Map<String, Appender<ILoggingEvent>> appendersMap, LoggerTO logger,
+ ch.qos.logback.classic.Logger logbackLogger,
+ Optional<Set<Appender<ILoggingEvent>>> appendersBefore) {
+ for (String appenderName : logger.getAppenders()) {
+ if (appendersMap.containsKey(appenderName)) {
+ logbackLogger.addAppender(appendersMap.get(appenderName));
+ classLogger.trace("Logger {}: Adding new appender: {}",
+ logger.getLoggerName(), appenderName);
+ } else {
+ throw new IllegalStateException(
+ "No appender "
+ + appenderName
+ + " found. This error should have been discovered by validation");
+ }
+ }
+ }
+
+ private void removeBeforeAppenders(
+ Set<ch.qos.logback.classic.Logger> loggersBefore, LoggerTO logger,
+ ch.qos.logback.classic.Logger logbackLogger,
+ Optional<Set<Appender<ILoggingEvent>>> appendersBefore) {
+ if (appendersBefore.isPresent()) {
+ for (Appender<ILoggingEvent> appenderBefore : appendersBefore.get()) {
+ logbackLogger.detachAppender(appenderBefore);
+ appenderBefore.stop();
+ classLogger.trace("Logger {}: Removing old appender: {}",
+ logger.getLoggerName(), appenderBefore.getName());
+ }
+ loggersBefore.remove(logbackLogger);
+ }
+ }
+
+ private Optional<Set<Appender<ILoggingEvent>>> getAppendersBefore(
+ Set<ch.qos.logback.classic.Logger> loggersBefore,
+ ch.qos.logback.classic.Logger logbackLogger) {
+ if (loggersBefore.contains(logbackLogger)) {
+ Iterator<Appender<ILoggingEvent>> appenderIt = logbackLogger
+ .iteratorForAppenders();
+ Set<Appender<ILoggingEvent>> appendersBefore = Sets.newHashSet();
+ while (appenderIt.hasNext()) {
+ appendersBefore.add(appenderIt.next());
+ }
+ return Optional.of(appendersBefore);
+ } else
+ return Optional.absent();
+
+ }
+
+ private Map<String, Appender<ILoggingEvent>> getAppenders(
+ LogbackModule module, LoggerContext context) {
+ Map<String, Appender<ILoggingEvent>> appendersMap = new HashMap<>();
+ addAllAppenders(appendersMap, createRollingAppenders(context, module));
+ addAllAppenders(appendersMap, createConsoleAppenders(context, module));
+
+ return appendersMap;
+ }
+
+ private void addAllAppenders(
+ Map<String, Appender<ILoggingEvent>> allAppenders,
+ Map<String, Appender<ILoggingEvent>> appendersToAdd) {
+ for (String appenderName : appendersToAdd.keySet()) {
+ Preconditions.checkState(
+ allAppenders.containsKey(appenderName) == false,
+ "Duplicate appender name %s", appenderName);
+ allAppenders.put(appenderName, appendersToAdd.get(appenderName));
+ }
+ }
+
+ private Map<String, Appender<ILoggingEvent>> createRollingAppenders(
+ LoggerContext context, LogbackModule module) {
+ Map<String, Appender<ILoggingEvent>> appendersMap = new HashMap<>();
+ for (RollingFileAppenderTO appender : module.getRollingFileAppenderTO()) {
+ Preconditions.checkState(
+ appendersMap.containsKey(appender.getName()) == false,
+ "Duplicate appender name %s", appender.getName());
+ ch.qos.logback.core.rolling.RollingFileAppender app = new ch.qos.logback.core.rolling.RollingFileAppender<>();
+ app.setAppend(appender.getAppend());
+ app.setContext(context);
+ PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+ encoder.setContext(context);
+ encoder.setPattern(appender.getEncoderPattern());
+ encoder.start();
+ app.setEncoder(encoder);
+ app.setFile(appender.getFileName());
+ FixedWindowRollingPolicy policy = new FixedWindowRollingPolicy();
+ policy.setContext(context);
+ policy.setMaxIndex(appender.getMaxIndex());
+ policy.setMinIndex(appender.getMinIndex());
+ policy.setFileNamePattern(appender.getFileNamePattern());
+ policy.setParent(app);
+ policy.start();
+ app.setRollingPolicy(policy);
+ SizeBasedTriggeringPolicy triggeringPolicy = new SizeBasedTriggeringPolicy();
+ triggeringPolicy.setContext(context);
+ triggeringPolicy.setMaxFileSize(appender.getMaxFileSize());
+ triggeringPolicy.start();
+ app.setTriggeringPolicy(triggeringPolicy);
+ app.setName(appender.getName());
+ app.start();
+ appendersMap.put(app.getName(), app);
+ }
+ return appendersMap;
+ }
+
+ @Override
+ public void close() throws IOException {
+ statusListener.close();
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Wed Jul 17 15:26:45 CEST 2013
+ *
+ * Do not modifiy this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import java.util.Set;
+
+import org.opendaylight.controller.config.api.JmxAttribute;
+import org.opendaylight.controller.config.api.JmxAttributeValidationException;
+
+import com.google.common.collect.Sets;
+
+/**
+*
+*/
+public final class LogbackModule
+ extends
+ org.opendaylight.controller.config.yang.logback.config.AbstractLogbackModule {
+
+ public LogbackModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(name, dependencyResolver);
+ }
+
+ public LogbackModule(
+ org.opendaylight.controller.config.api.ModuleIdentifier name,
+ org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ org.opendaylight.controller.config.yang.logback.config.AbstractLogbackModule oldModule,
+ java.lang.AutoCloseable oldInstance) {
+ super(name, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ Set<String> appenderNames = Sets.newHashSet();
+ validateRollingObjects(appenderNames);
+ validateConsoleObjects(appenderNames);
+ validateLoggersObjects(appenderNames);
+ }
+
+ private void validateLoggersObjects(Set<String> appenderNames) {
+ JmxAttributeValidationException.checkNotNull(getLoggerTO(),
+ loggersJmxAttribute);
+
+ for (LoggerTO loggerToValidate : getLoggerTO()) {
+ JmxAttributeValidationException.checkNotNull(
+ loggerToValidate.getLoggerName(), "LoggerName is null",
+ loggersJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(
+ loggerToValidate.getLevel(), "Level is null",
+ loggersJmxAttribute);
+ JmxAttributeValidationException.checkCondition(!loggerToValidate
+ .getLoggerName().isEmpty(), "LoggerName needs to be set",
+ loggersJmxAttribute);
+ JmxAttributeValidationException.checkCondition(!loggerToValidate
+ .getLevel().isEmpty(), "Level needs to be set",
+ loggersJmxAttribute);
+
+ for (String appenderName : loggerToValidate.getAppenders()) {
+ JmxAttributeValidationException
+ .checkCondition(
+ appenderNames.contains(appenderName),
+ "Appender "
+ + appenderName
+ + " referenced by logger "
+ + loggerToValidate.getLoggerName()
+ + " not present in configuration, present appenders: "
+ + appenderNames, loggersJmxAttribute);
+ }
+
+ }
+ }
+
+ private void validateConsoleObjects(Set<String> appenderNames) {
+
+ JmxAttributeValidationException.checkNotNull(getConsoleAppenderTO(),
+ consoleAppendersJmxAttribute);
+ for (ConsoleAppenderTO object : getConsoleAppenderTO()) {
+ JmxAttributeValidationException.checkNotNull(
+ object.getEncoderPattern(), "EncoderPattern is null",
+ consoleAppendersJmxAttribute);
+
+ validateAppenderName(appenderNames, object.getName(),
+ consoleAppendersJmxAttribute);
+
+ JmxAttributeValidationException.checkNotNull(
+ object.getThresholdFilter(), "Filterlevel is null",
+ consoleAppendersJmxAttribute);
+ }
+ }
+
+ private void validateRollingObjects(Set<String> appenderNames) {
+
+ JmxAttributeValidationException.checkNotNull(getRollingFileAppenderTO(),
+ rollingAppendersJmxAttribute);
+ for (RollingFileAppenderTO object : getRollingFileAppenderTO()) {
+ JmxAttributeValidationException.checkNotNull(
+ object.getEncoderPattern(), "EncoderPattern is null",
+ rollingAppendersJmxAttribute);
+
+ validateAppenderName(appenderNames, object.getName(),
+ rollingAppendersJmxAttribute);
+
+ JmxAttributeValidationException.checkNotNull(object.getFileName(),
+ "FileName is null", rollingAppendersJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(
+ object.getMaxFileSize(), "MaxFileSize is null",
+ rollingAppendersJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(object.getMinIndex(),
+ "MinIndex is null", rollingAppendersJmxAttribute);
+ JmxAttributeValidationException.checkNotNull(object.getMaxIndex(),
+ "MaxIndex is null", rollingAppendersJmxAttribute);
+ JmxAttributeValidationException.checkCondition(!object
+ .getEncoderPattern().isEmpty(),
+ "EncoderPattern needs to be set",
+ rollingAppendersJmxAttribute);
+ JmxAttributeValidationException.checkCondition(!object
+ .getFileName().isEmpty(), "FileName needs to be set",
+ rollingAppendersJmxAttribute);
+
+ }
+ }
+
+ private void validateAppenderName(Set<String> appenderNames,
+ String appenderName, JmxAttribute jmxAttribute) {
+ JmxAttributeValidationException.checkNotNull(appenderName,
+ "Name is null", jmxAttribute);
+ JmxAttributeValidationException.checkCondition(
+ appenderNames.contains(appenderName) == false,
+ "Duplicate appender name " + appenderName, jmxAttribute);
+ appenderNames.add(appenderName);
+ JmxAttributeValidationException.checkCondition(!appenderName.isEmpty(),
+ "Name needs to be set", jmxAttribute);
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ ContextSetterImpl setter = new ContextSetterImpl(
+ getRootRuntimeBeanRegistratorWrapper());
+
+ setter.updateContext(this);
+
+ return setter;
+ }
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Wed Jul 17 15:26:45 CEST 2013
+ *
+ * Do not modifiy this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.StringUtils;
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggerComparator;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
+import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+/**
+*
+*/
+public class LogbackModuleFactory
+ extends
+ org.opendaylight.controller.config.yang.logback.config.AbstractLogbackModuleFactory {
+
+ private static final String INSTANCE_NAME = "singleton";
+ private Map<String, LoggerTO> loggersDTOs;
+ private Map<String, RollingFileAppenderTO> rollingDTOs;
+ private Map<String, ConsoleAppenderTO> consoleDTOs;
+
+ @Override
+ public LogbackModule instantiateModule(String instanceName,
+ DependencyResolver dependencyResolver) {
+ Preconditions.checkArgument(instanceName.equals(INSTANCE_NAME),
+ "There should be just one instance of logback, named "
+ + INSTANCE_NAME);
+ prepareDTOs();
+ LogbackModule module = new LogbackModule(new ModuleIdentifier(
+ getImplementationName(), INSTANCE_NAME), dependencyResolver);
+ module.setConsoleAppenderTO(Lists.newArrayList(consoleDTOs.values()));
+ module.setRollingFileAppenderTO(Lists.newArrayList(rollingDTOs.values()));
+ module.setLoggerTO(Lists.newArrayList(loggersDTOs.values()));
+ return module;
+ }
+
+ @Override
+ public LogbackModule instantiateModule(String instanceName,
+ DependencyResolver dependencyResolver, LogbackModule oldModule,
+ AutoCloseable oldInstance) {
+ Preconditions.checkArgument(instanceName.equals(INSTANCE_NAME),
+ "There should be just one instance of logback, named "
+ + INSTANCE_NAME);
+ prepareDTOs();
+ LogbackModule module = new LogbackModule(new ModuleIdentifier(
+ getImplementationName(), INSTANCE_NAME), dependencyResolver,
+ oldModule, oldInstance);
+ module.setConsoleAppenderTO(Lists.newArrayList(consoleDTOs.values()));
+ /*
+ * module.setJCloudsAppender(Lists.newArrayList(jcloudsDTOs.values()));
+ */
+ module.setRollingFileAppenderTO(Lists.newArrayList(rollingDTOs.values()));
+ module.setLoggerTO(Lists.newArrayList(loggersDTOs.values()));
+ return module;
+ }
+
+ private void prepareDTOs() {
+ LoggerContext context = (LoggerContext) LoggerFactory
+ .getILoggerFactory();
+ this.loggersDTOs = prepareLoggersDTOs(context);
+ prepareAppendersDTOs(context);
+ }
+
+ private void prepareAppendersDTOs(LoggerContext context) {
+ this.rollingDTOs = new HashMap<>();
+ this.consoleDTOs = new HashMap<>();
+ ch.qos.logback.core.rolling.RollingFileAppender<ILoggingEvent> rollingApp;
+ ch.qos.logback.core.ConsoleAppender<ILoggingEvent> consoleApp;
+ Map<Logger, List<Appender<ILoggingEvent>>> appendersAll = new HashMap<>();
+ for (Logger log : context.getLoggerList()) {
+ List<Appender<ILoggingEvent>> appenders = new ArrayList<>();
+ Iterator<Appender<ILoggingEvent>> iter = log.iteratorForAppenders();
+ while (iter.hasNext()) {
+ Appender<ILoggingEvent> element = iter.next();
+ appenders.add(element);
+ }
+ appendersAll.put(log, appenders);
+ }
+ for (List<ch.qos.logback.core.Appender<ILoggingEvent>> appEntry : appendersAll.values()) {
+ for (ch.qos.logback.core.Appender<ILoggingEvent> appender : appEntry) {
+ if (appender instanceof ch.qos.logback.core.rolling.RollingFileAppender<?>) {
+ RollingFileAppenderTO app = new RollingFileAppenderTO();
+ rollingApp = (ch.qos.logback.core.rolling.RollingFileAppender< ILoggingEvent >) appender;
+ app.setAppend(rollingApp.isAppend());
+ PatternLayoutEncoder encoder = (PatternLayoutEncoder) rollingApp
+ .getEncoder();
+ app.setEncoderPattern(encoder.getPattern());
+ app.setFileName(rollingApp.getFile());
+ FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) rollingApp
+ .getRollingPolicy();
+ app.setMaxIndex(rollingPolicy.getMaxIndex());
+ app.setMinIndex(rollingPolicy.getMinIndex());
+ SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = (SizeBasedTriggeringPolicy<ILoggingEvent>) rollingApp
+ .getTriggeringPolicy();
+ app.setMaxFileSize(triggeringPolicy.getMaxFileSize());
+ app.setFileNamePattern(rollingPolicy.getFileNamePattern());
+ app.setName(rollingApp.getName());
+ this.rollingDTOs.put(rollingApp.getName(), app);
+ }
+ if (appender instanceof ch.qos.logback.core.ConsoleAppender<?>) {
+ ConsoleAppenderTO app = new ConsoleAppenderTO();
+ consoleApp = (ch.qos.logback.core.ConsoleAppender<ILoggingEvent>) appender;
+ consoleApp.getCopyOfAttachedFiltersList();
+ PatternLayoutEncoder encoder = (PatternLayoutEncoder) consoleApp
+ .getEncoder();
+ app.setEncoderPattern(encoder.getPattern());
+ app.setName(consoleApp.getName());
+ app.setThresholdFilter(context.getLogger(
+ Logger.ROOT_LOGGER_NAME).getEffectiveLevel().levelStr);
+ this.consoleDTOs.put(consoleApp.getName(), app);
+ }
+ }
+ }
+ }
+
+ private Map<String, LoggerTO> prepareLoggersDTOs(
+ LoggerContext context) {
+ Map<String, LoggerTO> DTOs = new HashMap<>();
+ List<String> appenders = new ArrayList<>();
+ List<org.slf4j.Logger> loggersToBeAdd = removeUnusableLoggers(
+ context.getLoggerList(),
+ context.getLogger(Logger.ROOT_LOGGER_NAME));
+ for (org.slf4j.Logger log : loggersToBeAdd) {
+ LoggerTO logger = new LoggerTO();
+ if (((Logger) log).getLevel() != null)
+ logger.setLevel(((Logger) log).getLevel().levelStr);
+ else
+ logger.setLevel(((Logger) log).getEffectiveLevel().levelStr);
+ logger.setLoggerName(log.getName());
+ Iterator<Appender<ILoggingEvent>> iter = ((Logger) log)
+ .iteratorForAppenders();
+ while (iter.hasNext()) {
+ Appender<ILoggingEvent> element = iter.next();
+ appenders.add(element.getName());
+ }
+ logger.setAppenders(appenders);
+ DTOs.put(log.getName(), logger);
+ appenders = new ArrayList<>();
+
+ }
+ return DTOs;
+ }
+
+ private List<org.slf4j.Logger> removeUnusableLoggers(
+ List<Logger> loggerList, Logger rootLogger) {
+ Collections.sort(loggerList, new LoggerComparator());
+ Map<String, org.slf4j.Logger> loggersToReturn = new HashMap<>();
+
+ for (org.slf4j.Logger log : loggerList) {
+ boolean shouldAdd = true;
+ for (Entry<String, org.slf4j.Logger> entry : loggersToReturn
+ .entrySet()) {
+ if (StringUtils.contains(log.getName(), entry.getKey())) {
+ if (((Logger) log).getLevel() != null
+ && ((Logger) log).getLevel().equals(
+ ((Logger) entry.getValue()).getLevel())
+ && !((Logger) log).iteratorForAppenders().hasNext()) {
+ shouldAdd = false;
+ break;
+ }
+ if (((Logger) log).getLevel() == null
+ && ((Logger) log).getEffectiveLevel().equals(
+ ((Logger) entry.getValue())
+ .getEffectiveLevel())
+ && !((Logger) log).iteratorForAppenders().hasNext()) {
+ shouldAdd = false;
+ break;
+ }
+ }
+ if (((Logger) log).getLevel() != null
+ && ((Logger) log).getLevel().equals(
+ rootLogger.getLevel())
+ && !((Logger) log).iteratorForAppenders().hasNext()) {
+ shouldAdd = false;
+ break;
+ }
+ if (((Logger) log).getLevel() == null
+ && ((Logger) log).getEffectiveLevel().equals(
+ rootLogger.getEffectiveLevel())
+ && !((Logger) log).iteratorForAppenders().hasNext()) {
+ shouldAdd = false;
+ break;
+ }
+ }
+ if (shouldAdd) {
+ loggersToReturn.put(log.getName(), log);
+ }
+ }
+ return Lists.newArrayList(loggersToReturn.values());
+ }
+
+}
--- /dev/null
+/**
+ * Generated file
+
+ * Generated from: yang module name: config-test yang module local name: testing
+ * Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+ * Generated at: Fri Sep 27 14:06:33 CEST 2013
+ *
+ * Do not modify this file unless it is present under src/main directory
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import ch.qos.logback.core.status.Status;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.status.StatusBase;
+import ch.qos.logback.core.status.StatusListener;
+import ch.qos.logback.core.status.StatusManager;
+
+public class LogbackStatusListener implements StatusListener,
+ LogbackRuntimeMXBean, Closeable {
+
+ private final List<StatusTO> receivedStatuses;
+ private final LogbackRuntimeRegistrator rootRuntimeBeanRegistratorWrapper;
+ private LogbackRuntimeRegistration reg;
+
+ public LogbackStatusListener(
+ LogbackRuntimeRegistrator rootRuntimeBeanRegistratorWrapper) {
+ receivedStatuses = new ArrayList<>();
+ this.rootRuntimeBeanRegistratorWrapper = rootRuntimeBeanRegistratorWrapper;
+ }
+
+ @Override
+ public synchronized List<StatusTO> getStatusTO() {
+ return Collections.unmodifiableList(receivedStatuses);
+ }
+
+ @Override
+ public synchronized void reset() {
+ receivedStatuses.clear();
+ }
+
+ public LogbackRuntimeRegistration register() {
+ reg = registerToJMX(rootRuntimeBeanRegistratorWrapper);
+ registerToLogback();
+ return reg;
+ }
+
+ private LogbackRuntimeRegistration registerToJMX(
+ LogbackRuntimeRegistrator rootRuntimeBeanRegistratorWrapper) {
+ return rootRuntimeBeanRegistratorWrapper.register(this);
+ }
+
+ private synchronized void registerToLogback() {
+ LoggerContext context = (LoggerContext) LoggerFactory
+ .getILoggerFactory();
+ final StatusManager statusManager = context.getStatusManager();
+
+ statusManager.remove(this);
+ reset();
+
+ statusManager.add(this);
+ addInitialStatuses(statusManager);
+ }
+
+ private void addInitialStatuses(StatusManager statusManager) {
+ for (ch.qos.logback.core.status.Status status : statusManager
+ .getCopyOfStatusList()) {
+ addStatusEvent(status);
+ }
+ }
+
+ @Override
+ public synchronized void addStatusEvent(
+ ch.qos.logback.core.status.Status status) {
+ receivedStatuses.add(transformStatus(status));
+ }
+
+ private StatusTO transformStatus(ch.qos.logback.core.status.Status status) {
+ StatusTO transformed = new StatusTO();
+
+ transformed.setDate(status.getDate());
+ transformed.setLevel(transformStatusLevel(status.getLevel()));
+ transformed.setMessage(status.getMessage());
+
+ return transformed;
+ }
+
+ private String transformStatusLevel(int status) {
+ switch (status) {
+ case StatusBase.INFO:
+ return "INFO";
+ case StatusBase.WARN:
+ return "WARN";
+ case StatusBase.ERROR:
+ return "ERROR";
+ default:
+ throw new IllegalStateException("Unknown status level " + status);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (reg != null)
+ reg.close();
+ unregisterFromLogback();
+ }
+
+ private void unregisterFromLogback() {
+ LoggerContext context = (LoggerContext) LoggerFactory
+ .getILoggerFactory();
+ final StatusManager statusManager = context.getStatusManager();
+ statusManager.remove(this);
+ }
+}
--- /dev/null
+// vi: set smarttab et sw=4 tabstop=4:
+module config-logging {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:logback:config";
+ prefix "logging";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import rpc-context { prefix rpcx; revision-date 2013-06-17; }
+
+ organization "Cisco Systems, Inc.";
+
+ description
+ "This module contains the base YANG definitions for NS-OS
+ logging module.";
+
+ revision "2013-07-16" {
+ description
+ "Initial revision.";
+ }
+
+ identity logback {
+ description
+ "Actual state of logback configuration.";
+ base config:module-type;
+ config:java-name-prefix Logback;
+ }
+
+ identity logback-rpc;
+
+ augment "/config:modules/config:module/config:configuration" {
+ case logback {
+ when "/config:modules/config:module/config:type = 'logback'";
+
+ list rolling-appenders {
+ leaf append {
+ type boolean;
+ mandatory false;
+ }
+
+ leaf file-name {
+ type string;
+ mandatory true;
+ }
+
+ leaf encoder-pattern {
+ type string;
+ mandatory true;
+ }
+
+ leaf min-index {
+ type int32;
+ mandatory true;
+ }
+
+ leaf max-index {
+ type int32;
+ mandatory true;
+ }
+
+ leaf max-file-size {
+ type string;
+ mandatory true;
+ }
+
+ leaf name {
+ type string;
+ mandatory true;
+ }
+
+ leaf file-name-pattern {
+ type string;
+ mandatory true;
+ }
+
+ config:java-name-prefix RollingFileAppenderTO;
+ }
+
+ list console-appenders {
+
+ leaf encoder-pattern {
+ type string;
+ mandatory true;
+ }
+
+ leaf threshold-filter {
+ type string;
+ default 'ALL';
+ }
+
+ leaf name {
+ type string;
+ mandatory true;
+ }
+ config:java-name-prefix ConsoleAppenderTO;
+ }
+
+ list loggers {
+ leaf logger-name {
+ type string;
+ mandatory true;
+ }
+
+ leaf level {
+ type string;
+ mandatory true;
+ }
+
+ leaf-list appenders {
+ type string;
+ }
+ config:java-name-prefix LoggerTO;
+ }
+ }
+ }
+
+
+ augment "/config:modules/config:module/config:state" {
+ case logback {
+ when "/config:modules/config:module/config:type = 'logback'";
+
+ rpcx:rpc-context-instance "logback-rpc";
+
+ list status {
+ config:java-name-prefix StatusTO;
+
+ leaf level {
+ type string;
+ }
+
+ leaf message {
+ type string;
+ }
+
+ leaf date {
+ type uint32;
+ }
+ }
+ }
+ }
+
+ rpc reset {
+ input {
+ uses rpcx:rpc-context-ref {
+ refine context-instance {
+ rpcx:rpc-context-instance logback-rpc;
+ }
+ }
+ }
+ }
+
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+public class ContextSetterImplTest {
+
+ @Mock
+ private LogbackRuntimeRegistrator runtimeRegistratorMock;
+ @Mock
+ private DependencyResolver dependencyResolverMock;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ LogbackRuntimeRegistration reg = mock(LogbackRuntimeRegistration.class);
+ doReturn(reg).when(runtimeRegistratorMock).register(
+ any(LogbackRuntimeMXBean.class));
+ }
+
+ @Test
+ public void testUpdate() throws Exception {
+ Multimap<String, String> loggersToAppenders = HashMultimap.create();
+ loggersToAppenders.put("l1", "a1");
+ loggersToAppenders.put("l1", "a2");
+ createContextSetter(loggersToAppenders);
+
+ assertLoggerWithAppenders("l1", "a1", "a2");
+ }
+
+ @Test
+ public void testUpdateTwice() throws Exception {
+ Multimap<String, String> loggersToAppenders = HashMultimap.create();
+ loggersToAppenders.put("l1", "a1");
+ loggersToAppenders.put("l1", "a2");
+ createContextSetter(loggersToAppenders);
+
+ loggersToAppenders.clear();
+ loggersToAppenders.put("l1", "a3");
+ loggersToAppenders.put("l1", "a2");
+ loggersToAppenders.put("l1", "a4");
+ createContextSetter(loggersToAppenders);
+
+ assertLoggerWithAppenders("l1", "a2", "a3", "a4");
+ }
+
+ @Test
+ public void testKeepOtherLoggers() throws Exception {
+ Multimap<String, String> loggersToAppenders = HashMultimap.create();
+ loggersToAppenders.put("l1", "a1");
+ loggersToAppenders.put("l1", "a2");
+ loggersToAppenders.put("l2", "a22");
+ createContextSetter(loggersToAppenders);
+
+ loggersToAppenders.clear();
+ loggersToAppenders.put("l1", "a3");
+ createContextSetter(loggersToAppenders);
+
+ assertLoggerWithAppenders("l1", "a3");
+ assertLoggerWithAppenders("l2", "a22");
+ }
+
+ private void createContextSetter(Multimap<String, String> loggersToAppenders) {
+ ContextSetterImpl setter = new ContextSetterImpl(runtimeRegistratorMock);
+
+ List<LoggerTO> logger = Lists.newArrayList();
+ List<ConsoleAppenderTO> consoleAppenders = Lists.newArrayList();
+
+ for (String loggerName : loggersToAppenders.keySet()) {
+ LoggerTO l1 = createLogger(loggerName,
+ loggersToAppenders.get(loggerName));
+ logger.add(l1);
+ for (String appenderName : loggersToAppenders.get(loggerName)) {
+ consoleAppenders.add(createConsoleAppender(appenderName));
+ }
+
+ }
+
+ LogbackModule logbackModule = createLogbackModule(logger,
+ consoleAppenders);
+ setter.updateContext(logbackModule);
+ }
+
+ private void assertLoggerWithAppenders(String name, String... appenders) {
+ LoggerContext context = (LoggerContext) LoggerFactory
+ .getILoggerFactory();
+ ch.qos.logback.classic.Logger logger = context.getLogger(name);
+ Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
+
+ Multimap<String, Appender<?>> foundAppenders = HashMultimap.create();
+ while (it.hasNext()) {
+ final Appender<ILoggingEvent> app = it.next();
+ foundAppenders.put(app.getName(), app);
+ }
+
+ if (appenders.length == 0) {
+ assertEquals(0, foundAppenders.values().size());
+ }
+
+ for (String appender : appenders) {
+ boolean isPresent = foundAppenders.get(appender).isEmpty();
+ assertFalse(
+ "Appender " + appender + " for logger " + name
+ + " was not present, present appenders: "
+ + foundAppenders.keys(), isPresent);
+ }
+
+ }
+
+ private LogbackModule createLogbackModule(List<LoggerTO> logger,
+ List<ConsoleAppenderTO> consoleAppenders) {
+ LogbackModule logbackModule = new LogbackModule(new ModuleIdentifier(
+ "fact", "first"), dependencyResolverMock);
+ logbackModule.setLoggerTO(logger);
+ logbackModule.setConsoleAppenderTO(consoleAppenders);
+ logbackModule.setRollingFileAppenderTO(Lists
+ .<RollingFileAppenderTO> newArrayList());
+ return logbackModule;
+ }
+
+ private LoggerTO createLogger(String name, Collection<String> appenders) {
+ LoggerTO l1 = new LoggerTO();
+ l1.setAppenders(Lists.newArrayList(appenders));
+ l1.setLoggerName(name);
+ l1.setLevel("INFO");
+ return l1;
+ }
+
+ private ConsoleAppenderTO createConsoleAppender(String name) {
+ ConsoleAppenderTO a = new ConsoleAppenderTO();
+ a.setName(name);
+ a.setEncoderPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n");
+ return a;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectName;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.config.api.ValidationException;
+import org.opendaylight.controller.config.api.jmx.CommitStatus;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+
+public class LogbackModuleTest extends AbstractConfigTest {
+
+ private static final String INSTANCE_NAME = "singleton";
+
+ private LogbackModuleFactory factory;
+
+ @Before
+ public void setUp() throws IOException, ClassNotFoundException,
+ InterruptedException {
+
+ factory = new LogbackModuleFactory();
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ factory));
+ }
+
+ @Test
+ public void testCreateBean() throws InstanceAlreadyExistsException {
+
+ CommitStatus status = createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n", "30MB",
+ 1, 5, "target/%i.log", "rolling", "consoleName", "ALL",
+ "logger1", "DEBUG").commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+ assertStatus(status, 1, 0, 0);
+ }
+
+ @Test
+ public void testReusingInstance() throws InstanceAlreadyExistsException {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n", "30MB",
+ 1, 5, "target/%i.log", "rolling", "consoleName", "ALL",
+ "logger1", "DEBUG").commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ CommitStatus status = transaction.commit();
+
+ assertBeanCount(1, factory.getImplementationName());
+ assertStatus(status, 0, 0, 1);
+ }
+
+ @Test
+ public void testRecreateInstance() throws InstanceAlreadyExistsException,
+ ValidationException, ConflictingVersionException,
+ InstanceNotFoundException {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n", "30MB",
+ 1, 5, "target/%i.log", "rolling", "consoleName", "ALL",
+ "logger1", "DEBUG").commit();
+
+ assertBeanCount(1, LogbackModuleFactory.NAME);
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+
+ ObjectName logback = transaction.lookupConfigBean(
+ LogbackModuleFactory.NAME, "singleton");
+ LogbackModuleMXBean nwBean = transaction.newMXBeanProxy(logback,
+ LogbackModuleMXBean.class);
+ CommitStatus status = transaction.commit();
+ assertBeanCount(1, LogbackModuleFactory.NAME);
+
+ assertStatus(status, 0, 0, 1);
+ }
+
+ @Test
+ public void testDestroyInstance() throws InstanceNotFoundException,
+ InstanceAlreadyExistsException {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n", "30MB",
+ 1, 5, "target/%i.log", "rolling", "consoleName", "ALL",
+ "logger1", "DEBUG").commit();
+ assertBeanCount(1, factory.getImplementationName());
+
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ transaction.destroyConfigBean(factory.getImplementationName(),
+ INSTANCE_NAME);
+ CommitStatus status = transaction.commit();
+
+ assertBeanCount(0, factory.getImplementationName());
+ assertStatus(status, 0, 0, 0);
+ }
+
+ @Ignore
+ @Test
+ public void testValidation1() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", 1, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", "logger1", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("FileName is null"));
+ }
+ }
+
+ @Test
+ public void testValidation2() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp", null, "30MB", 1, 5, "target/%i.log",
+ "rolling", "consoleName", "ALL", "logger1", "DEBUG")
+ .commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("EncoderPattern is null"));
+ }
+ }
+
+ @Test
+ public void testValidation4() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ null, 1, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", "logger1", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("MaxFileSize is null"));
+ }
+ }
+
+ @Test
+ public void testValidation6() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "", "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", 1, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", "logger1", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("FileName needs to be set"));
+ }
+ }
+
+ @Test
+ public void testValidation7() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+
+ true, "target/rollingApp", "", "30MB", 1, 5, "target/%i.log",
+ "rolling", "consoleName", "ALL", "logger1", "DEBUG")
+ .commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("EncoderPattern needs to be set"));
+ }
+ }
+
+ @Test
+ public void testValidation8() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", 1, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", null, "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("LoggerName is null"));
+ }
+ }
+
+ @Test
+ public void testValidation9() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", 1, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", "", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("LoggerName needs to be set"));
+ }
+ }
+
+ @Test
+ public void testValidation10() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", null, 5, "target/%i.log", "rolling", "consoleName",
+ "ALL", "logger1", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("MinIndex is null"));
+ }
+ }
+
+ @Test
+ public void testValidation11() throws InstanceAlreadyExistsException {
+ try {
+ createBeans(
+ true, "target/rollingApp",
+ "%-4relative [%thread] %-5level %logger{35} - %msg%n",
+ "30MB", 1, null, "target/%i.log", "rolling", "consoleName",
+ "ALL", "logger1", "DEBUG").commit();
+ fail();
+ } catch (ValidationException e) {
+ assertThat(e.getFailedValidations().toString(),
+ containsString("MaxIndex is null"));
+ }
+ }
+
+ private ConfigTransactionJMXClient createBeans(
+ Boolean isAppend, String rollingFileName, String encoderPattern,
+ String maxFileSize, Integer minIndex, Integer maxIndex,
+ String fileNamePattern, String rollingName, String consoleName,
+ String thresholdFilter, String loggerName, String level )
+ throws InstanceAlreadyExistsException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName nameCreated = transaction.createModule(
+ factory.getImplementationName(), INSTANCE_NAME);
+ LogbackModuleMXBean bean = transaction.newMXBeanProxy(nameCreated,
+ LogbackModuleMXBean.class);
+
+ List<RollingFileAppenderTO> rollingAppenders = new ArrayList<>();
+ RollingFileAppenderTO rollingAppender = new RollingFileAppenderTO();
+ rollingAppender.setAppend(isAppend);
+ rollingAppender.setEncoderPattern(encoderPattern);
+ rollingAppender.setFileName(rollingFileName);
+ rollingAppender.setMaxFileSize(maxFileSize);
+ rollingAppender.setMaxIndex(maxIndex);
+ rollingAppender.setMinIndex(minIndex);
+ rollingAppender.setFileNamePattern(fileNamePattern);
+ rollingAppender.setName(rollingName);
+ rollingAppenders.add(rollingAppender);
+
+ List<ConsoleAppenderTO> consoleAppenders = new ArrayList<>();
+ ConsoleAppenderTO consoleAppender = new ConsoleAppenderTO();
+ consoleAppender.setEncoderPattern(encoderPattern);
+ consoleAppender.setName(consoleName);
+ consoleAppender.setThresholdFilter(thresholdFilter);
+ consoleAppenders.add(consoleAppender);
+
+ List<LoggerTO> loggers = new ArrayList<>();
+
+ LoggerTO logger = new LoggerTO();
+
+ logger.setAppenders(Arrays.<String> asList());
+
+ logger.setLevel(level);
+ logger.setLoggerName(loggerName);
+ loggers.add(logger);
+ bean.setLoggerTO(loggers);
+ bean.setRollingFileAppenderTO(rollingAppenders);
+ bean.setConsoleAppenderTO(consoleAppenders);
+
+ transaction.validateConfig();
+
+ return transaction;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.JMX;
+import javax.management.ObjectName;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionClient;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
+import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
+
+import com.google.common.collect.Lists;
+
+public class LogbackModuleWithInitialConfigurationTest extends
+ AbstractConfigTest {
+
+ private LogbackModuleFactory factory;
+
+ @Before
+ public void setUp() throws IOException, ClassNotFoundException {
+
+ factory = new LogbackModuleFactory();
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ factory));
+ }
+
+ /**
+ * Tests that initial configuration was changed. Changed attributes:
+ * location, fileName, duplicateInsertTries
+ *
+ */
+ @Test
+ public void test() throws Exception {
+
+ createBeans();
+
+ ConfigTransactionClient transaction = configRegistryClient
+ .createTransaction();
+
+ LogbackModuleMXBean bean = JMX.newMXBeanProxy(
+ ManagementFactory.getPlatformMBeanServer(),
+ transaction.lookupConfigBean("logback", "singleton"),
+ LogbackModuleMXBean.class);
+ assertEquals(1, bean.getConsoleAppenderTO().size());
+ assertEquals(1, bean.getRollingFileAppenderTO().size());
+ assertEquals(1, bean.getLoggerTO().size());
+
+ RollingFileAppenderTO rolling = new RollingFileAppenderTO();
+ RollingFileAppenderTO old = bean
+ .getRollingFileAppenderTO().get(0);
+ rolling.setAppend(old.getAppend());
+ rolling.setEncoderPattern(old.getEncoderPattern());
+ rolling.setFileName("target/logFile1.log");
+ rolling.setFileNamePattern("target/%i.log");
+ rolling.setMaxFileSize(old.getMaxFileSize());
+ rolling.setMinIndex(old.getMinIndex());
+ rolling.setMaxIndex(old.getMaxIndex());
+ rolling.setName("FILE");
+
+ ConsoleAppenderTO console = new ConsoleAppenderTO();
+ console.setEncoderPattern("%date %level [%thread] %logger{10} %msg%n");
+ console.setName("SYSTEM");
+ console.setThresholdFilter("DEBUG");
+
+ bean.setConsoleAppenderTO(Lists.newArrayList(console));
+ bean.setRollingFileAppenderTO(Lists.newArrayList(rolling));
+
+ LoggerTO logger = new LoggerTO();
+ logger.setLevel("INFO");
+ logger.setLoggerName("logger");
+ logger.setAppenders(Lists.newArrayList("SYSTEM"));
+ List<LoggerTO> loggers = Lists
+ .newArrayList(logger);
+ bean.setLoggerTO(loggers);
+
+ transaction.commit();
+
+ LogbackModuleMXBean logback = configRegistryClient.newMXBeanProxy(
+ ObjectNameUtil.createReadOnlyModuleON("logback", "singleton"),
+ LogbackModuleMXBean.class);
+
+
+ List<RollingFileAppenderTO> rollingList = logback
+ .getRollingFileAppenderTO();
+ assertEquals(1, rollingList.size());
+
+ RollingFileAppenderTO rollingApp = rollingList
+ .get(0);
+ assertEquals(rollingApp.getFileName(), "target/logFile1.log");
+ assertEquals(rollingApp.getName(), "FILE");
+
+ List<ConsoleAppenderTO> consoleList = logback
+ .getConsoleAppenderTO();
+ assertEquals(1, consoleList.size());
+
+ ConsoleAppenderTO consoleApp = consoleList
+ .get(0);
+ assertEquals(consoleApp.getThresholdFilter(), "DEBUG");
+ assertEquals(consoleApp.getName(), "SYSTEM");
+
+ loggers = logback.getLoggerTO();
+ assertEquals(1, loggers.size());
+
+ }
+
+ public ObjectName createBeans() throws JoranException,
+ InstanceAlreadyExistsException, IOException {
+
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(lc);
+ configurator
+ .doConfigure("src/test/resources/simple_config_logback.xml");
+ File f = new File("target/it");
+ if (f.exists())
+ FileUtils.cleanDirectory(f);
+ ch.qos.logback.classic.Logger logger = lc.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
+ ch.qos.logback.core.rolling.RollingFileAppender<ILoggingEvent> fileAppender = (ch.qos.logback.core.rolling.RollingFileAppender<ILoggingEvent>) logger
+ .getAppender("VARLOGFILE");
+ fileAppender.start();
+
+ ch.qos.logback.core.ConsoleAppender<ILoggingEvent> consoleAppender = (ch.qos.logback.core.ConsoleAppender<ILoggingEvent>) logger
+ .getAppender("STDOUT");
+ consoleAppender.start();
+ List<RollingFileAppenderTO> rollingAppenders = new ArrayList<>();
+ RollingFileAppenderTO rollingApp = new RollingFileAppenderTO();
+ rollingApp.setAppend(fileAppender.isAppend());
+ PatternLayoutEncoder enc = (PatternLayoutEncoder) fileAppender
+ .getEncoder();
+ rollingApp.setEncoderPattern(enc.getPattern());
+ rollingApp.setFileName(fileAppender.getFile());
+ FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) fileAppender
+ .getRollingPolicy();
+ rollingApp.setMaxIndex(rollingPolicy.getMaxIndex());
+ rollingApp.setMinIndex(rollingPolicy.getMinIndex());
+ SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = (SizeBasedTriggeringPolicy<ILoggingEvent>) fileAppender
+ .getTriggeringPolicy();
+ rollingApp.setMaxFileSize(triggeringPolicy.getMaxFileSize());
+ rollingApp.setName(fileAppender.getName());
+ rollingApp.setFileNamePattern(rollingPolicy.getFileNamePattern());
+ rollingAppenders.add(rollingApp);
+
+ assertEquals(rollingApp.getFileName(), "target/osgi.log");
+ assertEquals(rollingApp.getMaxFileSize(), "50MB");
+ assertEquals(rollingApp.getName(), "VARLOGFILE");
+
+ List<ConsoleAppenderTO> consoleAppenders = new ArrayList<>();
+ ConsoleAppenderTO consoleApp = new ConsoleAppenderTO();
+ enc = (PatternLayoutEncoder) consoleAppender.getEncoder();
+ consoleApp.setEncoderPattern(enc.getPattern());
+ consoleApp.setName(consoleAppender.getName());
+ consoleApp.setThresholdFilter("ALL");
+ consoleAppenders.add(consoleApp);
+
+ List<LoggerTO> loggersDTOs = new ArrayList<>();
+ LoggerTO log = new LoggerTO();
+ log.setAppenders(Arrays.asList(fileAppender.getName(),
+ consoleApp.getName()));
+
+ log.setLevel(logger.getLevel().toString());
+ log.setLoggerName(logger.getName());
+ loggersDTOs.add(log);
+
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName nameCreated = transaction.createModule(
+ factory.getImplementationName(), "singleton");
+ LogbackModuleMXBean bean = transaction.newMXBeanProxy(nameCreated,
+ LogbackModuleMXBean.class);
+
+ bean.setLoggerTO(loggersDTOs);
+ bean.setRollingFileAppenderTO(rollingAppenders);
+ bean.setConsoleAppenderTO(consoleAppenders);
+
+ transaction.commit();
+
+ return nameCreated;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yang.logback.config;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMX;
+import javax.management.ObjectName;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.config.manager.impl.AbstractConfigTest;
+import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
+import org.opendaylight.controller.config.util.ConfigTransactionJMXClient;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+
+public class LogbackWithXmlConfigModuleTest extends AbstractConfigTest {
+
+ private LogbackModuleFactory factory;
+ private LoggerContext lc;
+
+ @Before
+ public void setUp() throws JoranException, IOException {
+
+ factory = new LogbackModuleFactory();
+ super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(
+ factory));
+
+ lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ JoranConfigurator configurator = new JoranConfigurator();
+ lc.reset();
+ configurator.setContext(lc);
+ configurator
+ .doConfigure("src/test/resources/simple_config_logback.xml");
+ File f = new File("target/it");
+ if (f.exists())
+ FileUtils.cleanDirectory(f);
+ }
+
+ /**
+ * Tests configuration of Logger factory.
+ */
+ @Test
+ public void test() throws InstanceAlreadyExistsException,
+ InstanceNotFoundException {
+
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ ObjectName nameCreated = transaction.createModule(
+ factory.getImplementationName(), "singleton");
+
+ LogbackModuleMXBean bean = transaction.newMXBeanProxy(nameCreated,
+ LogbackModuleMXBean.class);
+
+ assertEquals(1, bean.getConsoleAppenderTO().size());
+
+ assertEquals(1, bean.getRollingFileAppenderTO().size());
+
+ transaction.commit();
+
+ transaction = configRegistryClient.createTransaction();
+
+ nameCreated = transaction.lookupConfigBean(
+ factory.getImplementationName(), "singleton");
+
+ bean = JMX.newMXBeanProxy(platformMBeanServer, nameCreated,
+ LogbackModuleMXBean.class);
+
+ assertEquals(1, bean.getConsoleAppenderTO().size());
+ assertEquals(1, bean.getRollingFileAppenderTO().size());
+
+ }
+
+ /**
+ * Tests filtering loggers. Loggers inherited from ROOT logger and duplicate
+ * loggers should be removed.
+ */
+ @Test
+ public void testAllLoggers() throws InstanceAlreadyExistsException,
+ InstanceNotFoundException {
+ ConfigTransactionJMXClient transaction = configRegistryClient
+ .createTransaction();
+ transaction.createModule(factory.getImplementationName(), "singleton");
+
+ transaction.commit();
+
+ transaction = configRegistryClient.createTransaction();
+
+ LogbackModuleMXBean bean = JMX.newMXBeanProxy(
+ ManagementFactory.getPlatformMBeanServer(),
+ transaction.lookupConfigBean("logback", "singleton"),
+ LogbackModuleMXBean.class);
+
+ assertEquals(5, bean.getLoggerTO().size());
+ }
+
+}
--- /dev/null
+<configuration>
+
+ <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
+ <resetJUL>true</resetJUL>
+ </contextListener>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>INFO</level>
+ </filter>
+ <encoder>
+ <pattern>[%d{HH:mm:ss.SSS}] [%thread] %-5level %logger - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <!-- rolling file /var/log/osgi/osgi.log that keeps last 3 backups of 5MB -->
+ <appender name="VARLOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>target/osgi.log</file>
+ <append>true</append>
+ <encoder>
+ <pattern>[%d{HH:mm:ss.SSS}] [%thread] %-5level %logger - %msg%n</pattern>
+ </encoder>
+ <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+ <fileNamePattern>/opt/Demo1/logs/osgi.log.%i.gz</fileNamePattern>
+ <minIndex>1</minIndex>
+ <maxIndex>5</maxIndex>
+ </rollingPolicy>
+
+ <triggeringPolicy
+ class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+ <maxFileSize>50MB</maxFileSize>
+ </triggeringPolicy>
+
+ </appender>
+
+ <appender name="BGPDUMPFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>target/bgp.log</file>
+ <append>false</append>
+ <encoder>
+ <pattern>[%d{HH:mm:ss.SSS}] [%thread] %-5level %logger - %msg%n</pattern>
+ </encoder>
+ <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+ <fileNamePattern>/opt/Demo1/logs/bgp.log.%i.gz</fileNamePattern>
+ <minIndex>1</minIndex>
+ <maxIndex>5</maxIndex>
+ </rollingPolicy>
+
+ <triggeringPolicy
+ class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+ <maxFileSize>5MB</maxFileSize>
+ </triggeringPolicy>
+
+ </appender>
+
+
+ <!-- Increase to INFO to see content of REST messages -->
+ <logger name="org.apache.cxf.interceptor.LoggingInInterceptor"
+ level="WARN"/>
+ <logger name="org.apache.cxf.interceptor.LoggingOutInterceptor"
+ level="WARN"/>
+ <!-- Increase to INFO to see content of SOAP messages -->
+ <logger name="org.apache.cxf.services"
+ level="WARN"/>
+ <logger name="org.opendaylight"
+ level="DEBUG"/>
+ <logger name="com"
+ level="INFO"/>
+ <root level="INFO">
+ <!--
+ <appender-ref ref="ALL_APPENDER"/>
+ -->
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="VARLOGFILE"/>
+ </root>
+
+</configuration>
<module>yang-store-api</module>
<module>yang-store-impl</module>
<module>yang-test</module>
+ <module>logback-config</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>