diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java index 5d486c7649d..99ee65dbdad 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java @@ -41,6 +41,11 @@ import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.config.NullConfiguration; import org.apache.logging.log4j.core.config.Reconfigurable; +import org.apache.logging.log4j.core.event.Event; +import org.apache.logging.log4j.core.event.Event.EventType; +import org.apache.logging.log4j.core.event.EventFilter; +import org.apache.logging.log4j.core.event.EventListener; +import org.apache.logging.log4j.core.event.EventService; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.jmx.Server; import org.apache.logging.log4j.core.util.Cancellable; @@ -73,7 +78,8 @@ public class LoggerContext extends AbstractLifeCycle AutoCloseable, Terminable, ConfigurationListener, - LoggerContextShutdownEnabled { + LoggerContextShutdownEnabled, + EventListener { /** * Property name of the property change event fired if the configuration is changed. @@ -308,6 +314,7 @@ public void start() { configLock.unlock(); } } + EventService.subscribe(this, new EventFilter(EventType.CONFIGURATION_CHANGED)); LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this); } @@ -331,6 +338,7 @@ public void start(final Configuration config) { } } setConfiguration(config); + EventService.subscribe(this, new EventFilter(EventType.CONFIGURATION_CHANGED)); LOGGER.info("{}[name={}] started with configuration {}.", getClass().getSimpleName(), getName(), config); } @@ -863,4 +871,12 @@ private Logger newInstance(final String name, final MessageFactory messageFactor protected Logger newInstance(LoggerContext context, String name, MessageFactory messageFactory) { return new Logger(context, name, messageFactory); } + + @Override + public void onEvent(Event event) { + LOGGER.debug("Event received: " + event.getEventType()); + if (configuration instanceof Reconfigurable) { + onChange((Reconfigurable) configuration); + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java index 6eed83a54e9..abf19fcd6f4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/package-info.java @@ -18,7 +18,7 @@ * Provides Asynchronous Logger classes and interfaces for low-latency logging. */ @Export -@Version("2.24.1") +@Version("2.25.0") package org.apache.logging.log4j.core.async; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/event/Event.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/Event.java new file mode 100644 index 00000000000..1e575a0d282 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/Event.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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.apache.logging.log4j.core.event; + +/** + * Represents an event + */ +public class Event { + public enum EventType { + CONFIGURATION_CHANGED + }; + + private EventType eventType; + + public Event(final EventType eventType) { + this.eventType = eventType; + } + + public EventType getEventType() { + return eventType; + } + + public void setEventType(final EventType eventType) { + this.eventType = eventType; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventFilter.java new file mode 100644 index 00000000000..2f455b75357 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventFilter.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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.apache.logging.log4j.core.event; + +import org.apache.logging.log4j.core.event.Event.EventType; + +/** + * A filter that can be used in subscribing for events + */ +public class EventFilter { + + private EventType eventType; + + public EventFilter(final EventType eventType) { + this.eventType = eventType; + } + + public boolean matches(final Event event) { + return event.getEventType().equals(eventType); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventListener.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventListener.java new file mode 100644 index 00000000000..426baeeb885 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventListener.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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.apache.logging.log4j.core.event; + +/** + * Interface to be implemented by classes interested in receiving events + */ +public interface EventListener { + + /** + * Handle an event + * @param event the event to handle + */ + void onEvent(Event event); +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventService.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventService.java new file mode 100644 index 00000000000..7fe4f729c09 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/EventService.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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.apache.logging.log4j.core.event; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A service for handling events and subscriptions to events + */ +public class EventService { + + private static Map eventListeners = new ConcurrentHashMap<>(); + + /** + * Subscribe to receive events + * @param eventListener the listener the events should be sent to + * @param eventFilter a filter to control the events to be sent to the listener + */ + public static void subscribe(final EventListener eventListener, final EventFilter eventFilter) { + eventListeners.put(eventListener, eventFilter); + } + + /** + * Publish the given event to subscribed listeners + * + * @param event the event to publish + */ + public static void publish(Event event) { + System.out.println("Publishing event " + event); + + eventListeners.entrySet().stream() + .filter(listener -> listener.getValue().matches(event)) + .forEach(listener -> { + System.out.println("To:" + listener.getKey()); + listener.getKey().onEvent(event); + }); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/event/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/package-info.java new file mode 100644 index 00000000000..b9132a29de7 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/event/package-info.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ +/** + * Classes and interfaces supporting configuration of Log4j 2 with JSON. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.core.event; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java index a70b71edd32..e871b84d257 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java @@ -16,16 +16,29 @@ */ package org.apache.logging.log4j.core.net.ssl; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileSystems; +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.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.util.Arrays; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executors; import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.event.Event; +import org.apache.logging.log4j.core.event.Event.EventType; +import org.apache.logging.log4j.core.event.EventService; import org.apache.logging.log4j.core.util.NetUtils; /** @@ -63,6 +76,20 @@ public AbstractKeyStoreConfiguration(final String location, final String passwor this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()), keyStoreType); } + private static void watchKeyAndTrustStoreFiles(final String keyStoreLocation, final String trustStoreLocation) { + Set filePathsToWatch = new HashSet<>(); + if (keyStoreLocation != null) { + filePathsToWatch.add(keyStoreLocation); + } + if (trustStoreLocation != null) { + filePathsToWatch.add(trustStoreLocation); + } + } + + private void watchKeyStoreForChanges(final String location) { + Executors.newSingleThreadExecutor().execute(new WatchRunnable(location)); + } + @Override protected KeyStore load() throws StoreConfigurationException { final String loadLocation = this.getLocation(); @@ -81,6 +108,9 @@ protected KeyStore load() throws StoreConfigurationException { try (final InputStream fin = openInputStream(loadLocation)) { ks.load(fin, password); LOGGER.debug("KeyStore successfully loaded from location {}", loadLocation); + + watchKeyStoreForChanges(loadLocation); + return ks; } } catch (final CertificateException e) { @@ -152,4 +182,39 @@ public boolean equals(final Object obj) { public String getKeyStoreType() { return keyStoreType; } + + private static final class WatchRunnable implements Runnable { + + private String fileToWatch; + + public WatchRunnable(String fileToWatch) { + this.fileToWatch = fileToWatch; + } + + @Override + public void run() { + WatchService watchService; + try { + watchService = FileSystems.getDefault().newWatchService(); + + File file = new File(fileToWatch); + Path directory = file.getParentFile().toPath(); + LOGGER.info("Watching {} for changes", fileToWatch); + directory.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); + + WatchKey key; + while ((key = watchService.take()) != null) { + for (WatchEvent event : key.pollEvents()) { + if (file.getName().equals(event.context().toString())) { + LOGGER.info("Change detected in configuration file {}", event.context()); + EventService.publish(new Event(EventType.CONFIGURATION_CHANGED)); + } + } + key.reset(); + } + } catch (IOException | InterruptedException exception) { + LOGGER.error("Error watching a configuration related file for SocketAppender", exception); + } + } + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java index 00552976b76..171f9c7e78d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/package-info.java @@ -18,7 +18,7 @@ * Implementation of Log4j 2. */ @Export -@Version("2.24.2") +@Version("2.25.0") package org.apache.logging.log4j.core; import org.osgi.annotation.bundle.Export;