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]

Reply via email to