/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.runtime.module.launcher.log4j2;
import static java.lang.ClassLoader.getSystemClassLoader;
import org.mule.runtime.api.lifecycle.Disposable;
import org.mule.runtime.deployment.model.internal.domain.MuleSharedDomainClassLoader;
import org.mule.runtime.module.artifact.classloader.ArtifactClassLoader;
import org.mule.runtime.module.artifact.classloader.RegionClassLoader;
import org.mule.runtime.module.artifact.classloader.ShutdownListener;
import java.net.URI;
import java.util.List;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.selector.ContextSelector;
import org.apache.logging.log4j.status.StatusLogger;
/**
* Implementation of {@link org.apache.logging.log4j.core.selector.ContextSelector} which is used to implement log separation
* based on provided or current {@link java.lang.ClassLoader}
* <p/>
* This component is responsible for managing the {@link org.apache.logging.log4j.core.LoggerContext} that corresponds to each
* artifact (aka applications, domains, container), using its classloader as an identifier. The same classloader always gets the
* same {@link org.apache.logging.log4j.core.LoggerContext}
* <p/>
* This component also overrides log4j2's default algorithm for locating configuration files, although it does it in a way
* consistent with the replaced behavior:
* <ul>
* <li>A file called log4j2-test.xml is fetched from the corresponding search path</li>
* <li>If log4j2-test.xml is not found, then log4j2.xml is attempted</li>
* <li>If not found, a default configuration consisting of a single rolling file appender is used</li>
* <li>The search path is derived from the artifact for which a logging context is being requested, following a child first
* strategy (artifact - domain - container). Each artifact starts looking in the phase that makes sense for it</li>
* </ul>
* <p/>
* If the classloader is an artifact one, then it adds a {@link ShutdownListener} to destroy the logging context when the app is
* undeployed, preventing memory leaks.
* <p/>
* If mule is running in embedded mode, then all of this logic described above is discarded and it simply logs to a file called
* mule-main.log
*
* @since 3.6.0
*/
class ArtifactAwareContextSelector implements ContextSelector, Disposable {
static final StatusLogger LOGGER = StatusLogger.getLogger();
private static final ClassLoader SYSTEM_CLASSLOADER = getSystemClassLoader();
private LoggerContextCache cache = new LoggerContextCache(this, getClass().getClassLoader());
private final MuleLoggerContextFactory loggerContextFactory = new MuleLoggerContextFactory();
ArtifactAwareContextSelector() {}
@Override
public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
@Override
public LoggerContext getContext(String fqcn, ClassLoader classLoader, boolean currentContext, URI configLocation) {
return cache.getLoggerContext(resolveLoggerContextClassLoader(classLoader));
}
@Override
public List<LoggerContext> getLoggerContexts() {
return cache.getAllLoggerContexts();
}
@Override
public void removeContext(LoggerContext context) {
cache.remove(context);
}
/**
* Given a {@code classLoader} this method will resolve which is the {@code classLoader} associated with the logger context to
* use for this {@code classLoader} .
* <p/>
* When the provided {@code classLoader} is from an application or a domain it will return the {@code classLoader} associated
* with the logger context of the application or domain. So far the artifact (domain or app) {@code classLoader} will be
* resolved to it self.
* <p/>
* If the {@code classLoader} belongs to the container or any other {@code classLoader} created from a library running outside
* the context of an artifact then the system {@code classLoader} will be used.
*
* @param classLoader {@link ClassLoader} running the code where the logging was done
* @return the {@link ClassLoader} owner of the logger context
*/
static ClassLoader resolveLoggerContextClassLoader(ClassLoader classLoader) {
ClassLoader loggerClassLoader = classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
// Obtains the first artifact class loader in the hierarchy
while (!(loggerClassLoader instanceof ArtifactClassLoader) && loggerClassLoader != null) {
loggerClassLoader = loggerClassLoader.getParent();
}
if (!(loggerClassLoader instanceof ArtifactClassLoader)) {
return SYSTEM_CLASSLOADER;
} else if (isRegionClassLoaderMember(loggerClassLoader)) {
loggerClassLoader = loggerClassLoader.getParent();
} else if (!(loggerClassLoader instanceof RegionClassLoader) && !(loggerClassLoader instanceof MuleSharedDomainClassLoader)) {
return SYSTEM_CLASSLOADER;
}
return loggerClassLoader;
}
private static boolean isRegionClassLoaderMember(ClassLoader classLoader) {
return !(classLoader instanceof RegionClassLoader) && classLoader.getParent() instanceof RegionClassLoader;
}
@Override
public void dispose() {
cache.dispose();
}
public void destroyLoggersFor(ClassLoader classLoader) {
cache.remove(classLoader);
}
LoggerContext buildContext(final ClassLoader classLoader) {
return loggerContextFactory.build(classLoader, this);
}
}