You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/04/15 10:14:57 UTC
(skywalking) 02/05: LAL: add layer:auto mode for dynamic layer assignment
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch feature/swip11-general-enhancements
in repository https://gitbox.apache.org/repos/asf/skywalking.git
commit 6786f833743b08d2ad59a13d5fb3947003378ae6
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Wed Apr 15 17:27:22 2026 +0800
LAL: add layer:auto mode for dynamic layer assignment
When a LAL rule declares layer: auto, it matches logs where
service.layer is absent. The script determines the layer via
the extractor (e.g., layer "IOS"). If the script does not set
a layer, the log is warned and dropped at sink time.
- LALConfig: add LAYER_AUTO = "auto" constant
- ExecutionContext: add autoLayerMode flag
- LogFilterListener.Factory: store auto rules separately, route
logs with null layer to auto rules
- LogAnalyzer: empty layer → createAnalysisListeners(null)
instead of defaulting to Layer.GENERAL
- FilterSpec.doSink(): enforce layer-must-be-set in auto mode
- Tests: 4 data-driven execution tests (set, not-set, abort)
Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
.../oap/log/analyzer/v2/dsl/ExecutionContext.java | 11 +++++
.../analyzer/v2/dsl/spec/filter/FilterSpec.java | 10 +++++
.../oap/log/analyzer/v2/provider/LALConfig.java | 6 +++
.../log/analyzer/v2/provider/log/LogAnalyzer.java | 15 +++----
.../provider/log/listener/LogFilterListener.java | 47 ++++++++++++++++------
.../feature-cases/execution-basic.data.yaml | 32 +++++++++++++++
.../test-lal/feature-cases/execution-basic.yaml | 38 +++++++++++++++++
7 files changed, 138 insertions(+), 21 deletions(-)
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/ExecutionContext.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/ExecutionContext.java
index 04154077a2..2039bae36b 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/ExecutionContext.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/ExecutionContext.java
@@ -49,6 +49,7 @@ public class ExecutionContext {
public static final String KEY_ABORT = "abort";
public static final String KEY_METRICS_CONTAINER = "metrics_container";
public static final String KEY_DRY_RUN = "dry_run";
+ public static final String KEY_AUTO_LAYER = "auto_layer";
public static final String KEY_OUTPUT = "output";
private final Map<String, Object> properties = new HashMap<>();
@@ -147,6 +148,16 @@ public class ExecutionContext {
return (boolean) getProperty(KEY_DRY_RUN);
}
+ public ExecutionContext autoLayerMode(final boolean autoLayer) {
+ setProperty(KEY_AUTO_LAYER, autoLayer);
+ return this;
+ }
+
+ public boolean isAutoLayerMode() {
+ final Object val = getProperty(KEY_AUTO_LAYER);
+ return val != null && (boolean) val;
+ }
+
public void setOutput(final Object output) {
setProperty(KEY_OUTPUT, output);
}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/filter/FilterSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/filter/FilterSpec.java
index dcd72fa052..0b0de203eb 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/filter/FilterSpec.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/filter/FilterSpec.java
@@ -184,6 +184,16 @@ public class FilterSpec extends AbstractSpec {
return;
}
+ // In auto-layer mode, the script must set the layer. If not set, drop the log.
+ if (ctx.isAutoLayerMode()) {
+ final String layer = metadata.getLayer();
+ if (layer == null || layer.isEmpty()) {
+ LOGGER.warn("Auto-layer LAL rule did not set layer for service={}, dropping log",
+ metadata.getService());
+ return;
+ }
+ }
+
sinkListenerFactories.stream()
.map(LogSinkListenerFactory::create)
.forEach(it -> it.parse(metadata, input, ctx).build());
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfig.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfig.java
index a8177cf341..b0098bacf3 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfig.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfig.java
@@ -22,6 +22,12 @@ import lombok.Data;
@Data
public class LALConfig {
+ /**
+ * Special layer value indicating the layer is determined by the LAL script at runtime.
+ * Rules with {@code layer: auto} match logs where {@code service.layer} is absent.
+ * The script is expected to set the layer in the extractor; if not set, the log is dropped.
+ */
+ public static final String LAYER_AUTO = "auto";
private String name;
private String dsl;
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
index 19d99541bd..36a14a94bd 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
@@ -22,7 +22,6 @@ import java.util.List;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogAnalysisListener;
-import org.apache.skywalking.oap.server.core.UnexpectedException;
import org.apache.skywalking.oap.server.core.analysis.Layer;
import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.util.StringUtil;
@@ -62,19 +61,17 @@ public class LogAnalyzer {
log.debug("The log is ignored because the Service name is empty");
return;
}
- Layer layer;
if (metadata.getLayer() == null || metadata.getLayer().isEmpty()) {
- layer = Layer.GENERAL;
+ // Empty layer: route to auto-layer rules (pass null)
+ createAnalysisListeners(null);
} else {
- try {
- layer = Layer.nameOf(metadata.getLayer());
- } catch (UnexpectedException e) {
- log.warn("The Layer {} is not found, abandon the log.", metadata.getLayer());
+ final Layer layer = Layer.nameOf(metadata.getLayer());
+ if (layer == Layer.UNDEFINED) {
+ log.warn("The Layer {} is not recognized, abandon the log.", metadata.getLayer());
return;
}
+ createAnalysisListeners(layer);
}
-
- createAnalysisListeners(layer);
if (metadata.getTimestamp() == 0) {
metadata.setTimestamp(System.currentTimeMillis());
}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListener.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListener.java
index ab16b1f540..385668a617 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListener.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListener.java
@@ -57,10 +57,12 @@ import org.apache.skywalking.oap.server.library.module.ModuleStartException;
@Slf4j
public class LogFilterListener implements LogAnalysisListener {
private final List<DSL> dsls;
+ private final boolean autoMode;
private List<ExecutionContext> contexts;
- LogFilterListener(final Collection<DSL> dsls) {
+ LogFilterListener(final Collection<DSL> dsls, final boolean autoMode) {
this.dsls = new ArrayList<>(dsls);
+ this.autoMode = autoMode;
}
@Override
@@ -79,7 +81,11 @@ public class LogFilterListener implements LogAnalysisListener {
final Object input) {
contexts = new ArrayList<>(dsls.size());
for (int i = 0; i < dsls.size(); i++) {
- contexts.add(new ExecutionContext().init(metadata, input));
+ final ExecutionContext ctx = new ExecutionContext().init(metadata, input);
+ if (autoMode) {
+ ctx.autoLayerMode(true);
+ }
+ contexts.add(ctx);
}
return this;
}
@@ -105,9 +111,11 @@ public class LogFilterListener implements LogAnalysisListener {
*/
public static class Factory implements LogAnalysisListenerFactory {
private final Map<Layer, Map<String, DSL>> dsls;
+ private final Map<String, DSL> autoDsls;
public Factory(final ModuleManager moduleManager, final LogAnalyzerModuleConfig config) throws Exception {
dsls = new HashMap<>();
+ autoDsls = new HashMap<>();
// Scan SPI providers for default inputType/outputType per layer
final Map<Layer, LALSourceTypeProvider> spiProviders = new HashMap<>();
@@ -123,19 +131,30 @@ public class LogFilterListener implements LogAnalysisListener {
.flatMap(it -> it.getRules().stream())
.collect(Collectors.toList());
for (final LALConfig c : configList) {
- final Layer layer = Layer.nameOf(c.getLayer());
- final LALSourceTypeProvider spiProvider = spiProviders.get(layer);
+ final boolean isAuto = LALConfig.LAYER_AUTO.equalsIgnoreCase(c.getLayer());
+ final Layer layer = isAuto ? null : Layer.nameOf(c.getLayer());
+ final LALSourceTypeProvider spiProvider = isAuto ? null : spiProviders.get(layer);
// Per-rule resolution: explicit YAML > SPI > null
final Class<?> resolvedInputType = resolveInputType(c, spiProvider);
final Class<?> resolvedOutputType = resolveOutputType(c, spiProvider);
- final Map<String, DSL> layerDsls = this.dsls.computeIfAbsent(layer, k -> new HashMap<>());
- if (layerDsls.put(c.getName(), DSL.of(
- moduleManager, config, c.getDsl(),
- resolvedInputType, resolvedOutputType,
- c.getName(), c.getSourceName())) != null) {
- throw new ModuleStartException("Layer " + layer.name() + " has already set " + c.getName() + " rule.");
+ final DSL dsl = DSL.of(
+ moduleManager, config, c.getDsl(),
+ resolvedInputType, resolvedOutputType,
+ c.getName(), c.getSourceName());
+
+ if (isAuto) {
+ if (autoDsls.put(c.getName(), dsl) != null) {
+ throw new ModuleStartException(
+ "Auto-layer rules have duplicate name: " + c.getName());
+ }
+ } else {
+ final Map<String, DSL> layerDsls = this.dsls.computeIfAbsent(layer, k -> new HashMap<>());
+ if (layerDsls.put(c.getName(), dsl) != null) {
+ throw new ModuleStartException(
+ "Layer " + layer.name() + " has already set " + c.getName() + " rule.");
+ }
}
}
}
@@ -221,13 +240,17 @@ public class LogFilterListener implements LogAnalysisListener {
@Override
public LogAnalysisListener create(Layer layer) {
if (layer == null) {
- return null;
+ // null layer → route to auto-layer rules
+ if (autoDsls.isEmpty()) {
+ return null;
+ }
+ return new LogFilterListener(autoDsls.values(), true);
}
final Map<String, DSL> dsl = dsls.get(layer);
if (dsl == null) {
return null;
}
- return new LogFilterListener(dsl.values());
+ return new LogFilterListener(dsl.values(), false);
}
}
}
diff --git a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
index 62fe6ab839..7c67febff0 100644
--- a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
+++ b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
@@ -167,3 +167,35 @@ source-attribute-missing:
expect:
save: true
tag.missing: ""
+
+auto-layer-set:
+ body-type: json
+ body: '{"service":"MyApp"}'
+ source-attributes:
+ os.name: "iOS"
+ expect:
+ save: true
+ layer: IOS
+ service: MyApp
+
+auto-layer-not-set:
+ body-type: json
+ body: '{"service":"MyApp"}'
+ expect:
+ save: true
+ service: MyApp
+
+auto-layer-abort:
+ - body-type: json
+ body: '{"msg":"hello"}'
+ source-attributes:
+ os.name: "iOS"
+ expect:
+ save: true
+ layer: IOS
+ - body-type: json
+ body: '{"msg":"hello"}'
+ source-attributes:
+ os.name: "Android"
+ expect:
+ abort: true
diff --git a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.yaml b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.yaml
index b15a9663e6..45a1398854 100644
--- a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.yaml
+++ b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/feature-cases/execution-basic.yaml
@@ -286,3 +286,41 @@ rules:
}
sink {}
}
+
+ - name: auto-layer-set
+ layer: auto
+ dsl: |
+ filter {
+ json {}
+ if (sourceAttribute("os.name") == "iOS") {
+ extractor {
+ layer "IOS"
+ service parsed.service as String
+ }
+ }
+ sink {}
+ }
+
+ - name: auto-layer-not-set
+ layer: auto
+ dsl: |
+ filter {
+ json {}
+ extractor {
+ service parsed.service as String
+ }
+ sink {}
+ }
+
+ - name: auto-layer-abort
+ layer: auto
+ dsl: |
+ filter {
+ if (sourceAttribute("os.name") != "iOS") {
+ abort {}
+ }
+ extractor {
+ layer "IOS"
+ }
+ sink {}
+ }