Skip to content

Instantly share code, notes, and snippets.

@jamezp
Last active October 2, 2018 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamezp/68cd45982ba2b75aa2e95465695ec061 to your computer and use it in GitHub Desktop.
Save jamezp/68cd45982ba2b75aa2e95465695ec061 to your computer and use it in GitHub Desktop.
Example Log Configuration Change Updater
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.jboss.logmanager;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.jboss.logging.Logger;
/**
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
public class Example {
public static void main(String[] args) throws Exception {
final Path loggingConfig = Paths.get(System.getProperty("user.home"), "/tmp/logging.properties");
System.setProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager");
System.setProperty("logging.configuration", "file://" + loggingConfig.toString());
System.setProperty("log.dir", "/home/jperkins/tmp/logs");
final Logger logger = Logger.getLogger(Example.class);
final ExecutorService service = Executors.newCachedThreadPool();
final LoggingConfigurationChangeListener listener = new LoggingConfigurationChangeListener(loggingConfig);
try {
service.submit(listener);
service.submit(new Runnable() {
@Override
public void run() {
for (; ; ) {
logger.trace("Test trace");
logger.debug("Test debug");
logger.info("Test info");
logger.warn("Test warn");
logger.error("Test error");
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
} finally {
service.shutdown();
}
}
}
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.jboss.logmanager;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jboss.logmanager.config.LogContextConfiguration;
/**
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
public class LoggingConfigurationChangeListener implements Runnable, Closeable {
private static final Logger.AttachmentKey<LoggingConfigurationUpdater> KEY = new Logger.AttachmentKey<>();
private static final Logger LOGGER = Logger.getLogger(LoggingConfigurationChangeListener.class.getName());
private final WatchService watcher;
private final Path configFile;
private final Lock lock;
@SuppressWarnings("WeakerAccess")
public LoggingConfigurationChangeListener(final Path configFile) throws IOException {
this.configFile = configFile;
lock = new ReentrantLock();
watcher = FileSystems.getDefault().newWatchService();
configFile.getParent().register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
}
@Override
public void run() {
try {
for (; ; ) {
// Wait for signal
final WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
x.printStackTrace();
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == OVERFLOW) {
continue;
}
// Context for directory entry event is the file name of entry
@SuppressWarnings("unchecked")
final WatchEvent<Path> ev = (WatchEvent<Path>) event;
final Path file = ev.context();
if (event.kind() == ENTRY_MODIFY && file.getFileName().equals(configFile.getFileName())) {
final LoggingConfigurationUpdater updater = getOrCreateUpdater();
if (updater == null) {
System.err.printf("Could not listen for configuration changes on %s. The LogContextConfiguration could not be found.%n", configFile);
break;
}
try (InputStream in = Files.newInputStream(configFile)) {
lock.lock();
updater.configure(in);
} catch (Exception e) {
System.err.println("Failed to process changes to the logging configuration file.");
e.printStackTrace(System.err);
} finally {
lock.unlock();
}
}
}
// Reset the key and remove from the registry if it's no longer available
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (ClosedWatchServiceException e) {
LOGGER.log(Level.DEBUG, "Configuration change listener has been closed.");
}
}
@Override
public void close() throws IOException {
watcher.close();
}
private LoggingConfigurationUpdater getOrCreateUpdater() {
final LogContext logContext = LogContext.getLogContext();
final Logger rootLogger = logContext.getLogger("");
LoggingConfigurationUpdater updater = rootLogger.getAttachment(KEY);
if (updater == null) {
final LogContextConfiguration logContextConfiguration = getOrCreateConfiguration(rootLogger);
if (logContextConfiguration == null) {
return null;
}
updater = new LoggingConfigurationUpdater(logContextConfiguration);
final LoggingConfigurationUpdater appearing = rootLogger.attachIfAbsent(KEY, updater);
if (appearing != null) {
updater = appearing;
}
}
return updater;
}
private LogContextConfiguration getOrCreateConfiguration(final Logger rootLogger) {
Configurator configurator = rootLogger.getAttachment(Configurator.ATTACHMENT_KEY);
if (configurator == null) {
configurator = new PropertyConfigurator(rootLogger.getLogContext());
final Configurator appearing = rootLogger.attachIfAbsent(Configurator.ATTACHMENT_KEY, configurator);
if (appearing != null) {
configurator = appearing;
}
}
if (configurator instanceof PropertyConfigurator) {
return ((PropertyConfigurator) configurator).getLogContextConfiguration();
}
return null;
}
}
/*
* JBoss, Home of Professional Open Source.
*
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 org.jboss.logmanager;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.regex.Pattern;
import org.jboss.logmanager.config.ErrorManagerConfiguration;
import org.jboss.logmanager.config.FilterConfiguration;
import org.jboss.logmanager.config.FormatterConfiguration;
import org.jboss.logmanager.config.HandlerConfiguration;
import org.jboss.logmanager.config.HandlerContainingConfigurable;
import org.jboss.logmanager.config.LogContextConfiguration;
import org.jboss.logmanager.config.LoggerConfiguration;
import org.jboss.logmanager.config.PojoConfiguration;
import org.jboss.logmanager.config.PropertyConfigurable;
import org.jboss.logmanager.config.ValueExpression;
/**
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
@SuppressWarnings("WeakerAccess")
public class LoggingConfigurationUpdater {
private static final String[] EMPTY_STRINGS = new String[0];
private static final Pattern EXPRESSION_PATTERN = Pattern.compile(".*\\$\\{.*\\}.*");
private final LogContextConfiguration config;
public LoggingConfigurationUpdater(final LogContextConfiguration config) {
this.config = config;
}
/**
* {@inheritDoc}
*/
public void configure(final InputStream inputStream) throws IOException {
final Properties properties = new Properties();
try {
properties.load(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
inputStream.close();
} finally {
safeClose(inputStream);
}
configure(properties);
}
/**
* Configure the log manager from the given properties.
* <p/>
* The following values read in from a configuration will be trimmed of prefixed and trailing whitespace:
* <pre>
* <ul>
* <li>logger.NAME.filter</li>
* <li>logger.NAME.level</li>
* <li>logger.NAME.useParentHandlers</li>
* <li>handler.NAME.filter</li>
* <li>handler.NAME.formatter</li>
* <li>handler.NAME.level</li>
* <li>handler.NAME.encoding</li>
* <li>handler.NAME.errorManager</li>
* </ul>
* </pre>
*
* @param properties the properties
*/
private void configure(final Properties properties) {
try {
final Collection<String> handlersToRemove = config.getHandlerNames();
// Start with the list of loggers to configure. The root logger is always on the list.
handlersToRemove.removeAll(configureLogger(properties, ""));
// And, for each logger name, configure any filters, handlers, etc.
final String[] loggerNames = getStringCsvArray(properties, "loggers");
for (String loggerName : loggerNames) {
handlersToRemove.removeAll(configureLogger(properties, loggerName));
}
// Remove any loggers that are not longer required
final Collection<String> loggersToRemove = config.getLoggerNames();
loggersToRemove.remove("");
loggersToRemove.removeAll(Arrays.asList(loggerNames));
for (String loggerName : loggersToRemove) {
config.removeLoggerConfiguration(loggerName);
}
// Configure any declared handlers.
final String[] handlerNames = getStringCsvArray(properties, "handlers");
for (String handlerName : handlerNames) {
configureHandler(properties, handlerName);
}
// Remove any handlers that are not longer required
handlersToRemove.removeAll(Arrays.asList(handlerNames));
for (String handlerName : handlersToRemove) {
config.removeHandlerConfiguration(handlerName);
}
// Configure any declared filters.
for (String filterName : getStringCsvArray(properties, "filters")) {
configureFilter(properties, filterName);
}
// Configure any declared formatters.
for (String formatterName : getStringCsvArray(properties, "formatters")) {
configureFormatter(properties, formatterName);
}
// Configure any declared error managers.
for (String errorManagerName : getStringCsvArray(properties, "errorManagers")) {
configureErrorManager(properties, errorManagerName);
}
// Configure POJOs
for (String pojoName : getStringCsvArray(properties, "pojos")) {
configurePojos(properties, pojoName);
}
config.commit();
} finally {
config.forget();
}
}
private List<String> configureLogger(final Properties properties, final String loggerName) {
final LoggerConfiguration loggerConfiguration;
if (config.getLoggerNames().contains(loggerName)) {
loggerConfiguration = config.getLoggerConfiguration(loggerName);
} else {
loggerConfiguration = config.addLoggerConfiguration(loggerName);
}
// Get logger level
final String levelName = getStringProperty(properties, getKey("logger", loggerName, "level"));
if (notEqual(levelName, loggerConfiguration.getLevelValueExpression())) {
loggerConfiguration.setLevel(levelName == null ? "ALL" : levelName);
}
// Get logger filter
final String filterName = getStringProperty(properties, getKey("logger", loggerName, "filter"));
final ValueExpression<String> newValue = ValueExpression.STRING_RESOLVER.resolve(filterName);
if (notEqual(newValue, loggerConfiguration.getFilterValueExpression())) {
loggerConfiguration.setFilter(filterName);
final String resolvedFilter = loggerConfiguration.getFilterValueExpression().getResolvedValue();
if (resolvedFilter != null) {
// Check for a filter class
final String filterClassName = getStringProperty(properties, getKey("filter", resolvedFilter));
// If the filter class is null, assume it's a filter expression
if (filterClassName != null) {
configureFilter(properties, resolvedFilter);
}
}
}
// Get logger handlers
configureHandlerNames(properties, loggerConfiguration, "logger", loggerName);
// Get logger properties
final String useParentHandlersString = getStringProperty(properties, getKey("logger", loggerName, "useParentHandlers"));
if (booleanNotEqual(useParentHandlersString, loggerConfiguration.getUseParentHandlersValueExpression())) {
// Check for expression
if (EXPRESSION_PATTERN.matcher(useParentHandlersString).matches()) {
loggerConfiguration.setUseParentHandlers(useParentHandlersString);
} else {
loggerConfiguration.setUseParentHandlers(Boolean.parseBoolean(useParentHandlersString));
}
}
return loggerConfiguration.getHandlerNames();
}
private void configureFilter(final Properties properties, final String filterName) {
final String className = getStringProperty(properties, getKey("filter", filterName));
if (className == null) {
// Assume we're using a filter expression
return;
}
final FilterConfiguration configuration;
if (config.getFilterNames().contains(filterName)) {
configuration = config.getFilterConfiguration(filterName);
} else {
configuration = config.addFilterConfiguration(
getStringProperty(properties, getKey("filter", filterName, "module")),
className,
filterName,
getStringCsvArray(properties, getKey("filter", filterName, "constructorProperties")));
}
final String[] postConfigurationMethods = getStringCsvArray(properties, getKey("filter", filterName, "postConfiguration"));
configuration.setPostConfigurationMethods(postConfigurationMethods);
configureProperties(properties, configuration, getKey("filter", filterName));
}
private boolean configureFormatter(final Properties properties, final String formatterName) {
final String className = getStringProperty(properties, getKey("formatter", formatterName));
if (className == null) {
printError("Formatter %s is not defined%n", formatterName);
return false;
}
final FormatterConfiguration configuration;
if (config.getFormatterNames().contains(formatterName)) {
configuration = config.getFormatterConfiguration(formatterName);
} else {
configuration = config.addFormatterConfiguration(
getStringProperty(properties, getKey("formatter", formatterName, "module")),
className,
formatterName,
getStringCsvArray(properties, getKey("formatter", formatterName, "constructorProperties")));
}
final String[] postConfigurationMethods = getStringCsvArray(properties, getKey("formatter", formatterName, "postConfiguration"));
configuration.setPostConfigurationMethods(postConfigurationMethods);
configureProperties(properties, configuration, getKey("formatter", formatterName));
return true;
}
private boolean configureErrorManager(final Properties properties, final String errorManagerName) {
final String className = getStringProperty(properties, getKey("errorManager", errorManagerName));
if (className == null) {
printError("Error manager %s is not defined%n", errorManagerName);
return false;
}
final ErrorManagerConfiguration configuration;
if (config.getErrorManagerNames().contains(errorManagerName)) {
configuration = config.getErrorManagerConfiguration(errorManagerName);
} else {
configuration = config.addErrorManagerConfiguration(
getStringProperty(properties, getKey("errorManager", errorManagerName, "module")),
className,
errorManagerName,
getStringCsvArray(properties, getKey("errorManager", errorManagerName, "constructorProperties")));
}
final String[] postConfigurationMethods = getStringCsvArray(properties, getKey("errorManager", errorManagerName, "postConfiguration"));
configuration.setPostConfigurationMethods(postConfigurationMethods);
configureProperties(properties, configuration, getKey("errorManager", errorManagerName));
return true;
}
private boolean configureHandler(final Properties properties, final String handlerName) {
final String className = getStringProperty(properties, getKey("handler", handlerName));
if (className == null) {
printError("Handler %s is not defined%n", handlerName);
return false;
}
final HandlerConfiguration configuration;
if (config.getHandlerNames().contains(handlerName)) {
configuration = config.getHandlerConfiguration(handlerName);
} else {
configuration = config.addHandlerConfiguration(
getStringProperty(properties, getKey("handler", handlerName, "module")),
className,
handlerName,
getStringCsvArray(properties, getKey("handler", handlerName, "constructorProperties")));
}
final String filter = getStringProperty(properties, getKey("handler", handlerName, "filter"));
if (notEqual(filter, configuration.getFilterValueExpression())) {
configuration.setFilter(filter);
final String resolvedFilter = configuration.getFilterValueExpression().getResolvedValue();
if (resolvedFilter != null) {
// Check for a filter class
final String filterClassName = getStringProperty(properties, getKey("filter", resolvedFilter));
// If the filter class is null, assume it's a filter expression
if (filterClassName != null) {
configureFilter(properties, resolvedFilter);
}
}
}
final String levelName = getStringProperty(properties, getKey("handler", handlerName, "level"));
if (notEqual(levelName, configuration.getLevelValueExpression())) {
configuration.setLevel(levelName == null ? "ALL" : levelName);
}
final String formatterName = getStringProperty(properties, getKey("handler", handlerName, "formatter"));
if (formatterName != null) {
if (getStringProperty(properties, getKey("formatter", ValueExpression.STRING_RESOLVER.resolve(formatterName).getResolvedValue())) == null) {
printError("Formatter %s is not defined%n", formatterName);
} else {
final ValueExpression<String> newValue = ValueExpression.STRING_RESOLVER.resolve(formatterName);
final boolean changed = configureFormatter(properties, newValue.getResolvedValue());
if (notEqual(newValue, configuration.getFormatterNameValueExpression())) {
if (changed) {
configuration.setFormatterName(formatterName);
}
}
}
}
final String encoding = getStringProperty(properties, getKey("handler", handlerName, "encoding"));
if (notEqual(encoding, configuration.getEncodingValueExpression())) {
configuration.setEncoding(encoding);
}
final String errorManagerName = getStringProperty(properties, getKey("handler", handlerName, "errorManager"));
if (errorManagerName != null) {
if (getStringProperty(properties, getKey("errorManager", ValueExpression.STRING_RESOLVER.resolve(errorManagerName).getResolvedValue())) == null) {
printError("Error manager %s is not defined%n", errorManagerName);
} else {
final ValueExpression<String> newValue = ValueExpression.STRING_RESOLVER.resolve(errorManagerName);
final boolean changed = configureFormatter(properties, newValue.getResolvedValue());
if (notEqual(newValue, configuration.getErrorManagerNameValueExpression())) {
if (changed) {
configuration.setErrorManagerName(errorManagerName);
}
}
}
}
configureHandlerNames(properties, configuration, "handler", handlerName);
final String[] postConfigurationMethods = getStringCsvArray(properties, getKey("handler", handlerName, "postConfiguration"));
configuration.setPostConfigurationMethods(postConfigurationMethods);
configureProperties(properties, configuration, getKey("handler", handlerName));
return true;
}
private void configurePojos(final Properties properties, final String pojoName) {
final String className = getStringProperty(properties, getKey("pojo", pojoName));
if (className == null) {
printError("POJO %s is not defined%n", pojoName);
return;
}
final PojoConfiguration configuration;
if (config.getPojoNames().contains(pojoName)) {
configuration = config.getPojoConfiguration(pojoName);
} else {
configuration = config.addPojoConfiguration(
getStringProperty(properties, getKey("pojo", pojoName, "module")),
getStringProperty(properties, getKey("pojo", pojoName)),
pojoName,
getStringCsvArray(properties, getKey("pojo", pojoName, "constructorProperties")));
}
final String[] postConfigurationMethods = getStringCsvArray(properties, getKey("pojo", pojoName, "postConfiguration"));
configuration.setPostConfigurationMethods(postConfigurationMethods);
configureProperties(properties, configuration, getKey("pojo", pojoName));
}
private void configureProperties(final Properties properties, final PropertyConfigurable configurable, final String prefix) {
final List<String> propertyNames = getStringCsvList(properties, getKey(prefix, "properties"));
for (String propertyName : propertyNames) {
final String valueString = getStringProperty(properties, getKey(prefix, propertyName), false);
if (notEqual(valueString, configurable.getPropertyValueExpression(propertyName))) {
configurable.setPropertyValueString(propertyName, valueString);
}
}
}
private void configureHandlerNames(final Properties properties, final HandlerContainingConfigurable configuration,
final String prefix, final String name) {
final String[] handlerNames = getStringCsvArray(properties, getKey(prefix, name, "handlers"));
final Collection<String> availableHandlers = new ArrayList<>();
for (String handlerName : handlerNames) {
if (configureHandler(properties, handlerName)) {
availableHandlers.add(handlerName);
}
}
configuration.setHandlerNames(availableHandlers);
}
private static String getKey(final String prefix, final String objectName) {
return objectName.length() > 0 ? prefix + "." + objectName : prefix;
}
private static String getKey(final String prefix, final String objectName, final String key) {
return objectName.length() > 0 ? prefix + "." + objectName + "." + key : prefix + "." + key;
}
private static String getStringProperty(final Properties properties, final String key) {
return getStringProperty(properties, key, true);
}
private static String getStringProperty(final Properties properties, final String key, final boolean trim) {
final String value = properties.getProperty(key);
return (trim ? (value == null ? null : value.trim()) : value);
}
private static String[] getStringCsvArray(final Properties properties, final String key) {
final String property = properties.getProperty(key, "");
if (property == null) {
return EMPTY_STRINGS;
}
final String value = property.trim();
if (value.length() == 0) {
return EMPTY_STRINGS;
}
return value.split("\\s*,\\s*");
}
private static List<String> getStringCsvList(final Properties properties, final String key) {
return new ArrayList<>(Arrays.asList(getStringCsvArray(properties, key)));
}
private static void printError(final String format, final Object... args) {
System.err.printf(format, args);
}
private static void safeClose(final Closeable stream) {
if (stream != null) try {
stream.close();
} catch (Exception e) {
// can't do anything about it
}
}
private static boolean notEqual(final ValueExpression<String> newValue, final ValueExpression<String> currentValue) {
if (newValue == null) {
return currentValue.getResolvedValue() != null;
}
return !Objects.equals(newValue.getValue(), currentValue.getValue());
}
private static boolean notEqual(final String newValue, final ValueExpression<String> currentValue) {
if (newValue == null) {
return currentValue.getResolvedValue() != null;
}
if (currentValue.isExpression()) {
final String resolvedCurrentValue = currentValue.getResolvedValue();
final String resolvedNewValue = ValueExpression.STRING_RESOLVER.resolve(newValue).getResolvedValue();
return resolvedCurrentValue == null ? resolvedNewValue != null : !resolvedCurrentValue.equals(resolvedNewValue);
}
return !newValue.equals(currentValue.getValue());
}
private static boolean booleanNotEqual(final String newValue, final ValueExpression<Boolean> currentValue) {
if (newValue == null) {
return currentValue.getResolvedValue() != null;
}
if (currentValue.isExpression()) {
final Boolean resolvedCurrentValue = currentValue.getResolvedValue();
final Boolean resolvedNewValue = ValueExpression.BOOLEAN_RESOLVER.resolve(newValue).getResolvedValue();
return resolvedCurrentValue == null ? resolvedNewValue != null : !resolvedCurrentValue.equals(resolvedNewValue);
}
return !newValue.equals(currentValue.getValue());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment