This is an automated email from the ASF dual-hosted git repository.

merlimat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 908e88f0e9 Log only the bookie settings that differ from the built-in 
defaults (#4892)
908e88f0e9 is described below

commit 908e88f0e9f79a23f11d3a4d73a18148e59226e4
Author: Matteo Merli <[email protected]>
AuthorDate: Sat Oct 17 05:34:46 2026 -0700

    Log only the bookie settings that differ from the built-in defaults (#4892)
---
 .../org/apache/bookkeeper/proto/BookieServer.java  | 108 +++++++++++++++++---
 .../proto/BookieServerOverriddenConfigTest.java    | 110 +++++++++++++++++++++
 2 files changed, 204 insertions(+), 14 deletions(-)

diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
index 2fa5e90e0b..b835798eef 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/proto/BookieServer.java
@@ -27,11 +27,18 @@ import com.google.common.annotations.VisibleForTesting;
 import io.netty.buffer.ByteBufAllocator;
 import java.io.IOException;
 import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.net.UnknownHostException;
 import java.util.Arrays;
-import java.util.Iterator;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.regex.Pattern;
 import lombok.CustomLog;
 import org.apache.bookkeeper.bookie.Bookie;
 import org.apache.bookkeeper.bookie.BookieCriticalThread;
@@ -39,6 +46,7 @@ import org.apache.bookkeeper.bookie.BookieException;
 import org.apache.bookkeeper.bookie.BookieImpl;
 import org.apache.bookkeeper.bookie.ExitCode;
 import org.apache.bookkeeper.bookie.UncleanShutdownDetection;
+import org.apache.bookkeeper.conf.AbstractConfiguration;
 import org.apache.bookkeeper.conf.ServerConfiguration;
 import org.apache.bookkeeper.net.BookieId;
 import org.apache.bookkeeper.net.BookieSocketAddress;
@@ -76,6 +84,9 @@ public class BookieServer {
     // Exception handler
     private volatile UncaughtExceptionHandler uncaughtExceptionHandler = null;
 
+    // configuration keys whose values must not be logged
+    private static final Pattern SENSITIVE_KEY = 
Pattern.compile("password|secret|token", Pattern.CASE_INSENSITIVE);
+
     public BookieServer(ServerConfiguration conf,
                         Bookie bookie,
                         StatsLogger statsLogger,
@@ -220,28 +231,97 @@ public class BookieServer {
     }
 
     /**
-     * Returns the configuration entries that differ from the {@link 
ServerConfiguration}
-     * defaults — i.e. values explicitly set by the user (via config file or 
programmatically)
-     * whose value is not the same as the built-in default.
+     * Returns the entries of {@code conf} whose value differs from the 
built-in default, keyed by
+     * configuration property name.
+     *
+     * <p>The defaults are not stored in the configuration but encoded in the 
{@link ServerConfiguration}
+     * getters, so every getter is invoked on both {@code conf} and an empty 
configuration, recording the
+     * keys it reads. When the two results differ, the recorded keys that are 
explicitly set in {@code conf}
+     * are reported, with the value masked when the key looks like a secret. 
Keys that no getter reads
+     * (settings of other components, or unrelated entries loaded from a 
shared properties file) are not
+     * reported.
+     *
+     * <p>Since every public {@code get*}/{@code is*} getter of {@link 
ServerConfiguration} is invoked
+     * reflectively here on each bookie start, those getters must remain 
side-effect free and cheap.
      */
-    private static Map<String, Object> overriddenConfig(ServerConfiguration 
conf) {
-        ServerConfiguration defaults = new ServerConfiguration();
+    @VisibleForTesting
+    static Map<String, Object> overriddenConfig(ServerConfiguration conf) {
+        KeyRecordingConfiguration actual = new KeyRecordingConfiguration(conf);
+        KeyRecordingConfiguration defaults = new KeyRecordingConfiguration(new 
ServerConfiguration());
         Map<String, Object> overrides = new TreeMap<>();
-        Iterator<String> keys = conf.getInMemoryConfiguration().getKeys();
-        while (keys.hasNext()) {
-            String key = keys.next();
-            Object value = conf.getProperty(key);
-            if (value == null) {
+        for (Method getter : ServerConfiguration.class.getMethods()) {
+            if (!isGetter(getter)) {
                 continue;
             }
-            Object defaultValue = defaults.getProperty(key);
-            if (defaultValue == null || 
!value.toString().equals(defaultValue.toString())) {
-                overrides.put(key, value);
+            actual.keys.clear();
+            defaults.keys.clear();
+            if (Objects.deepEquals(invoke(getter, actual), invoke(getter, 
defaults))) {
+                continue;
+            }
+            Set<String> keys = new HashSet<>(actual.keys);
+            keys.addAll(defaults.keys);
+            for (String key : keys) {
+                if (conf.containsKey(key)) {
+                    overrides.put(key, SENSITIVE_KEY.matcher(key).find() ? 
"******" : conf.getProperty(key));
+                }
             }
         }
         return overrides;
     }
 
+    private static boolean isGetter(Method method) {
+        return method.getParameterCount() == 0
+                && !Modifier.isStatic(method.getModifiers())
+                && 
AbstractConfiguration.class.isAssignableFrom(method.getDeclaringClass())
+                && (method.getName().startsWith("get") || 
method.getName().startsWith("is"));
+    }
+
+    private static Object invoke(Method getter, ServerConfiguration conf) {
+        try {
+            return getter.invoke(conf);
+        } catch (InvocationTargetException e) {
+            // a getter rejecting the configured value still means the value 
differs from the default
+            return e.getCause().getClass();
+        } catch (IllegalAccessException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * A {@link ServerConfiguration} that reads its properties from another 
configuration and records the
+     * keys that have been looked up.
+     *
+     * <p>Besides the two internal hooks, {@code getList} must be overridden 
because
+     * {@code CompositeConfiguration} implements it (and {@code 
getStringArray}) by reading its child
+     * configurations directly.
+     */
+    private static class KeyRecordingConfiguration extends ServerConfiguration 
{
+        private final ServerConfiguration source;
+        private final Set<String> keys = new HashSet<>();
+
+        KeyRecordingConfiguration(ServerConfiguration source) {
+            this.source = source;
+        }
+
+        @Override
+        protected Object getPropertyInternal(String key) {
+            keys.add(key);
+            return source.getProperty(key);
+        }
+
+        @Override
+        protected boolean containsKeyInternal(String key) {
+            keys.add(key);
+            return source.containsKey(key);
+        }
+
+        @Override
+        public List<Object> getList(String key, List<?> defaultValue) {
+            keys.add(key);
+            return source.getList(key, defaultValue);
+        }
+    }
+
 
     public boolean isRunning() {
         return bookie.isRunning() && nettyServer.isRunning() && running;
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/proto/BookieServerOverriddenConfigTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/proto/BookieServerOverriddenConfigTest.java
new file mode 100644
index 0000000000..bb6864e1d6
--- /dev/null
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/proto/BookieServerOverriddenConfigTest.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * 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.bookkeeper.proto;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.Set;
+import org.apache.bookkeeper.conf.ServerConfiguration;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link BookieServer#overriddenConfig(ServerConfiguration)}.
+ */
+public class BookieServerOverriddenConfigTest {
+
+    @Test
+    public void testEmptyConfiguration() {
+        assertTrue(BookieServer.overriddenConfig(new 
ServerConfiguration()).isEmpty());
+    }
+
+    @Test
+    public void testValuesEqualToDefaultsAreNotReported() {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setBookiePort(3181);
+        conf.setJournalSyncData(true);
+        conf.setGcWaitTime(600000);
+        conf.setJournalDirName("/tmp/bk-txn");
+
+        assertTrue(BookieServer.overriddenConfig(conf).isEmpty());
+    }
+
+    @Test
+    public void testOverriddenValuesAreReported() {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setBookiePort(3181);
+        conf.setJournalSyncData(false);
+        conf.setGcWaitTime(300000);
+        conf.setLedgerDirNames(new String[] { "/data/ledgers1", 
"/data/ledgers2" });
+
+        Map<String, Object> overrides = BookieServer.overriddenConfig(conf);
+
+        assertEquals(Set.of("journalSyncData", "gcWaitTime", 
"ledgerDirectories"), overrides.keySet());
+        assertEquals("false", 
String.valueOf(overrides.get("journalSyncData")));
+        assertEquals("300000", String.valueOf(overrides.get("gcWaitTime")));
+        assertEquals("[/data/ledgers1, /data/ledgers2]", 
String.valueOf(overrides.get("ledgerDirectories")));
+    }
+
+    @Test
+    public void testOnlyKeysActuallySetAreReported() {
+        ServerConfiguration conf = new ServerConfiguration();
+        // getJournalDirNames() looks up journalDirectories first and falls 
back to journalDirectory
+        conf.setProperty("journalDirectory", "/data/journal");
+
+        Map<String, Object> overrides = BookieServer.overriddenConfig(conf);
+
+        assertEquals(Set.of("journalDirectory"), overrides.keySet());
+        assertEquals("/data/journal", overrides.get("journalDirectory"));
+    }
+
+    @Test
+    public void testUnknownKeysAreNotReported() {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setProperty("brokerServicePort", "6650");
+
+        assertTrue(BookieServer.overriddenConfig(conf).isEmpty());
+    }
+
+    @Test
+    public void testSensitiveValuesAreMasked() {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setHttpServerKeyStorePassword("hunter2");
+
+        Map<String, Object> overrides = BookieServer.overriddenConfig(conf);
+
+        assertEquals("******", overrides.get("httpServerKeyStorePassword"));
+        assertFalse(overrides.toString().contains("hunter2"));
+    }
+
+    @Test
+    public void testValueRejectedByGetterIsReported() {
+        ServerConfiguration conf = new ServerConfiguration();
+        conf.setProperty("ledgerManagerFactoryClass", "does.not.Exist");
+
+        Map<String, Object> overrides = BookieServer.overriddenConfig(conf);
+
+        assertEquals("does.not.Exist", 
overrides.get("ledgerManagerFactoryClass"));
+    }
+}

Reply via email to