This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 6285f1b33c1 [fix](jni) fix JNI logging by migrating to log4j2 with
proper configuration (#60584)
6285f1b33c1 is described below
commit 6285f1b33c1711d5e54e9bcdbdb27156050c09f1
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Feb 9 09:51:52 2026 +0800
[fix](jni) fix JNI logging by migrating to log4j2 with proper configuration
(#60584)
The BE's jni.log was not being written to because the JNI scanner module
was using log4j 1.x APIs which were not properly configured in the JNI
classloader environment.
Changes:
- Migrate ScannerLoader from log4j 1.x to log4j2 API
- Add log4j2.xml configuration with RollingFile appender for jni.log
- Initialize log4j2 explicitly in ScannerLoader's static block to ensure
logging works in JNI environment
- Trigger log4j2 initialization from TrinoConnectorPluginLoader before
plugin loading
- Improve error messages in TrinoConnectorCache by including root cause
via ExceptionUtils.getRootCauseMessage()
- Preserve original exception as cause in invalidateTableCache()
- Use parameterized logging consistently instead of string concatenation
- Remove unused Log4jOutputStream class and no-op
redirectStdStreamsToLog4j()
---
.../doris/common/classloader/ScannerLoader.java | 68 ++++++++++++++++------
.../doris/common/jni/utils/Log4jOutputStream.java | 44 --------------
.../java-common/src/main/resources/log4j2.xml | 63 ++++++++++++++++++++
.../doris/trinoconnector/TrinoConnectorCache.java | 15 +++--
.../trinoconnector/TrinoConnectorPluginLoader.java | 29 +++++++--
5 files changed, 147 insertions(+), 72 deletions(-)
diff --git
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/classloader/ScannerLoader.java
b/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/classloader/ScannerLoader.java
index bcfa0d17985..3de8dcc70ee 100644
---
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/classloader/ScannerLoader.java
+++
b/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/classloader/ScannerLoader.java
@@ -18,16 +18,14 @@
package org.apache.doris.common.classloader;
import org.apache.doris.common.jni.utils.ExpiringMap;
-import org.apache.doris.common.jni.utils.Log4jOutputStream;
import org.apache.doris.common.jni.utils.UdfClassCache;
import com.google.common.collect.Streams;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
-import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -47,7 +45,48 @@ import java.util.stream.Collectors;
* BE will load scanners by JNI call, and then the JniConnector on BE will get
scanner class by getLoadedClass.
*/
public class ScannerLoader {
- public static final Logger LOG = Logger.getLogger(ScannerLoader.class);
+ static {
+ // Explicitly initialize log4j2 to ensure logging works in JNI
environment
+ try {
+ // Set logPath system property if not already set or normalize it
+ String logPath = System.getProperty("logPath");
+ if (logPath == null || logPath.isEmpty()) {
+ String dorisHome = System.getenv("DORIS_HOME");
+ if (dorisHome != null) {
+ logPath = dorisHome + "/log/jni.log";
+ }
+ }
+ // Normalize path to remove double slashes
+ if (logPath != null) {
+ logPath = logPath.replaceAll("//+", "/");
+ System.setProperty("logPath", logPath);
+ }
+
+ // Point log4j2 to our configuration file in classpath
+ System.setProperty("log4j2.configurationFile", "log4j2.xml");
+
+ // Disable log4j2's shutdown hook to prevent premature shutdown in
JNI environment
+ System.setProperty("log4j.shutdownHookEnabled", "false");
+
+ // Force log4j2 to reconfigure with our settings
+ org.apache.logging.log4j.core.LoggerContext ctx =
+ (org.apache.logging.log4j.core.LoggerContext)
LogManager.getContext(false);
+ ctx.reconfigure();
+
+ // Log initialization success
+ Logger logger = LogManager.getLogger(ScannerLoader.class);
+ logger.info("Log4j2 initialized successfully. Log file: {}",
logPath);
+
+ // Test SLF4J bridge
+ org.slf4j.Logger slf4jLogger =
org.slf4j.LoggerFactory.getLogger(ScannerLoader.class);
+ slf4jLogger.info("SLF4J bridge to log4j2 verified successfully");
+ } catch (Exception e) {
+ System.err.println("Failed to initialize log4j2: " +
e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ public static final Logger LOG = LogManager.getLogger(ScannerLoader.class);
private static final Map<String, Class<?>> loadedClasses = new HashMap<>();
private static final ExpiringMap<String, UdfClassCache> udfLoadedClasses =
new ExpiringMap<>();
private static final String CLASS_SUFFIX = ".class";
@@ -57,27 +96,20 @@ public class ScannerLoader {
* Load all classes from $DORIS_HOME/lib/java_extensions/*
*/
public void loadAllScannerJars() {
- redirectStdStreamsToLog4j();
+ LOG.info("Starting to load scanner JARs from
$DORIS_HOME/lib/java_extensions/");
String basePath = System.getenv("DORIS_HOME");
File library = new File(basePath, "/lib/java_extensions/");
+ LOG.info("Scanner library path: {}", library.getAbsolutePath());
// TODO: add thread pool to load each scanner
listFiles(library).stream().filter(File::isDirectory).forEach(sd -> {
+ LOG.info("Loading scanner from directory: {}", sd.getName());
JniScannerClassLoader classLoader = new
JniScannerClassLoader(sd.getName(), buildClassPath(sd),
this.getClass().getClassLoader());
try (ThreadClassLoaderContext ignored = new
ThreadClassLoaderContext(classLoader)) {
loadJarClassFromDir(sd, classLoader);
}
});
- }
-
- private void redirectStdStreamsToLog4j() {
- Logger outLogger = Logger.getLogger("stdout");
- PrintStream logPrintStream = new PrintStream(new
Log4jOutputStream(outLogger, Level.INFO));
- System.setOut(logPrintStream);
-
- Logger errLogger = Logger.getLogger("stderr");
- PrintStream errorPrintStream = new PrintStream(new
Log4jOutputStream(errLogger, Level.ERROR));
- System.setErr(errorPrintStream);
+ LOG.info("Finished loading scanner JARs");
}
public static UdfClassCache getUdfClassLoader(String functionSignature) {
@@ -86,12 +118,12 @@ public class ScannerLoader {
public static synchronized void cacheClassLoader(String functionSignature,
UdfClassCache classCache,
long expirationTime) {
- LOG.info("Cache UDF for: " + functionSignature);
+ LOG.info("Cache UDF for: {}", functionSignature);
udfLoadedClasses.put(functionSignature, classCache, expirationTime *
60 * 1000L);
}
public synchronized void cleanUdfClassLoader(String functionSignature) {
- LOG.info("cleanUdfClassLoader for: " + functionSignature);
+ LOG.info("cleanUdfClassLoader for: {}", functionSignature);
udfLoadedClasses.remove(functionSignature);
}
diff --git
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/Log4jOutputStream.java
b/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/Log4jOutputStream.java
deleted file mode 100644
index bb4e4281ee1..00000000000
---
a/fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/utils/Log4jOutputStream.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// 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.doris.common.jni.utils;
-
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
-
-import java.io.OutputStream;
-
-public class Log4jOutputStream extends OutputStream {
- private final Logger logger;
- private final StringBuilder buffer = new StringBuilder();
- private final Level level;
-
- public Log4jOutputStream(Logger logger, Level level) {
- this.logger = logger;
- this.level = level;
- }
-
- @Override
- public void write(int b) {
- if (b == '\n') {
- logger.log(level, buffer.toString());
- buffer.setLength(0);
- } else {
- buffer.append((char) b);
- }
- }
-}
diff --git a/fe/be-java-extensions/java-common/src/main/resources/log4j2.xml
b/fe/be-java-extensions/java-common/src/main/resources/log4j2.xml
new file mode 100644
index 00000000000..b3624530420
--- /dev/null
+++ b/fe/be-java-extensions/java-common/src/main/resources/log4j2.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<Configuration status="WARN" name="JNIScannerConfig">
+ <Properties>
+ <Property
name="logPath">${sys:logPath:-${env:DORIS_HOME}/log/jni.log}</Property>
+ </Properties>
+
+ <Appenders>
+ <Console name="Console" target="SYSTEM_ERR">
+ <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level [%t]
%c{1.}:%L - %msg%n"/>
+ </Console>
+
+ <RollingFile name="File" fileName="${logPath}"
+ filePattern="${logPath}.%i">
+ <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5level [%t]
%c{1.}:%L - %msg%n"/>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="100MB"/>
+ </Policies>
+ <DefaultRolloverStrategy max="10"/>
+ </RollingFile>
+ </Appenders>
+
+ <Loggers>
+ <!-- Specific loggers for different components -->
+ <Logger name="org.apache.doris.trinoconnector" level="INFO"
additivity="false">
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="Console"/>
+ </Logger>
+
+ <Logger name="org.apache.doris.jdbc" level="INFO" additivity="false">
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="Console"/>
+ </Logger>
+
+ <Logger name="org.apache.doris.common.classloader" level="INFO"
additivity="false">
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="Console"/>
+ </Logger>
+
+ <!-- Root logger catches everything else -->
+ <Root level="INFO">
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git
a/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorCache.java
b/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorCache.java
index e9d92a98f78..7a42bc504b7 100644
---
a/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorCache.java
+++
b/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorCache.java
@@ -52,6 +52,7 @@ import io.trino.testing.TestingAccessControlManager;
import io.trino.transaction.NoOpTransactionManager;
import io.trino.type.InternalTypeManager;
import io.trino.util.EmbedVersion;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -94,10 +95,12 @@ public class TrinoConnectorCache {
public static TrinoConnectorCacheValue getConnector(TrinoConnectorCacheKey
key) {
try {
- LOG.info("Connector cache size is : " + connectorCache.size());
+ LOG.info("Connector cache size is : {}", connectorCache.size());
return connectorCache.get(key);
} catch (Exception e) {
- throw new RuntimeException("failed to get connector for:" + key);
+ LOG.warn("failed to get connector for: " + key + ": " +
ExceptionUtils.getRootCauseMessage(e), e);
+ throw new RuntimeException("failed to get connector for: " + key +
": "
+ + ExceptionUtils.getRootCauseMessage(e), e);
}
}
@@ -142,8 +145,8 @@ public class TrinoConnectorCache {
return new TrinoConnectorCacheValue(catalogHandle, connector,
handleResolver, trinoConnectorServicesProvider);
} catch (Exception e) {
- LOG.warn("failed to create trino connector", e);
- throw new RuntimeException(e);
+ LOG.warn("failed to create trino connector: " +
ExceptionUtils.getRootCauseMessage(e), e);
+ throw new RuntimeException("failed to create trino connector: " +
ExceptionUtils.getRootCauseMessage(e), e);
}
}
@@ -159,7 +162,9 @@ public class TrinoConnectorCache {
}
}
} catch (Exception e) {
- throw new RuntimeException("failed to invalidate connector for: "
+ key);
+ LOG.warn("failed to invalidate connector for: " + key + ": " +
ExceptionUtils.getRootCauseMessage(e), e);
+ throw new RuntimeException("failed to invalidate connector for: "
+ key
+ + ": " + ExceptionUtils.getRootCauseMessage(e), e);
}
}
diff --git
a/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorPluginLoader.java
b/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorPluginLoader.java
index 842c4c744ca..2347bd52c16 100644
---
a/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorPluginLoader.java
+++
b/fe/be-java-extensions/trino-connector-scanner/src/main/java/org/apache/doris/trinoconnector/TrinoConnectorPluginLoader.java
@@ -53,6 +53,9 @@ public class TrinoConnectorPluginLoader {
static {
try {
+ // Initialize log4j2 configuration FIRST to ensure all logging
works
+ initializeLog4j2Configuration();
+
// Allow self-attachment for Java agents,this is required for
certain debugging and monitoring functions
System.setProperty("jdk.attach.allowAttachSelf", "true");
// Get the operating system name
@@ -61,19 +64,23 @@ public class TrinoConnectorPluginLoader {
if (osName.contains("mac") || osName.contains("darwin")) {
System.setProperty("jol.skipHotspotSAAttach", "true");
}
- // Trino uses jul as its own log system, so the attributes of
JUL are configured here
+
+ // Configure JUL for Trino's internal logging (trino itself
uses JUL)
+ // This is separate from our log4j2 configuration
System.setProperty("java.util.logging.SimpleFormatter.format",
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s:
%5$s%6$s%n");
- java.util.logging.Logger logger =
java.util.logging.Logger.getLogger("");
- logger.setUseParentHandlers(false);
- Arrays.stream(logger.getHandlers())
+ java.util.logging.Logger julLogger =
java.util.logging.Logger.getLogger("");
+ julLogger.setUseParentHandlers(false);
+ Arrays.stream(julLogger.getHandlers())
.filter(handler -> handler instanceof ConsoleHandler)
.forEach(handler -> handler.setLevel(Level.OFF));
FileHandler fileHandler = new
FileHandler(EnvUtils.getDorisHome() + "/log/trinoconnector%g.log",
500000000, 10, true);
fileHandler.setLevel(Level.INFO);
fileHandler.setFormatter(new SimpleFormatter());
- logger.addHandler(fileHandler);
+ julLogger.addHandler(fileHandler);
+
+ LOG.info("TrinoConnectorPluginLoader starting to load
plugins...");
TypeOperators typeOperators = new TypeOperators();
featuresConfig = new FeaturesConfig();
@@ -87,11 +94,23 @@ public class TrinoConnectorPluginLoader {
trinoConnectorPluginManager = new
TrinoConnectorPluginManager(serverPluginsProvider,
typeRegistry, handleResolver);
trinoConnectorPluginManager.loadPlugins();
+
+ LOG.info("TrinoConnectorPluginLoader successfully loaded
plugins from: " + checkAndReturnPluginDir());
} catch (Exception e) {
LOG.warn("Failed load trino-connector plugins from " +
checkAndReturnPluginDir()
+ ", Exception:" + e.getMessage(), e);
}
}
+
+ private static void initializeLog4j2Configuration() {
+ // Ensure log4j2 is configured before any logging happens
+ // by forcing ScannerLoader class to load (triggers its static
block)
+ try {
+
Class.forName("org.apache.doris.common.classloader.ScannerLoader");
+ } catch (ClassNotFoundException e) {
+ System.err.println("Failed to initialize log4j2: " +
e.getMessage());
+ }
+ }
}
// called by c++
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]