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

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


The following commit(s) were added to refs/heads/master by this push:
     new 1f28d20  Add logging module to load log configuration dynamically 
(#7377)
1f28d20 is described below

commit 1f28d2036a0581d6d891933a9cc1933c3990eea4
Author: Gao Hongtao <[email protected]>
AuthorDate: Thu Jul 29 19:55:22 2021 +0800

    Add logging module to load log configuration dynamically (#7377)
    
    * Add logging module to load log configuration dynamically
    
    Signed-off-by: Gao Hongtao <[email protected]>
---
 CHANGES.md                                         |  4 +-
 docs/en/setup/backend/dynamic-config.md            |  1 +
 docs/en/setup/backend/dynamical-logging.md         | 52 ++++++++++++
 docs/menu.yml                                      |  2 +
 .../ConfigmapConfigurationWatcherRegister.java     |  5 +-
 .../ConfigmapConfigWatcherRegisterTest.java        |  8 +-
 .../oap/server/core/CoreModuleProvider.java        |  4 +
 .../server/core/logging/LoggingConfigWatcher.java  | 99 ++++++++++++++++++++++
 .../core/logging/log4j/OapConfiguration.java       | 33 ++++++++
 .../logging/log4j/OapConfigurationFactory.java     | 59 +++++++++++++
 10 files changed, 260 insertions(+), 7 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index e80df80..c172f6f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -122,7 +122,9 @@ Release Notes.
 * Filtering NaN value samples when build SampleFamily
 * Add Thread and ClassLoader Metrics for the self-observability and 
otel-oc-rules
 * Simple optimization of trace sql query statement. Avoid "select *" query 
method
-* Breaking Change: rename `qps` to `rpm` in LAL 
+* Introduce dynamical logging to update log configuration at runtime
+* Fix Kubernetes ConfigMap configuration center doesn't send delete event 
+* Breaking Change: emove `qps` and add `rpm` in LAL 
 
 #### UI
 
diff --git a/docs/en/setup/backend/dynamic-config.md 
b/docs/en/setup/backend/dynamic-config.md
index 44b7f86..eab8442 100755
--- a/docs/en/setup/backend/dynamic-config.md
+++ b/docs/en/setup/backend/dynamic-config.md
@@ -11,6 +11,7 @@ Right now, SkyWalking supports following dynamic 
configurations.
 |alarm.default.alarm-settings| The alarm settings, will override 
`alarm-settings.yml`. | same as [`alarm-settings.yml`](backend-alarm.md) |
 |core.default.apdexThreshold| The apdex threshold settings, will override 
`service-apdex-threshold.yml`. | same as 
[`service-apdex-threshold.yml`](apdex-threshold.md) |
 |core.default.endpoint-name-grouping| The endpoint name grouping setting, will 
override `endpoint-name-grouping.yml`. | same as 
[`endpoint-name-grouping.yml`](endpoint-grouping-rules.md) |
+|core.default.log4j-xml| The log4j xml configuration, will override 
`log4j2.xml`. | same as [`log4j2.xml`](dynamical-logging.md) |
 |agent-analyzer.default.sampleRate| Trace sampling , override 
`receiver-trace/default/sampleRate` of `application.yml`. | 10000 |
 |agent-analyzer.default.slowTraceSegmentThreshold| Setting this threshold 
about the latency would make the slow trace segments sampled if they cost more 
time, even the sampling mechanism activated. The default value is `-1`, which 
means would not sample slow traces. Unit, millisecond. override 
`receiver-trace/default/slowTraceSegmentThreshold` of `application.yml`. | -1 |
 |configuration-discovery.default.agentConfigurations| The 
ConfigurationDiscovery settings | look at 
[`configuration-discovery.md`](../service-agent/java-agent/configuration-discovery.md)
 |
diff --git a/docs/en/setup/backend/dynamical-logging.md 
b/docs/en/setup/backend/dynamical-logging.md
new file mode 100644
index 0000000..d143167
--- /dev/null
+++ b/docs/en/setup/backend/dynamical-logging.md
@@ -0,0 +1,52 @@
+# Dynamical Logging
+
+OAP server leverage the log4j2 to manage the logging system. The log4j2 
supports changing the configuration 
+at the runtime, but you have to update the XML configuration file manually,  
which could waste you much time and 
+easy to make mistakes.
+
+The dynamical logging, which depends on the dynamic configuration, provides us 
an agile way to update all OAP log4j 
+configurations through a single operation.
+
+The key of the configuration item is `core.default.log4j-xml`, and you can 
select any one of the configuration implements 
+to store the content of log4j.xml. In the booting phase, once the core module 
gets started, the `core.default.log4j-xml`
+would come into the OAP log4j context.
+
+Supposing changing the configuration after OAP started, you have to wait a 
while to get the changes applied. 
+The default value is `60` seconds which you could change through 
`configuration.<configuration implement>.peroid` in application.yaml.
+
+If you remove `core.default.log4j-xml` from the configuration center or 
disable the configuration module, the `log4j.xml`
+laid in `config` directory would get the effect. 
+
+> Caveat: OAP only supports XML configuration format.
+
+There is an example to show how to config dynamical logging through a 
ConfigMap in a Kubernetes cluster. Other configuration
+clusters could follow the same convention to set up.
+
+```yaml
+apiVersion: v1
+data:
+  core.default.log4j-xml: |-
+    <Configuration status="WARN">
+       <Appenders>
+         <Console name="Console" target="SYSTEM_OUT">
+           <PatternLayout charset="UTF-8" pattern="%d - %c - %L [%t] %-5p %x - 
%m%n"/>
+         </Console>
+       </Appenders>
+       <Loggers>
+         <logger name="io.grpc.netty" level="INFO"/>
+         <logger name="org.apache.skywalking.oap.server.configuration.api" 
level="TRACE"/>
+         <logger 
name="org.apache.skywalking.oap.server.configuration.configmap" level="DEBUG"/>
+         <Root level="WARN">
+           <AppenderRef ref="Console"/>
+         </Root>
+        </Loggers>
+    </Configuration>
+kind: ConfigMap
+metadata:
+  labels:
+    app: collector
+    release: skywalking
+  name: skywalking-oap
+  namespace: default
+```
+
diff --git a/docs/menu.yml b/docs/menu.yml
index bec98b6..a778217 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -149,6 +149,8 @@ catalog:
               path: "/en/setup/backend/advanced-deployment"
             - name: "Data Lifecycle. Time To Live (TTL)"
               path: "/en/setup/backend/ttl"
+            - name: "Dynamical Logging"
+              path: "/en/setup/backend/dynamical-logging"
           - name: "Deploy In Kubernetes"
             path: "/en/setup/backend/backend-k8s"
           - name: "Tracing"
diff --git 
a/oap-server/server-configuration/configuration-k8s-configmap/src/main/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigurationWatcherRegister.java
 
b/oap-server/server-configuration/configuration-k8s-configmap/src/main/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigurationWatcherRegister.java
index c6bcdbc..f3ca25c 100644
--- 
a/oap-server/server-configuration/configuration-k8s-configmap/src/main/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigurationWatcherRegister.java
+++ 
b/oap-server/server-configuration/configuration-k8s-configmap/src/main/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigurationWatcherRegister.java
@@ -19,7 +19,6 @@
 package org.apache.skywalking.oap.server.configuration.configmap;
 
 import io.kubernetes.client.openapi.models.V1ConfigMap;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import lombok.extern.slf4j.Slf4j;
@@ -46,9 +45,7 @@ public class ConfigmapConfigurationWatcherRegister extends 
ConfigWatcherRegister
             if (log.isDebugEnabled()) {
                 log.debug("read config: name:{} ,value:{}", name, value);
             }
-            if (Objects.nonNull(value)) {
-                configTable.add(new ConfigTable.ConfigItem(name, value));
-            }
+            configTable.add(new ConfigTable.ConfigItem(name, value));
         }
         return Optional.of(configTable);
     }
diff --git 
a/oap-server/server-configuration/configuration-k8s-configmap/src/test/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigWatcherRegisterTest.java
 
b/oap-server/server-configuration/configuration-k8s-configmap/src/test/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigWatcherRegisterTest.java
index 19e5177..4612547 100644
--- 
a/oap-server/server-configuration/configuration-k8s-configmap/src/test/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigWatcherRegisterTest.java
+++ 
b/oap-server/server-configuration/configuration-k8s-configmap/src/test/java/org/apache/skywalking/oap/server/configuration/configmap/ConfigmapConfigWatcherRegisterTest.java
@@ -67,7 +67,9 @@ public class ConfigmapConfigWatcherRegisterTest {
 
         Assert.assertTrue(optionalConfigTable.isPresent());
         ConfigTable configTable = optionalConfigTable.get();
-        Assert.assertEquals(configTable.getItems().size(), 0);
+        Assert.assertEquals(configTable.getItems().size(), 1);
+        Assert.assertEquals(configTable.getItems().get(0).getName(), "key1");
+        Assert.assertNull(configTable.getItems().get(0).getValue());
     }
 
     @Test
@@ -79,7 +81,9 @@ public class ConfigmapConfigWatcherRegisterTest {
 
         Assert.assertTrue(optionalConfigTable.isPresent());
         ConfigTable configTable = optionalConfigTable.get();
-        Assert.assertEquals(configTable.getItems().size(), 0);
+        Assert.assertEquals(configTable.getItems().size(), 1);
+        Assert.assertEquals(configTable.getItems().get(0).getName(), "key1");
+        Assert.assertNull(configTable.getItems().get(0).getValue());
     }
 
     @Test
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
index 1be58a3..d6b1b97 100755
--- 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java
@@ -49,6 +49,7 @@ import 
org.apache.skywalking.oap.server.core.config.NamingControl;
 import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
 import 
org.apache.skywalking.oap.server.core.config.group.EndpointNameGroupingRuleWatcher;
 import 
org.apache.skywalking.oap.server.core.config.group.openapi.EndpointGroupingRuleReader4Openapi;
+import org.apache.skywalking.oap.server.core.logging.LoggingConfigWatcher;
 import 
org.apache.skywalking.oap.server.core.management.ui.template.UITemplateInitializer;
 import 
org.apache.skywalking.oap.server.core.management.ui.template.UITemplateManagementService;
 import org.apache.skywalking.oap.server.core.oal.rt.DisableOALDefine;
@@ -121,6 +122,7 @@ public class CoreModuleProvider extends ModuleProvider {
     private ApdexThresholdConfig apdexThresholdConfig;
     private EndpointNameGroupingRuleWatcher endpointNameGroupingRuleWatcher;
     private OALEngineLoaderService oalEngineLoaderService;
+    private LoggingConfigWatcher loggingConfigWatcher;
 
     public CoreModuleProvider() {
         super();
@@ -307,6 +309,7 @@ public class CoreModuleProvider extends ModuleProvider {
         
TopNStreamProcessor.getInstance().setTopNWorkerReportCycle(moduleConfig.getTopNReportPeriod());
         apdexThresholdConfig = new ApdexThresholdConfig(this);
         ApdexMetrics.setDICT(apdexThresholdConfig);
+        loggingConfigWatcher = new LoggingConfigWatcher(this);
     }
 
     @Override
@@ -349,6 +352,7 @@ public class CoreModuleProvider extends ModuleProvider {
                                                                                
   DynamicConfigurationService.class);
         
dynamicConfigurationService.registerConfigChangeWatcher(apdexThresholdConfig);
         
dynamicConfigurationService.registerConfigChangeWatcher(endpointNameGroupingRuleWatcher);
+        
dynamicConfigurationService.registerConfigChangeWatcher(loggingConfigWatcher);
     }
 
     @Override
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/LoggingConfigWatcher.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/LoggingConfigWatcher.java
new file mode 100644
index 0000000..6ea59e9
--- /dev/null
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/LoggingConfigWatcher.java
@@ -0,0 +1,99 @@
+/*
+ * 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.skywalking.oap.server.core.logging;
+
+import com.google.common.base.Strings;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.logging.log4j.OapConfiguration;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+
+/**
+ * LoggingConfigWatcher watches the change of logging configuration. Once got 
the change content, it would apply them to
+ * the current logger context.
+ */
+@Slf4j
+public class LoggingConfigWatcher extends ConfigChangeWatcher {
+    private final LoggerContext ctx;
+    private final OapConfiguration originConfiguration;
+    private volatile String content;
+
+    public LoggingConfigWatcher(final ModuleProvider provider) {
+        super(CoreModule.NAME, provider, "log4j-xml");
+        this.ctx = (LoggerContext) LogManager.getContext(false);
+        this.originConfiguration = (OapConfiguration) ctx.getConfiguration();
+    }
+
+    @Override
+    public void notify(final ConfigChangeEvent value) {
+        String newValue;
+        if (EventType.DELETE.equals(value.getEventType())) {
+            this.content = "";
+            newValue = "";
+        } else {
+            newValue = value.getNewValue();
+        }
+        try {
+            if (!reconfigure(newValue)) {
+                return;
+            }
+        } catch (Throwable t) {
+            log.error("failed to apply configuration to log4j", t);
+            return;
+        }
+        StringBuilder builder = new StringBuilder();
+        ctx.getConfiguration().getLoggers().forEach((loggerName, config) -> {
+            builder.append(Strings.isNullOrEmpty(loggerName) ? "Root" : 
loggerName)
+                   .append(":")
+                   .append(config.getLevel())
+                   .append(",");
+        });
+        this.content = builder.toString();
+    }
+
+    @Override
+    public String value() {
+        return this.content;
+    }
+
+    private boolean reconfigure(final String newValue) {
+        if (Strings.isNullOrEmpty(newValue)) {
+            if (ctx.getConfiguration().equals(originConfiguration)) {
+                return false;
+            }
+            ctx.onChange(originConfiguration);
+            return true;
+        }
+        OapConfiguration oc;
+        try {
+            oc = new OapConfiguration(ctx, new ConfigurationSource(new 
ByteArrayInputStream(newValue.getBytes())));
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("failed to parse %s from 
configuration center", newValue), e);
+        }
+        oc.initialize();
+        ctx.onChange(oc);
+        return true;
+    }
+}
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfiguration.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfiguration.java
new file mode 100644
index 0000000..a9c07f7
--- /dev/null
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * 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.skywalking.oap.server.core.logging.log4j;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
+
+/**
+ * OapConfiguration is the object loaded into log4j context.
+ */
+public class OapConfiguration extends XmlConfiguration {
+    public OapConfiguration(final LoggerContext loggerContext,
+                            final ConfigurationSource configSource) {
+        super(loggerContext, configSource);
+    }
+}
diff --git 
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfigurationFactory.java
 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfigurationFactory.java
new file mode 100644
index 0000000..54b829a
--- /dev/null
+++ 
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/logging/log4j/OapConfigurationFactory.java
@@ -0,0 +1,59 @@
+/*
+ * 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.skywalking.oap.server.core.logging.log4j;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Order;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+
+/**
+ * OapConfigurationFactory guarantee the log4j configuration object is {@link 
OapConfiguration}.
+ * It guarantees the downcast {@link Configuration} to {@link 
OapConfiguration} safely.
+ */
+@Plugin(name = "OapConfigurationFactory", category = "ConfigurationFactory")
+@Order(10)
+public class OapConfigurationFactory extends ConfigurationFactory {
+    /**
+     * Valid file extensions for XML files.
+     */
+    public static final String[] SUFFIXES = new String[] {".xml", "*"};
+
+    /**
+     * Returns the file suffixes for XML files.
+     * @return An array of File extensions.
+     */
+    @Override
+    public String[] getSupportedTypes() {
+        return SUFFIXES;
+    }
+
+    /**
+     * Return the configuration.
+     * @param loggerContext The logger context
+     * @param source The input source
+     * @return the configuration
+     */
+    @Override
+    public Configuration getConfiguration(final LoggerContext loggerContext, 
final ConfigurationSource source) {
+        return new OapConfiguration(loggerContext, source);
+    }
+}

Reply via email to