From dada5bf4cd7ee5d035cc056ee54b320ce9596d67 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Thu, 22 May 2025 15:21:20 -0700 Subject: [PATCH 01/10] Issue 52684: Update Log4j plugin registration --- server/bootstrap/build.gradle | 1 + .../org/labkey/bootstrap/CommonsLogger.java | 105 ------------------ .../bootstrap/LabKeyBootstrapClassLoader.java | 16 ++- .../org/labkey/bootstrap/ModuleArchive.java | 20 ++-- .../org/labkey/bootstrap/ModuleExtractor.java | 35 +++--- .../bootstrap/PipelineBootstrapConfig.java | 2 +- .../org/labkey/bootstrap/SimpleLogger.java | 28 ----- .../org/labkey/bootstrap/StdOutLogger.java | 42 ------- server/embedded/build.gradle | 12 ++ server/embedded/src/main/resources/log4j2.xml | 15 ++- .../embedded/LabKeySpringBootClassLoader.java | 2 +- 11 files changed, 55 insertions(+), 223 deletions(-) delete mode 100644 server/bootstrap/src/org/labkey/bootstrap/CommonsLogger.java delete mode 100644 server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java delete mode 100644 server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java diff --git a/server/bootstrap/build.gradle b/server/bootstrap/build.gradle index b023f18f86..880ad5f812 100644 --- a/server/bootstrap/build.gradle +++ b/server/bootstrap/build.gradle @@ -21,6 +21,7 @@ dependencies strictly "${apacheTomcatVersion}" } } + implementation "org.apache.logging.log4j:log4j-core:${log4j2Version}" } def JAR_BASE_NAME = "labkeyBootstrap" diff --git a/server/bootstrap/src/org/labkey/bootstrap/CommonsLogger.java b/server/bootstrap/src/org/labkey/bootstrap/CommonsLogger.java deleted file mode 100644 index 30d63566d4..0000000000 --- a/server/bootstrap/src/org/labkey/bootstrap/CommonsLogger.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2009-2013 LabKey Corporation - * - * 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.labkey.bootstrap; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * User: jeckels - * Date: Mar 11, 2009 - */ -public class CommonsLogger implements SimpleLogger -{ - private Object _log = null; - private Method _errorEx = null; - private Method _error = null; - private Method _info = null; - - public CommonsLogger(Class c) - { - try - { - // The Tomcat 6 compatible approach - _log = getFactoryClass("org.apache.juli.logging.LogFactory", c); - // The implementation class is package protected, but the interface is public - - // Class or interface that declares the methods that we'll be permitted to call - Class interfaceClass = (Class) Class.forName("org.apache.juli.logging.Log"); - - _errorEx = interfaceClass.getMethod("error", Object.class, Throwable.class); - _error = interfaceClass.getMethod("error", Object.class); - _info = interfaceClass.getMethod("info", Object.class); - } - catch (Exception x) - { - System.err.println("CommonsLogger: not initialized"); - x.printStackTrace(System.err); - } - } - - private Object getFactoryClass(String factoryClassName, Class logTarget) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException - { - Class factoryClass = Class.forName(factoryClassName); - Method getLog = factoryClass.getMethod("getLog", Class.class); - return getLog.invoke(null, logTarget); - } - - - @Override - public void error(Object message, Throwable t) - { - try - { - if (null != _log && null != _error) - _errorEx.invoke(_log, message, t); - } - catch (Exception ignored) - { - - } - } - - - @Override - public void error(Object message) - { - try - { - if (null != _log && null != _error) - _error.invoke(_log, message); - } - catch (Exception ignored) - { - - } - } - - - @Override - public void info(Object message) - { - try - { - if (null != _log && null != _error) - _info.invoke(_log, message); - } - catch (Exception ignored) - { - - } - } -} diff --git a/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java b/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java index 905e43b8a2..ae9038b4a6 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java +++ b/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java @@ -18,6 +18,8 @@ import org.apache.catalina.WebResourceRoot; import org.apache.catalina.loader.WebappClassLoader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.File; import java.io.FileNotFoundException; @@ -41,7 +43,7 @@ */ public class LabKeyBootstrapClassLoader extends WebappClassLoader implements ExplodedModuleService { - private final SimpleLogger _log = new CommonsLogger(LabKeyBootstrapClassLoader.class); + private final static Logger _log = LogManager.getLogger(LabKeyBootstrapClassLoader.class); /** Modules which have been previously logged as having changed, which would trigger a webapp redeployment in development scenarios */ private final Set _previouslyLoggedModules = new HashSet<>(); @@ -87,7 +89,7 @@ private void extract(File webappDir) { try { - _moduleExtractor = new ModuleExtractor(webappDir, new CommonsLogger(ModuleExtractor.class)); + _moduleExtractor = new ModuleExtractor(webappDir); var explodedModules = _moduleExtractor.extractModules(); for(var exploded : explodedModules) { @@ -176,10 +178,6 @@ public List> getExplodedModules() * * NOTE: this doesn't guarantee that the webapp won't reload. The caller has to inspect the archive * to ensure that. - * - * @param updatedArchive - * @param existingArchive - * @return */ public void validateReplaceArchive(File explodedModuleDirectory, File updatedArchive, File existingArchive) throws IOException { @@ -193,8 +191,8 @@ public void validateReplaceArchive(File explodedModuleDirectory, File updatedArc if (!_moduleExtractor.hasExplodedArchive(existingArchive)) throw new IllegalStateException(existingArchive.getAbsolutePath() + " it not an existing archive"); - ModuleArchive existingModuleArchive = new ModuleArchive(existingArchive, _log); - ModuleArchive updatedModuleArchive = new ModuleArchive(updatedArchive, _log); + ModuleArchive existingModuleArchive = new ModuleArchive(existingArchive); + ModuleArchive updatedModuleArchive = new ModuleArchive(updatedArchive); if (!existingModuleArchive.getModuleName().equalsIgnoreCase(updatedModuleArchive.getModuleName())) throw new IllegalArgumentException("Module name doesn't match, expected " + existingModuleArchive.getModuleName()); @@ -286,7 +284,7 @@ public void validateCreateArchive(File newArchive, File target) throws IOExcepti if (target.exists()) throw new IllegalArgumentException("File already exists: " + target.getPath()); - ModuleArchive newModuleArchive = new ModuleArchive(newArchive, _log); + ModuleArchive newModuleArchive = new ModuleArchive(newArchive); String moduleName = newModuleArchive.getModuleName(); if (null==moduleName || moduleName.isBlank()) throw new IllegalArgumentException("Module name not found in archive"); diff --git a/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java b/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java index f2bc357099..2e43673944 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java +++ b/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java @@ -15,6 +15,8 @@ */ package org.labkey.bootstrap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -53,10 +55,8 @@ public class ModuleArchive protected static final FileComparator _fileComparator = new FileComparator(); private final File _file; - private final long _modified; private final String _moduleName; - private final SimpleLogger _log; - private final boolean _hasJavaCode; + private final static Logger LOG = LogManager.getLogger(ModuleArchive.class); private String stripToNull(String s) @@ -118,12 +118,10 @@ private String nameFromModuleProperties(InputStream is) throws IOException } - public ModuleArchive(File file, SimpleLogger log) throws IOException + public ModuleArchive(File file) throws IOException { _file = file; assert _file.exists() && _file.isFile(); - _modified = _file.lastModified(); - _log = log; String moduleName = null; boolean hasJavaCode = false; @@ -163,7 +161,6 @@ public ModuleArchive(File file, SimpleLogger log) throws IOException } _moduleName = moduleName; - _hasJavaCode = hasJavaCode; } public File getFile() @@ -181,8 +178,7 @@ public String getModuleName() if (null == _moduleName) { String fileName = getFile().getName(); - String baseName = fileName.substring(0, fileName.length() - FILE_EXTENSION.length()); - return baseName; + return fileName.substring(0, fileName.length() - FILE_EXTENSION.length()); } return _moduleName; } @@ -234,7 +230,7 @@ public void extractAll(File targetDirectory) throws IOException File archiveFile = getFile(); long archiveFileLastModified = archiveFile.lastModified(); - _log.info("Extracting module " + archiveFile.getName() + "."); + LOG.info("Extracting module " + archiveFile.getName() + "."); // delete existing directory so that files that are // no longer in the archive are removed @@ -259,7 +255,7 @@ public void extractAll(File targetDirectory) throws IOException //set last mod on target directory to match module file targetDirectory.setLastModified(archiveFileLastModified); - _log.info("Done extracting module " + archiveFile.getName() + ". Processed " + fileCount + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); + LOG.info("Done extracting module " + archiveFile.getName() + ". Processed " + fileCount + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); } public File extractEntry(JarFile jar, JarEntry entry, File targetDirectory) throws IOException @@ -271,7 +267,7 @@ public File extractEntry(JarFile jar, JarEntry entry, File targetDirectory) thro entryParent.mkdirs(); if (!entryParent.isDirectory()) { - _log.error("Unable to create directory " + entryParent.getPath() + ", there may be a problem with file permissions"); + LOG.error("Unable to create directory " + entryParent.getPath() + ", there may be a problem with file permissions"); } // if entry is a directory, just mkdirs, set last mod and return diff --git a/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java b/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java index 7b319d478f..1bd2ccbc55 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java +++ b/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java @@ -16,6 +16,9 @@ package org.labkey.bootstrap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -37,13 +40,12 @@ public class ModuleExtractor private Set _ignoredExplodedDirs; private Set _explodedModules; - private final SimpleLogger _log; + private static final Logger _log = LogManager.getLogger(ModuleExtractor.class); - public ModuleExtractor(File webAppDirectory, SimpleLogger log) + public ModuleExtractor(File webAppDirectory) { _webAppDirectory = webAppDirectory; _moduleDirectories = new ModuleDirectories(_webAppDirectory); - _log = log; } public Collection extractModules() @@ -71,11 +73,11 @@ public Collection extractModules() .map(moduleArchiveFile -> { try { - return new ModuleArchive(moduleArchiveFile, _log); + return new ModuleArchive(moduleArchiveFile); } catch (IOException e) { - _log.error("Unable to open module archive " + moduleArchiveFile.getPath() + "!", e); + _log.error("Unable to open module archive {}!", moduleArchiveFile.getPath(), e); _errorArchives.put(moduleArchiveFile, moduleArchiveFile.lastModified()); return null; } @@ -89,8 +91,8 @@ public Collection extractModules() if (null != found) { var re = new IllegalStateException("LabKey found two modules with the name \"" + moduleArchive.getModuleName() + "\". Please resolve this problem and restart the server"); - _log.error("Unable to extract module archive " + found.getFile().getPath() + "!"); - _log.error("Unable to extract module archive " + moduleArchive.getFile().getPath(), re); + _log.error("Unable to extract module archive {}!", found.getFile().getPath()); + _log.error("Unable to extract module archive {}", moduleArchive.getFile().getPath(), re); throw re; } } @@ -107,7 +109,7 @@ public Collection extractModules() } catch (IOException e) { - _log.error("Unable to extract module archive " + moduleArchiveFile.getPath() + "!", e); + _log.error("Unable to extract module archive {}!", moduleArchiveFile.getPath(), e); _errorArchives.put(moduleArchiveFile, moduleArchiveFile.lastModified()); } }); @@ -120,7 +122,7 @@ public Collection extractModules() // from a .module archive. _moduleDirectories.streamAllModuleDirectories() .flatMap(dir-> {File[] files=dir.listFiles(File::isDirectory); return null==files ? null : Stream.of(files);}) - .collect(Collectors.toList()) // This intermediate list is critical. See comment above. + .toList() // This intermediate list is critical. See comment above. .parallelStream() .forEach(dir->{ if (dir.isHidden() || dir.getName().startsWith(".")) @@ -133,16 +135,16 @@ public Collection extractModules() { ModuleArchive archive = mapModuleDirToArchive.get(dir.getAbsoluteFile()); ExplodedModule explodedModule = new ExplodedModule(dir, null==archive?null:archive.getFile()); - _log.info("Deploying resources from " + explodedModule.getRootDirectory() + "."); + _log.info("Deploying resources from {}.", explodedModule.getRootDirectory()); long startTime = System.currentTimeMillis(); Set moduleWebAppFiles = explodedModule.deployToWebApp(_webAppDirectory); _explodedModules.add(explodedModule); - _log.info("Done deploying resources from " + explodedModule.getRootDirectory() + ". Extracted " + moduleWebAppFiles.size() + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); + _log.info("Done deploying resources from {}. Extracted {} file(s) in {}ms.", explodedModule.getRootDirectory(), moduleWebAppFiles.size(), System.currentTimeMillis() - startTime); } catch(IOException e) { - _log.error("Unable to deploy resources from exploded module " + dir.getPath() + " to web app directory!", e); + _log.error("Unable to deploy resources from exploded module {} to web app directory!", dir.getPath(), e); } }); @@ -239,7 +241,7 @@ public boolean areModulesModified(Set previouslyLoggedModules) moduleArchive = null; if (null == moduleArchive) { - moduleArchive = new ModuleArchive(moduleArchiveFile, _log); + moduleArchive = new ModuleArchive(moduleArchiveFile); File explodedDir = moduleArchive.extractAll(); new ExplodedModule(explodedDir).deployToWebApp(_webAppDirectory); _moduleArchiveFiles.put(moduleArchiveFile, moduleArchive); @@ -320,7 +322,7 @@ public boolean hasExplodedArchive(File moduleArchive) */ public Map.Entry extractUpdatedModuleArchive(File moduleArchiveFile, File previousArchiveFile) throws IOException { - ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile, _log); + ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile); File explodedDir = moduleArchive.extractAll(); ExplodedModule explodedModule = new ExplodedModule(explodedDir, moduleArchiveFile); @@ -340,7 +342,7 @@ public Map.Entry extractUpdatedModuleArchive(File moduleArchiveFile, public Map.Entry extractNewModuleArchive(File moduleArchiveFile) throws IOException { - ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile, _log); + ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile); File explodedDir = moduleArchive.extractAll(); ExplodedModule explodedModule = new ExplodedModule(explodedDir, moduleArchiveFile); @@ -356,7 +358,6 @@ public Map.Entry extractNewModuleArchive(File moduleArchiveFile) thro * Extract .module files * @param args see usages * @throws ConfigException thrown if there is a problem with the configuration - * @throws IOException thrown if there is a problem extracting the module archives */ public static void main(String... args) { @@ -364,7 +365,7 @@ public static void main(String... args) { PipelineBootstrapConfig config = new PipelineBootstrapConfig(args, false); - ModuleExtractor extractor = new ModuleExtractor(config.getWebappDir(), new StdOutLogger()); + ModuleExtractor extractor = new ModuleExtractor(config.getWebappDir()); extractor.extractModules(); } catch (ConfigException e) diff --git a/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java b/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java index a1b198dc8c..8c5de38244 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java +++ b/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java @@ -177,7 +177,7 @@ private synchronized void init() { if (_classLoader == null) { - ModuleExtractor extractor = new ModuleExtractor(getWebappDir(), new StdOutLogger()); + ModuleExtractor extractor = new ModuleExtractor(getWebappDir()); Collection explodedModules = extractor.extractModules(); _moduleFiles = new ArrayList<>(extractor.getExplodedModuleDirectories()); _moduleSpringContextFiles = new ArrayList<>(); diff --git a/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java b/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java deleted file mode 100644 index 038b0810f8..0000000000 --- a/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2009-2018 LabKey Corporation - * - * 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.labkey.bootstrap; - -/** - * Simple interface to abstract logger implementations and break dependencies on other JARs when they're not needed. - * User: jeckels - * Date: Mar 11, 2009 - */ -public interface SimpleLogger -{ - public void error(Object message, Throwable t); - public void error(Object message); - public void info(Object message); -} diff --git a/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java b/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java deleted file mode 100644 index f19442233f..0000000000 --- a/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2009 LabKey Corporation - * - * 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.labkey.bootstrap; - -/** - * User: jeckels - * Date: Mar 11, 2009 - */ -public class StdOutLogger implements SimpleLogger -{ - @Override - public void error(Object message, Throwable t) - { - System.err.println(message); - t.printStackTrace(System.err); - } - - @Override - public void error(Object message) - { - System.err.println(message); - } - - @Override - public void info(Object message) - { - System.out.println(message); - } -} diff --git a/server/embedded/build.gradle b/server/embedded/build.gradle index 8c01df0a60..1b81575455 100644 --- a/server/embedded/build.gradle +++ b/server/embedded/build.gradle @@ -88,6 +88,18 @@ dependencies { implementation "org.apache.logging.log4j:log4j-core:${log4j2Version}" developmentOnly("org.springframework.boot:spring-boot-devtools") + + // Process sources using `log4j-core` providing `org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor` that generates `Log4j2Plugins.dat` --> + annotationProcessor("org.apache.logging.log4j:log4j-core:${log4j2Version}") + + // Exclude Spring Boot's default logging framework so we can direct it to log through Log4J + implementation('org.springframework.boot:spring-boot-starter') { + exclude group: 'ch.qos.logback', module: 'logback-classic' + exclude group: 'org.slf4j', module: 'slf4j-log4j12' + } + + // Add Log4j2 Starter to get Spring Boot logging captured consistently with the rest of our logging + implementation 'org.springframework.boot:spring-boot-starter-log4j2' } BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: BuildUtils.getBootstrapProjectPath(gradle)) diff --git a/server/embedded/src/main/resources/log4j2.xml b/server/embedded/src/main/resources/log4j2.xml index 8be958e0e4..5dc3ed21f2 100644 --- a/server/embedded/src/main/resources/log4j2.xml +++ b/server/embedded/src/main/resources/log4j2.xml @@ -1,13 +1,6 @@ - - - - + @@ -17,6 +10,7 @@ account managers so that we can coordinate edits with other customized copies of @@ -37,6 +31,7 @@ account managers so that we can coordinate edits with other customized copies of @@ -53,6 +48,7 @@ account managers so that we can coordinate edits with other customized copies of @@ -69,6 +65,7 @@ account managers so that we can coordinate edits with other customized copies of @@ -84,6 +81,7 @@ account managers so that we can coordinate edits with other customized copies of @@ -147,6 +145,7 @@ account managers so that we can coordinate edits with other customized copies of diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index 4f03b0d404..3336e626e6 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -60,7 +60,7 @@ protected boolean filter(String name, boolean isClassName) // Defer to the Spring Boot classloader for SLF4J classes to avoid problems with double-loading. // Eventually we should shift to only configuring and loading SLF4J and Log4J via Spring Boot and not // from inside the webapp. - if (name.startsWith("org.slf4j.")) + if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j")) { return true; } From 905f1dc2f22027d98460738d0065c1f31be491d9 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Thu, 22 May 2025 15:33:16 -0700 Subject: [PATCH 02/10] Update comments --- .../org/labkey/embedded/LabKeySpringBootClassLoader.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index 3336e626e6..191e8e8a16 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -15,8 +15,8 @@ import java.util.List; /** - * Variant of the classloader that supports Spring Boot by deferring to the parent classloader for SLF4J classes - * to avoid conflicting copies (even if they're the same version) between the parent and webapp classloaders. + * Variant of the classloader that supports Spring Boot by deferring to the parent classloader for SLF4J and Log4J classes + * to avoid duplicate copies (even if they're the same version) between the parent and webapp classloaders. */ public class LabKeySpringBootClassLoader extends LabKeyBootstrapClassLoader { @@ -57,9 +57,8 @@ public LabKeySpringBootClassLoader(ClassLoader parent) @Override protected boolean filter(String name, boolean isClassName) { - // Defer to the Spring Boot classloader for SLF4J classes to avoid problems with double-loading. - // Eventually we should shift to only configuring and loading SLF4J and Log4J via Spring Boot and not - // from inside the webapp. + // Defer to the Spring Boot classloader for SLF4J and Log4J classes to avoid problems with double-loading. + // Eventually we should shift to only shipping SLF4J and Log4J via Spring Boot and not inside the webapp. if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j")) { return true; From cfbe3ef2c82e712fdf0c31a42b172852c9b4a16c Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 23 May 2025 15:27:58 -0700 Subject: [PATCH 03/10] Fix pipeline startup and tests --- .../bootstrap/LabKeyBootstrapClassLoader.java | 10 +- .../src/org/labkey/bootstrap/Log4JLogger.java | 32 ++++ .../org/labkey/bootstrap/ModuleArchive.java | 20 ++- .../org/labkey/bootstrap/ModuleExtractor.java | 32 ++-- .../bootstrap/PipelineBootstrapConfig.java | 2 +- .../org/labkey/bootstrap/SimpleLogger.java | 28 ++++ .../org/labkey/bootstrap/StdOutLogger.java | 42 ++++++ .../labkey/embedded/EmbeddedExtractor.java | 141 ++++++++++++------ .../src/org/labkey/embedded/LabKeyServer.java | 2 +- .../LabKeyTomcatServletWebServerFactory.java | 2 +- 10 files changed, 230 insertions(+), 81 deletions(-) create mode 100644 server/bootstrap/src/org/labkey/bootstrap/Log4JLogger.java create mode 100644 server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java create mode 100644 server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java diff --git a/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java b/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java index ae9038b4a6..0ce6b06436 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java +++ b/server/bootstrap/src/org/labkey/bootstrap/LabKeyBootstrapClassLoader.java @@ -44,6 +44,8 @@ public class LabKeyBootstrapClassLoader extends WebappClassLoader implements ExplodedModuleService { private final static Logger _log = LogManager.getLogger(LabKeyBootstrapClassLoader.class); + private static final Log4JLogger MODULE_ARCHIVE_LOG = new Log4JLogger(LogManager.getLogger(ModuleArchive.class)); + private static final Log4JLogger MODULE_EXTRACTOR_LOG = new Log4JLogger(LogManager.getLogger(ModuleExtractor.class)); /** Modules which have been previously logged as having changed, which would trigger a webapp redeployment in development scenarios */ private final Set _previouslyLoggedModules = new HashSet<>(); @@ -89,7 +91,7 @@ private void extract(File webappDir) { try { - _moduleExtractor = new ModuleExtractor(webappDir); + _moduleExtractor = new ModuleExtractor(webappDir, MODULE_EXTRACTOR_LOG); var explodedModules = _moduleExtractor.extractModules(); for(var exploded : explodedModules) { @@ -191,8 +193,8 @@ public void validateReplaceArchive(File explodedModuleDirectory, File updatedArc if (!_moduleExtractor.hasExplodedArchive(existingArchive)) throw new IllegalStateException(existingArchive.getAbsolutePath() + " it not an existing archive"); - ModuleArchive existingModuleArchive = new ModuleArchive(existingArchive); - ModuleArchive updatedModuleArchive = new ModuleArchive(updatedArchive); + ModuleArchive existingModuleArchive = new ModuleArchive(existingArchive, MODULE_ARCHIVE_LOG); + ModuleArchive updatedModuleArchive = new ModuleArchive(updatedArchive, MODULE_ARCHIVE_LOG); if (!existingModuleArchive.getModuleName().equalsIgnoreCase(updatedModuleArchive.getModuleName())) throw new IllegalArgumentException("Module name doesn't match, expected " + existingModuleArchive.getModuleName()); @@ -284,7 +286,7 @@ public void validateCreateArchive(File newArchive, File target) throws IOExcepti if (target.exists()) throw new IllegalArgumentException("File already exists: " + target.getPath()); - ModuleArchive newModuleArchive = new ModuleArchive(newArchive); + ModuleArchive newModuleArchive = new ModuleArchive(newArchive, MODULE_ARCHIVE_LOG); String moduleName = newModuleArchive.getModuleName(); if (null==moduleName || moduleName.isBlank()) throw new IllegalArgumentException("Module name not found in archive"); diff --git a/server/bootstrap/src/org/labkey/bootstrap/Log4JLogger.java b/server/bootstrap/src/org/labkey/bootstrap/Log4JLogger.java new file mode 100644 index 0000000000..98941b4fbe --- /dev/null +++ b/server/bootstrap/src/org/labkey/bootstrap/Log4JLogger.java @@ -0,0 +1,32 @@ +package org.labkey.bootstrap; + +import org.apache.logging.log4j.Logger; + +public class Log4JLogger implements SimpleLogger +{ + private final Logger _logger; + + public Log4JLogger(Logger logger) + { + _logger = logger; + } + + @Override + public void error(Object message, Throwable t) + { + _logger.error(message, t); + } + + @Override + public void error(Object message) + { + _logger.error(message); + } + + @Override + public void info(Object message) + { + _logger.info(message); + } + +} diff --git a/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java b/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java index 2e43673944..f2bc357099 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java +++ b/server/bootstrap/src/org/labkey/bootstrap/ModuleArchive.java @@ -15,8 +15,6 @@ */ package org.labkey.bootstrap; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; @@ -55,8 +53,10 @@ public class ModuleArchive protected static final FileComparator _fileComparator = new FileComparator(); private final File _file; + private final long _modified; private final String _moduleName; - private final static Logger LOG = LogManager.getLogger(ModuleArchive.class); + private final SimpleLogger _log; + private final boolean _hasJavaCode; private String stripToNull(String s) @@ -118,10 +118,12 @@ private String nameFromModuleProperties(InputStream is) throws IOException } - public ModuleArchive(File file) throws IOException + public ModuleArchive(File file, SimpleLogger log) throws IOException { _file = file; assert _file.exists() && _file.isFile(); + _modified = _file.lastModified(); + _log = log; String moduleName = null; boolean hasJavaCode = false; @@ -161,6 +163,7 @@ public ModuleArchive(File file) throws IOException } _moduleName = moduleName; + _hasJavaCode = hasJavaCode; } public File getFile() @@ -178,7 +181,8 @@ public String getModuleName() if (null == _moduleName) { String fileName = getFile().getName(); - return fileName.substring(0, fileName.length() - FILE_EXTENSION.length()); + String baseName = fileName.substring(0, fileName.length() - FILE_EXTENSION.length()); + return baseName; } return _moduleName; } @@ -230,7 +234,7 @@ public void extractAll(File targetDirectory) throws IOException File archiveFile = getFile(); long archiveFileLastModified = archiveFile.lastModified(); - LOG.info("Extracting module " + archiveFile.getName() + "."); + _log.info("Extracting module " + archiveFile.getName() + "."); // delete existing directory so that files that are // no longer in the archive are removed @@ -255,7 +259,7 @@ public void extractAll(File targetDirectory) throws IOException //set last mod on target directory to match module file targetDirectory.setLastModified(archiveFileLastModified); - LOG.info("Done extracting module " + archiveFile.getName() + ". Processed " + fileCount + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); + _log.info("Done extracting module " + archiveFile.getName() + ". Processed " + fileCount + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); } public File extractEntry(JarFile jar, JarEntry entry, File targetDirectory) throws IOException @@ -267,7 +271,7 @@ public File extractEntry(JarFile jar, JarEntry entry, File targetDirectory) thro entryParent.mkdirs(); if (!entryParent.isDirectory()) { - LOG.error("Unable to create directory " + entryParent.getPath() + ", there may be a problem with file permissions"); + _log.error("Unable to create directory " + entryParent.getPath() + ", there may be a problem with file permissions"); } // if entry is a directory, just mkdirs, set last mod and return diff --git a/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java b/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java index 1bd2ccbc55..224f6ed5ec 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java +++ b/server/bootstrap/src/org/labkey/bootstrap/ModuleExtractor.java @@ -16,9 +16,6 @@ package org.labkey.bootstrap; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - import java.io.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -40,12 +37,13 @@ public class ModuleExtractor private Set _ignoredExplodedDirs; private Set _explodedModules; - private static final Logger _log = LogManager.getLogger(ModuleExtractor.class); + private final SimpleLogger _log; - public ModuleExtractor(File webAppDirectory) + public ModuleExtractor(File webAppDirectory, SimpleLogger log) { _webAppDirectory = webAppDirectory; _moduleDirectories = new ModuleDirectories(_webAppDirectory); + _log = log; } public Collection extractModules() @@ -73,11 +71,11 @@ public Collection extractModules() .map(moduleArchiveFile -> { try { - return new ModuleArchive(moduleArchiveFile); + return new ModuleArchive(moduleArchiveFile, _log); } catch (IOException e) { - _log.error("Unable to open module archive {}!", moduleArchiveFile.getPath(), e); + _log.error("Unable to open module archive " + moduleArchiveFile.getPath() + "!", e); _errorArchives.put(moduleArchiveFile, moduleArchiveFile.lastModified()); return null; } @@ -91,8 +89,8 @@ public Collection extractModules() if (null != found) { var re = new IllegalStateException("LabKey found two modules with the name \"" + moduleArchive.getModuleName() + "\". Please resolve this problem and restart the server"); - _log.error("Unable to extract module archive {}!", found.getFile().getPath()); - _log.error("Unable to extract module archive {}", moduleArchive.getFile().getPath(), re); + _log.error("Unable to extract module archive " + found.getFile().getPath() + "!"); + _log.error("Unable to extract module archive " + moduleArchive.getFile().getPath(), re); throw re; } } @@ -109,7 +107,7 @@ public Collection extractModules() } catch (IOException e) { - _log.error("Unable to extract module archive {}!", moduleArchiveFile.getPath(), e); + _log.error("Unable to extract module archive " + moduleArchiveFile.getPath() + "!", e); _errorArchives.put(moduleArchiveFile, moduleArchiveFile.lastModified()); } }); @@ -135,16 +133,16 @@ public Collection extractModules() { ModuleArchive archive = mapModuleDirToArchive.get(dir.getAbsoluteFile()); ExplodedModule explodedModule = new ExplodedModule(dir, null==archive?null:archive.getFile()); - _log.info("Deploying resources from {}.", explodedModule.getRootDirectory()); + _log.info("Deploying resources from " + explodedModule.getRootDirectory() + "."); long startTime = System.currentTimeMillis(); Set moduleWebAppFiles = explodedModule.deployToWebApp(_webAppDirectory); _explodedModules.add(explodedModule); - _log.info("Done deploying resources from {}. Extracted {} file(s) in {}ms.", explodedModule.getRootDirectory(), moduleWebAppFiles.size(), System.currentTimeMillis() - startTime); + _log.info("Done deploying resources from " + explodedModule.getRootDirectory() + ". Extracted " + moduleWebAppFiles.size() + " file(s) in " + (System.currentTimeMillis() - startTime) + "ms."); } catch(IOException e) { - _log.error("Unable to deploy resources from exploded module {} to web app directory!", dir.getPath(), e); + _log.error("Unable to deploy resources from exploded module " + dir.getPath() + " to web app directory!", e); } }); @@ -241,7 +239,7 @@ public boolean areModulesModified(Set previouslyLoggedModules) moduleArchive = null; if (null == moduleArchive) { - moduleArchive = new ModuleArchive(moduleArchiveFile); + moduleArchive = new ModuleArchive(moduleArchiveFile, _log); File explodedDir = moduleArchive.extractAll(); new ExplodedModule(explodedDir).deployToWebApp(_webAppDirectory); _moduleArchiveFiles.put(moduleArchiveFile, moduleArchive); @@ -322,7 +320,7 @@ public boolean hasExplodedArchive(File moduleArchive) */ public Map.Entry extractUpdatedModuleArchive(File moduleArchiveFile, File previousArchiveFile) throws IOException { - ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile); + ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile, _log); File explodedDir = moduleArchive.extractAll(); ExplodedModule explodedModule = new ExplodedModule(explodedDir, moduleArchiveFile); @@ -342,7 +340,7 @@ public Map.Entry extractUpdatedModuleArchive(File moduleArchiveFile, public Map.Entry extractNewModuleArchive(File moduleArchiveFile) throws IOException { - ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile); + ModuleArchive moduleArchive = new ModuleArchive(moduleArchiveFile, _log); File explodedDir = moduleArchive.extractAll(); ExplodedModule explodedModule = new ExplodedModule(explodedDir, moduleArchiveFile); @@ -365,7 +363,7 @@ public static void main(String... args) { PipelineBootstrapConfig config = new PipelineBootstrapConfig(args, false); - ModuleExtractor extractor = new ModuleExtractor(config.getWebappDir()); + ModuleExtractor extractor = new ModuleExtractor(config.getWebappDir(), new StdOutLogger()); extractor.extractModules(); } catch (ConfigException e) diff --git a/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java b/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java index 8c5de38244..a1b198dc8c 100644 --- a/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java +++ b/server/bootstrap/src/org/labkey/bootstrap/PipelineBootstrapConfig.java @@ -177,7 +177,7 @@ private synchronized void init() { if (_classLoader == null) { - ModuleExtractor extractor = new ModuleExtractor(getWebappDir()); + ModuleExtractor extractor = new ModuleExtractor(getWebappDir(), new StdOutLogger()); Collection explodedModules = extractor.extractModules(); _moduleFiles = new ArrayList<>(extractor.getExplodedModuleDirectories()); _moduleSpringContextFiles = new ArrayList<>(); diff --git a/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java b/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java new file mode 100644 index 0000000000..5fd8731f45 --- /dev/null +++ b/server/bootstrap/src/org/labkey/bootstrap/SimpleLogger.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2009-2018 LabKey Corporation + * + * 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.labkey.bootstrap; + +/** + * Simple interface to abstract logger implementations and break dependencies on other JARs when they're not needed. + * User: jeckels + * Date: Mar 11, 2009 + */ +public interface SimpleLogger +{ + void error(Object message, Throwable t); + void error(Object message); + void info(Object message); +} diff --git a/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java b/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java new file mode 100644 index 0000000000..f19442233f --- /dev/null +++ b/server/bootstrap/src/org/labkey/bootstrap/StdOutLogger.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2009 LabKey Corporation + * + * 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.labkey.bootstrap; + +/** + * User: jeckels + * Date: Mar 11, 2009 + */ +public class StdOutLogger implements SimpleLogger +{ + @Override + public void error(Object message, Throwable t) + { + System.err.println(message); + t.printStackTrace(System.err); + } + + @Override + public void error(Object message) + { + System.err.println(message); + } + + @Override + public void info(Object message) + { + System.out.println(message); + } +} diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index d9449c6864..3760a79f52 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.Properties; import java.util.Set; +import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -35,17 +36,17 @@ public class EmbeddedExtractor private String labkeyWebappDirName = null; - public EmbeddedExtractor() + public EmbeddedExtractor(boolean expectEmbeddedInJarName) { File[] files = currentDir.listFiles(file -> { String name = file.getName().toLowerCase(); - return name.endsWith(".jar") && !name.contains("embedded") && !name.contains("labkeybootstrap"); + return name.endsWith(".jar") && !name.contains("labkeybootstrap") && (expectEmbeddedInJarName != name.contains("embedded")); }); if (files == null || files.length == 0) { labkeyServerJar = null; - LOG.debug("Executable jar not found in " + currentDir); + LOG.debug("Executable jar not found in {}", currentDir); } else if (files.length > 1) { @@ -54,7 +55,7 @@ else if (files.length > 1) else { labkeyServerJar = files[0]; - LOG.debug("Executable jar found: " + labkeyServerJar.getAbsolutePath()); + LOG.debug("Executable jar found: {}", labkeyServerJar.getAbsolutePath()); } } @@ -82,7 +83,7 @@ private boolean shouldExtract(File webAppLocation) // Fresh installation or upgrading from a pre-distribution.properties distribution if (!existingDistributionFile.exists()) { - LOG.info("Extracting new LabKey distribution - %s".formatted(incomingDistribution)); + LOG.info("Extracting new LabKey distribution - {}", incomingDistribution); return true; } @@ -102,12 +103,12 @@ private boolean shouldExtract(File webAppLocation) if (!existingDistribution.equals(incomingDistribution)) { - LOG.info("Updating LabKey (%s -> %s)".formatted(existingDistribution, incomingDistribution)); + LOG.info("Updating LabKey ({} -> {}})", existingDistribution, incomingDistribution); return true; } else if (incomingDistribution.buildUrl() == null) { - LOG.info("Extracting custom-build LabKey distribution (%s)".formatted(existingDistribution)); + LOG.info("Extracting custom-build LabKey distribution ({})", existingDistribution); return true; } else @@ -203,7 +204,7 @@ private LabKeyDistributionInfo getFromProperties(InputStream in) throws IOExcept String buildUrl = props.containsKey("buildUrl") ? props.getProperty("buildUrl").trim() : null; var info = new LabKeyDistributionInfo(version, buildUrl, distributionName); - LOG.info("LabKeyDistributionInfo: " + info); + LOG.info("LabKeyDistributionInfo: {}", info); return info; } @@ -218,77 +219,119 @@ public void extractDistribution(File webAppLocation) } } + @SuppressWarnings("unused") /* Called via reflection by PipelineServiceImpl.getClusterStartupArguments() */ + public File extractRemotePipelineJars() + { + return extractExecutableJar(new File("."), false, true); + } + public void extractExecutableJar(File destDirectory, boolean remotePipeline) { + extractExecutableJar(destDirectory, true, remotePipeline); + } + + public File extractExecutableJar(File destDirectory, boolean distribution, boolean remotePipeline) + { + File pipelineLib = null; + if (remotePipeline) + { + pipelineLib = new File(destDirectory, "pipeline-lib"); + if (!pipelineLib.exists()) + { + if (!pipelineLib.mkdirs()) + { + throw new ConfigException("Failed to create directory " + pipelineLib + " Please check file system permissions"); + } + } + } + + boolean foundDistributionZip = false; + File bootstrapJar = null; + File servletApiJar = null; + File log4JCoreJar = null; + File log4JApiJar = null; + try { try (JarFile jar = new JarFile(verifyJar())) { - boolean missingDistributionZip = true; - boolean missingBootstrapJar = remotePipeline; - boolean missingServletApiJar = remotePipeline; var entries = jar.entries(); while (entries.hasMoreElements()) { var entry = entries.nextElement(); var entryName = entry.getName(); - if ("labkey/distribution.zip".equals(entryName)) + if (distribution) { - missingDistributionZip = false; - try (var distInputStream = jar.getInputStream(entry)) + if ("labkey/distribution.zip".equals(entryName)) { - extractDistributionZip(distInputStream, destDirectory); + foundDistributionZip = true; + try (var distInputStream = jar.getInputStream(entry)) + { + extractDistributionZip(distInputStream, destDirectory); + } } } if (remotePipeline) { // Keep this code in sync with org.labkey.pipeline.api.PipelineServiceImpl.extractBootstrapFromEmbedded() - if (entry.getName().contains("labkeyBootstrap") && entry.getName().toLowerCase().endsWith(".jar")) - { - try (var in = jar.getInputStream(entry)) - { - extractFile(in, new File(destDirectory, "labkeyBootstrap.jar")); - } - missingBootstrapJar = false; - } - if (entry.getName().contains("tomcat-embed-core") && entry.getName().toLowerCase().endsWith(".jar")) - { - File pipelineLib = new File(destDirectory, "pipeline-lib"); - if (!pipelineLib.exists()) - { - if (!pipelineLib.mkdirs()) - { - throw new ConfigException("Failed to create directory " + pipelineLib + " Please check file system permissions"); - } - } - try (var in = jar.getInputStream(entry)) - { - extractFile(in, new File(pipelineLib, "servletApi.jar")); - } - missingServletApiJar = false; - } + bootstrapJar = extractIfMatch(bootstrapJar, entry, jar, "labkeyBootstrap", "labkeyBootstrap.jar", destDirectory); + servletApiJar = extractIfMatch(servletApiJar, entry, jar, "tomcat-embed-core", "servletApi.jar", pipelineLib); + log4JCoreJar = extractIfMatch(log4JCoreJar, entry, jar, "log4j-core", "log4j-core.jar", pipelineLib); + log4JApiJar = extractIfMatch(log4JApiJar, entry, jar, "log4j-api", "log4j-api.jar", pipelineLib); } } - if (missingDistributionZip) - { - throw new ConfigException("Unable to find distribution zip required to run LabKey Server."); - } - if (missingBootstrapJar) + if (distribution) { - throw new ConfigException("Unable to find labkeyServer.jar required to run LabKey Server's remote pipeline code."); + if (!foundDistributionZip) + { + throw new ConfigException("Unable to find distribution zip required to run LabKey Server."); + } } - if (missingServletApiJar) + + if (remotePipeline) { - throw new ConfigException("Unable to find Servlet API file required to run LabKey Server's remote pipeline code."); + if (bootstrapJar == null) + { + throw new ConfigException("Unable to find labkeyServer.jar required to run LabKey Server's remote pipeline code."); + } + if (servletApiJar == null) + { + throw new ConfigException("Unable to find Servlet API file required to run LabKey Server's remote pipeline code."); + } + if (log4JCoreJar == null) + { + throw new ConfigException("Unable to find Log4J Core file required to run LabKey Server's remote pipeline code."); + } + if (log4JApiJar == null) + { + throw new ConfigException("Unable to find Log4J API file required to run LabKey Server's remote pipeline code."); + } } } } - catch (IOException | ConfigException e) + catch (IOException e) { throw new RuntimeException(e); } + return bootstrapJar; + } + + private File extractIfMatch(File extractedFile, JarEntry entry, JarFile jar, String originalName, String targetName, File targetDirectory) throws IOException + { + if (extractedFile == null) + { + if (entry.getName().contains(originalName) && entry.getName().toLowerCase().endsWith(".jar")) + { + try (var in = jar.getInputStream(entry)) + { + extractedFile = new File(targetDirectory, targetName); + extractFile(in, extractedFile); + } + } + } + return extractedFile; } private void extractDistributionZip(InputStream zipInputStream, File destDir) throws IOException @@ -365,7 +408,7 @@ private void deleteOldDistribution(File webAppLocation) for (File f : toDelete) { - LOG.debug("Deleting directory from previous LabKey installation: " + f.getAbsolutePath()); + LOG.debug("Deleting directory from previous LabKey installation: {}", f.getAbsolutePath()); FileUtils.forceDelete(f); } } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 569f871a76..3b5239e8fb 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -40,7 +40,7 @@ public static void main(String[] args) if (args.length > 0 && args[0].equalsIgnoreCase("-extract")) { File currentDir = new File("").getAbsoluteFile(); - EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(); + EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(true); embeddedExtractor.extractExecutableJar(currentDir, true); return; } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index d70bb4b16f..a388569166 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -105,7 +105,7 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) webAppLocation = new File(contextProperties.getWebAppLocation()); } - EmbeddedExtractor extractor = new EmbeddedExtractor(); + EmbeddedExtractor extractor = new EmbeddedExtractor(true); if (contextProperties.getWebAppLocation() == null || extractor.foundLabkeyServerJar()) { extractor.extractDistribution(webAppLocation); From 7fb8e2e04fbc6441ac1568a0483cd92ba546aa8b Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Sat, 24 May 2025 13:13:22 -0700 Subject: [PATCH 04/10] Log contents of directory --- server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 3760a79f52..37b136e0f3 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -68,7 +68,7 @@ private File verifyJar() { if (labkeyServerJar == null) { - throw new ConfigException("Executable jar not found in " + currentDir); + throw new ConfigException("Executable jar not found in " + currentDir + " which had contents " + Arrays.stream(currentDir.listFiles()).map(File::getName).toList()); } return labkeyServerJar; From b755c34155d14b305e644043b6501ec495c6769c Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Mon, 26 May 2025 12:38:26 -0700 Subject: [PATCH 05/10] Try being less picky about the JAR name --- .../embedded/src/org/labkey/embedded/EmbeddedExtractor.java | 4 ++-- server/embedded/src/org/labkey/embedded/LabKeyServer.java | 2 +- .../labkey/embedded/LabKeyTomcatServletWebServerFactory.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 37b136e0f3..8de0245b9b 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -36,11 +36,11 @@ public class EmbeddedExtractor private String labkeyWebappDirName = null; - public EmbeddedExtractor(boolean expectEmbeddedInJarName) + public EmbeddedExtractor() { File[] files = currentDir.listFiles(file -> { String name = file.getName().toLowerCase(); - return name.endsWith(".jar") && !name.contains("labkeybootstrap") && (expectEmbeddedInJarName != name.contains("embedded")); + return name.endsWith(".jar") && !name.contains("labkeybootstrap"); }); if (files == null || files.length == 0) diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 3b5239e8fb..569f871a76 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -40,7 +40,7 @@ public static void main(String[] args) if (args.length > 0 && args[0].equalsIgnoreCase("-extract")) { File currentDir = new File("").getAbsoluteFile(); - EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(true); + EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(); embeddedExtractor.extractExecutableJar(currentDir, true); return; } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index a388569166..d70bb4b16f 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -105,7 +105,7 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) webAppLocation = new File(contextProperties.getWebAppLocation()); } - EmbeddedExtractor extractor = new EmbeddedExtractor(true); + EmbeddedExtractor extractor = new EmbeddedExtractor(); if (contextProperties.getWebAppLocation() == null || extractor.foundLabkeyServerJar()) { extractor.extractDistribution(webAppLocation); From 8872eb4251bae1ad24908179f92ce468288626d7 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Tue, 27 May 2025 11:42:25 -0700 Subject: [PATCH 06/10] Another try at finding the right JAR --- .../embedded/src/org/labkey/embedded/EmbeddedExtractor.java | 4 ++-- server/embedded/src/org/labkey/embedded/LabKeyServer.java | 2 +- .../labkey/embedded/LabKeyTomcatServletWebServerFactory.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 8de0245b9b..58521a6ed4 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -36,11 +36,11 @@ public class EmbeddedExtractor private String labkeyWebappDirName = null; - public EmbeddedExtractor() + public EmbeddedExtractor(boolean allowEmbedded) { File[] files = currentDir.listFiles(file -> { String name = file.getName().toLowerCase(); - return name.endsWith(".jar") && !name.contains("labkeybootstrap"); + return name.endsWith(".jar") && !name.contains("labkeybootstrap") && (allowEmbedded || !name.contains("embedded")); }); if (files == null || files.length == 0) diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 569f871a76..d50c840be8 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -40,7 +40,7 @@ public static void main(String[] args) if (args.length > 0 && args[0].equalsIgnoreCase("-extract")) { File currentDir = new File("").getAbsoluteFile(); - EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(); + EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(false); embeddedExtractor.extractExecutableJar(currentDir, true); return; } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index d70bb4b16f..c62ecce041 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -105,7 +105,7 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) webAppLocation = new File(contextProperties.getWebAppLocation()); } - EmbeddedExtractor extractor = new EmbeddedExtractor(); + EmbeddedExtractor extractor = new EmbeddedExtractor(false); if (contextProperties.getWebAppLocation() == null || extractor.foundLabkeyServerJar()) { extractor.extractDistribution(webAppLocation); From 00d80970bae1d75a7fd6b1113aaf3f2abacf3a7e Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Thu, 29 May 2025 16:18:45 -0700 Subject: [PATCH 07/10] Consolidate plugin implementations; hacks to cross classloader boundaries --- .../labkey/embedded/LabKeyDeleteAction.java | 27 +--- .../embedded/LabKeySpringBootClassLoader.java | 24 ++- .../org/labkey/embedded/SessionAppender.java | 150 ++---------------- 3 files changed, 41 insertions(+), 160 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeyDeleteAction.java b/server/embedded/src/org/labkey/embedded/LabKeyDeleteAction.java index 8988e4bccf..ed2e503b13 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyDeleteAction.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyDeleteAction.java @@ -48,13 +48,10 @@ /** * A modified version of log4j2 DeleteAction that doesn't require any conditions; files to delete are determined * by Java code. - * - * + *

* We will retain up to three error log files of 100 MB (as configured in log4j2.xml). We'll keep the first log from a given * webapp startup, moving it to labkey-errors-yyyy-MM-dd.log for archive purposes. - * - * Keep in sync with org.labkey.api.util.LabKeyDeleteAction until we're embedded-only and can consolidate. - * + *

* See issue 43686. */ @Plugin(name = "LabKeyDelete", category = Core.CATEGORY_NAME, printObject = true) @@ -111,21 +108,15 @@ public class LabKeyDeleteAction extends AbstractPathAction * @see org.apache.logging.log4j.core.appender.rolling.action.AbstractPathAction#execute() */ @Override - public boolean execute() throws IOException { - return executeDelete(); - } - - private boolean executeDelete() throws IOException { + public boolean execute() throws IOException + { final List selectedForDeletion = selectFiles(); - if (selectedForDeletion == null) { - LOGGER.trace("Null list returned (no files to delete)"); - return true; - } deleteSelectedFiles(selectedForDeletion); return true; } - private List selectFiles() throws IOException { + private List selectFiles() throws IOException + { final List sortedPaths = getSortedPaths(); trace("Sorted paths:", sortedPaths); return selectFilesToDelete(getBasePath(), sortedPaths); @@ -234,18 +225,14 @@ private void trace(final String label, final List sortedPath * Returns a sorted list of all files up to maxDepth under the basePath. * * @return a sorted list of files - * @throws IOException */ List getSortedPaths() throws IOException { final SortingVisitor sort = new SortingVisitor(pathSorter); super.execute(sort); - final List sortedPaths = sort.getSortedPaths(); - return sortedPaths; + return sort.getSortedPaths(); } /** - * Returns {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise. - * * @return {@code true} if files are not deleted even when all conditions accept a path, {@code false} otherwise */ public boolean isTestMode() { diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index 191e8e8a16..af769b2621 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -54,12 +54,34 @@ public LabKeySpringBootClassLoader(ClassLoader parent) } } + @Override + public Class loadClass(String name, boolean resolve) throws ClassNotFoundException + { + // Hack to make us only load one copy of SessionAppender. It loads via a parent classloader early + // during Log4J initialization, and if we load a second copy the log events aren't captured in a place + // where we can expose them to users in the Server JavaScript Console. + if (name.equals("org.labkey.embedded.SessionAppender")) + { + ClassLoader parent = getParent(); + while (parent != null) + { + if (parent.getClass().getName().equals("jdk.internal.loader.ClassLoaders$AppClassLoader")) + { + return getParent().getParent().loadClass(name); + } + parent = parent.getParent(); + } + LOG.error("Failed to find AppClassLoader. The Server JavaScript Console will not be available"); + } + return super.loadClass(name, resolve); + } + @Override protected boolean filter(String name, boolean isClassName) { // Defer to the Spring Boot classloader for SLF4J and Log4J classes to avoid problems with double-loading. // Eventually we should shift to only shipping SLF4J and Log4J via Spring Boot and not inside the webapp. - if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j")) + if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j.") || name.startsWith("org.labkey.embedded.")) { return true; } diff --git a/server/embedded/src/org/labkey/embedded/SessionAppender.java b/server/embedded/src/org/labkey/embedded/SessionAppender.java index ebcabefeb5..70d1b7720c 100644 --- a/server/embedded/src/org/labkey/embedded/SessionAppender.java +++ b/server/embedded/src/org/labkey/embedded/SessionAppender.java @@ -26,19 +26,16 @@ import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.springframework.util.ConcurrentReferenceHashMap; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.function.Consumer; +/** + * As configured in log4j2.xml, captures logging from server-side JavaScript to make it available through the + * web UI's Server JavaScript Console. Since Log4J is initialized in the outer Spring Boot code, this loads early and + * separate from the webapp. Thus, it lets the webapp register a consumer for the LogEvents, which this class + * forwards along. + */ @Plugin(name = "SessionAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) public class SessionAppender extends AbstractAppender { @@ -55,140 +52,15 @@ protected SessionAppender(String name, Filter filter, Layout eventIdMap = Collections.synchronizedMap(new LinkedHashMap<>() - { - // Safeguard against runaway size. - @Override - protected boolean removeEldestEntry(Map.Entry eldest) - { - return size() > 1000; - } - }); - int eventId=0; - } - - private static final ThreadLocal localInfo = new ThreadLocal<>(); - - // AppenderInfos are thread-local variables initialized with the user session. This Map allows background threads to share an - // active session's appenderInfo to output logs to that session's SessionAppender. When the session is ended, the - // thread-local appenderInfo will be released and this map, which uses weak references, will allow gc to remove and - // reclaim the appenderInfo entry. - private static final Map appenderInfos = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); - - // Makes appenderInfo available outside this thread - private static void registerAppenderInfo(AppenderInfo info) - { - appenderInfos.put(info.key, info); - } - - // If accessing appenderInfo using this function ensure operations on the appenderInfo are thread safe as multiple - // threads could be accessing it. - public static AppenderInfo getAppenderInfoByKey(String key) - { - return appenderInfos.get(key); - } - - public static String getAppendingInfoKey(HttpServletRequest request) - { - return _getLoggingForSession(request).key; - } + /** Set via reflection in org.labkey.api.util.SessionAppender */ + public static Consumer _consumer; @Override public void append(LogEvent event) { - AppenderInfo info = localInfo.get(); - if (null == info || !info.on) - return; - info.eventIdMap.put(event, ++info.eventId); - } - - - /** - * @return serialization-suitable list of events with eventId, level, message, and timestamp properties - */ - public static List> getLoggingEvents(HttpServletRequest request, Integer maxEventId) - { - AppenderInfo info = _getLoggingForSession(request); - if (null == info) - return Collections.emptyList(); - - // Lock the map to avoid concurrent modifications while we iterate - synchronized (info.eventIdMap) - { - List> result = new ArrayList<>(info.eventIdMap.size()); - for (Map.Entry entry : info.eventIdMap.entrySet()) - { - if (maxEventId == null || maxEventId < entry.getValue().intValue()) - { - LogEvent e = entry.getKey(); - Map m = new HashMap<>(); - // We've historically returned these as strings - m.put("eventId", Integer.toString(entry.getValue())); - m.put("level", e.getLevel().toString()); - m.put("message", e.getMessage().getFormattedMessage()); - m.put("timestamp", new Date(e.getTimeMillis())); - result.add(m); - } - } - - return result; - } - } - - public static void setLoggingForSession(HttpServletRequest request, boolean on) - { - AppenderInfo info = _getLoggingForSession(request); - if (null != info) - info.on = on; - } - - public static boolean isLogging(HttpServletRequest request) - { - AppenderInfo info = _getLoggingForSession(request); - return null != info && info.on; - } - - private static AppenderInfo _getLoggingForSession(HttpServletRequest request) - { - HttpSession session = request.getSession(true); - if (null == session) - return null; + if (_consumer != null) { - AppenderInfo info = (AppenderInfo)session.getAttribute("SessionAppender#info"); - if (null == info) - { - info = new AppenderInfo(session.getId(), false); - session.setAttribute("SessionAppender#info",info); - } - registerAppenderInfo(info); - return info; + _consumer.accept(event); } } - - public static void removeAppenderInfo() - { - localInfo.remove(); - } - - // set up logging for this thread, based on an already existing AppenderInfo - public static void initThread(AppenderInfo info) - { - localInfo.set(info); - } - - // set up logging for this thread, based on session settings - public static void initThread(HttpServletRequest request) - { - localInfo.set(_getLoggingForSession(request)); - } } From 2d66f26b2d9d9c5182dc5244cc0d541202fef59f Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 30 May 2025 10:17:35 -0700 Subject: [PATCH 08/10] Troubleshoot problems finding SessionAppender --- .../src/org/labkey/embedded/LabKeySpringBootClassLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index af769b2621..f6dbf32efb 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -65,6 +65,7 @@ public Class loadClass(String name, boolean resolve) throws ClassNotFoundExce ClassLoader parent = getParent(); while (parent != null) { + LOG.info("Looking for SessionAppending - checking ClassLoader " + parent); if (parent.getClass().getName().equals("jdk.internal.loader.ClassLoaders$AppClassLoader")) { return getParent().getParent().loadClass(name); @@ -81,7 +82,7 @@ protected boolean filter(String name, boolean isClassName) { // Defer to the Spring Boot classloader for SLF4J and Log4J classes to avoid problems with double-loading. // Eventually we should shift to only shipping SLF4J and Log4J via Spring Boot and not inside the webapp. - if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j.") || name.startsWith("org.labkey.embedded.")) + if (name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j.")) { return true; } From 63b74ce53e499b08ee6d5dcd3e98016337de49b7 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Fri, 30 May 2025 15:03:14 -0700 Subject: [PATCH 09/10] Troubleshoot problems finding SessionAppender --- .../src/org/labkey/embedded/LabKeySpringBootClassLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index f6dbf32efb..b65ab1d08f 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -66,7 +66,8 @@ public Class loadClass(String name, boolean resolve) throws ClassNotFoundExce while (parent != null) { LOG.info("Looking for SessionAppending - checking ClassLoader " + parent); - if (parent.getClass().getName().equals("jdk.internal.loader.ClassLoaders$AppClassLoader")) + if (parent.getClass().getName().equals("jdk.internal.loader.ClassLoaders$AppClassLoader") || + parent.getClass().getName().equals("org.springframework.boot.loader.launch.LaunchedClassLoader")) { return getParent().getParent().loadClass(name); } From 0387152a4d6e8a2bed852b7fbf8c86df8af4bea9 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Mon, 2 Jun 2025 13:56:22 -0700 Subject: [PATCH 10/10] Fix Log4J initialization without Spring Boot dev tools --- .../src/org/labkey/embedded/LabKeySpringBootClassLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java index b65ab1d08f..7c3bb05f4f 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java +++ b/server/embedded/src/org/labkey/embedded/LabKeySpringBootClassLoader.java @@ -65,11 +65,11 @@ public Class loadClass(String name, boolean resolve) throws ClassNotFoundExce ClassLoader parent = getParent(); while (parent != null) { - LOG.info("Looking for SessionAppending - checking ClassLoader " + parent); + LOG.debug("Looking for SessionAppending - checking ClassLoader " + parent); if (parent.getClass().getName().equals("jdk.internal.loader.ClassLoaders$AppClassLoader") || parent.getClass().getName().equals("org.springframework.boot.loader.launch.LaunchedClassLoader")) { - return getParent().getParent().loadClass(name); + return parent.loadClass(name); } parent = parent.getParent(); }