package org.hotswap.agent.plugin.log4j2; import org.hotswap.agent.annotation.FileEvent; import org.hotswap.agent.annotation.Init; import org.hotswap.agent.annotation.OnClassLoadEvent; import org.hotswap.agent.annotation.Plugin; import org.hotswap.agent.javassist.CannotCompileException; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtMethod; import org.hotswap.agent.javassist.NotFoundException; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.IOUtils; import org.hotswap.agent.util.PluginManagerInvoker; import org.hotswap.agent.watch.WatchEventListener; import org.hotswap.agent.watch.WatchFileEvent; import org.hotswap.agent.watch.Watcher; import java.net.URI; import java.nio.file.Paths; import java.util.HashSet; import java.util.Set; /** * Log4j2 configuration file reload. * * @author Lukasz Warzecha */ @Plugin(name = "Log4j2", description = "Log4j2 configuration reload.", testedVersions = { "2.1", "2.5", "2.7" }) public class Log4j2Plugin { private static final AgentLogger LOGGER = AgentLogger.getLogger(Log4j2Plugin.class); @Init Watcher watcher; @Init ClassLoader appClassLoader; // ensure uri registered only once Set<URI> registeredURIs = new HashSet<>(); volatile boolean initialized; /** * Callback method from * org.apache.logging.log4j.core.LoggerContext.setConfiguration(Configuration) * * @param config the Log4j2 configuration object */ public void init(final Object config) { URI configURI = null; try { Class<?> configurationClass = appClassLoader.loadClass("org.apache.logging.log4j.core.config.Configuration"); Class<?> configurationSourceClass = appClassLoader.loadClass("org.apache.logging.log4j.core.config.ConfigurationSource"); Object configurationSource = configurationClass.getDeclaredMethod("getConfigurationSource").invoke(config); String url = (String) configurationSourceClass.getDeclaredMethod("getLocation").invoke(configurationSource); configURI = Paths.get(url).toUri(); if (registeredURIs.contains(configURI)) { return; } final URI parentUri = Paths.get(configURI).getParent().toUri(); LOGGER.debug("Watching '{}' URI for Log4j2 configuration changes.", configURI); registeredURIs.add(configURI); watcher.addEventListener(appClassLoader, parentUri, new WatchEventListener() { @Override public void onEvent(WatchFileEvent event) { if (event.getEventType() != FileEvent.DELETE && registeredURIs.contains(event.getURI())) { reload(event.getURI()); } } }); if (!initialized) { LOGGER.info("Log4j2 plugin initialized."); initialized = true; } } catch (Exception e) { LOGGER.error("Exception initializing Log4j2 on uri {}.", e, configURI); } } /** * Do the reload using Log4j2 configurator. * * @param uri the configuration file uri */ protected void reload(URI uri) { try { IOUtils.toByteArray(uri); } catch (Exception e) { LOGGER.warning("Unable to open Log4j2 configuration file {}, is it deleted?", uri); return; } try { Class<?> logManagerClass = appClassLoader.loadClass("org.apache.logging.log4j.LogManager"); Class<?> contextClass = appClassLoader.loadClass("org.apache.logging.log4j.core.LoggerContext"); Object context = logManagerClass.getDeclaredMethod("getContext", Boolean.TYPE).invoke(logManagerClass, true); // resetting configLocation forces reconfiguration contextClass.getDeclaredMethod("setConfigLocation", URI.class).invoke(context, uri); LOGGER.reload("Log4j2 configuration reloaded from uri '{}'.", uri); } catch (Exception e) { LOGGER.error("Unable to reload {} with Log4j2", e, uri); } } @OnClassLoadEvent(classNameRegexp = "org.apache.logging.log4j.core.LoggerContext") public static void registerConfigurator(ClassPool classPool, CtClass ctClass) throws NotFoundException, CannotCompileException { // fallback to the old version (<2.3) of Log4j2 CtMethod m = ctClass.getDeclaredMethod("setConfiguration", new CtClass[] { classPool.get("org.apache.logging.log4j.core.config.Configuration") }); m.insertAfter(PluginManagerInvoker.buildInitializePlugin(Log4j2Plugin.class)); m.insertAfter(PluginManagerInvoker.buildCallPluginMethod(Log4j2Plugin.class, "init", "config", "java.lang.Object")); } }