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

wankai123 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 2822f125ae Route LAL rules within a layer by input type; add 
envoy-als-tcp rule (#13955)
2822f125ae is described below

commit 2822f125ae3ec397c5339ba172aee9ca3a09245f
Author: Wan Kai <[email protected]>
AuthorDate: Tue Jul 21 14:17:03 2026 +0800

    Route LAL rules within a layer by input type; add envoy-als-tcp rule 
(#13955)
---
 docs/en/changes/changes.md                         |   1 +
 .../analyzer/v2/compiler/LALClassGenerator.java    |  17 +++-
 .../skywalking/oap/log/analyzer/v2/dsl/DSL.java    |  16 ++-
 .../provider/log/listener/LogFilterListener.java   |  26 ++++-
 .../compiler/LALClassGeneratorExtractorTest.java   |  62 ++++++++++++
 .../log/listener/LogFilterListenerRoutingTest.java | 108 +++++++++++++++++++++
 .../lal/test-lal/oap-cases/envoy-als.data.yaml     |  23 +++++
 .../scripts/lal/test-lal/oap-cases/envoy-als.yaml  |  27 ++++++
 .../src/main/resources/lal/envoy-als.yaml          |  27 ++++++
 9 files changed, 300 insertions(+), 7 deletions(-)

diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index f762cba9ad..12ff3aae95 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -336,6 +336,7 @@
 * Surface the effective BanyanDB configuration (`bydb.yml` / `bydb-topn.yml`) 
in the `/debugging/config/dump` admin API. Because the BanyanDB config moved to 
a separate file in 10.2.0, a BanyanDB deployment previously showed an empty 
`storage.banyandb` block in the dump; its post-environment-resolution values 
are now merged into the same response under `storage.banyandb.*` (TopN rules 
under `storage.banyandb.topN.*`), masked by the same secret-keyword list, via a 
generic `ConfigDumpExten [...]
 * Fix: an MQE `top_n(metric, N, order, attrX='value')` query whose attribute 
is not a column of the target metric now returns a descriptive MQE error 
instead of a raw storage `IOException` surfaced as `Internal IO exception, 
query metrics error.`. Attribute columns (`attr0..attrN`) exist only on 
decorated metrics (`service_*` / `endpoint_*` / `kubernetes_service_*`, set to 
the layer name via OAL `.decorator(...)`) and the MAL meter base; metrics such 
as relations or database / cache / mq [...]
 * Migrate all BanyanDB storage read queries from the typed query-builder API 
to BydbQL.
+* Route LAL rules within a layer by their input type, so a single layer can 
host rules over different proto inputs. Each compiled rule now carries its 
effective input type (the proto class its `parsed.*` getters cast to, or `null` 
for parser-based / untyped rules), and `LogFilterListener` skips any rule whose 
type doesn't match the incoming log instead of running every rule in the layer. 
This fixes a latent `ClassCastException` (caught and logged per log) that fired 
whenever a `MESH` log [...]
 
 #### UI
 * Add Airflow layer dashboards and menu i18n under Workflow Scheduler in 
Horizon UI (SWIP-7).
diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGenerator.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGenerator.java
index 4c0cc8d7bc..5453b29d33 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGenerator.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGenerator.java
@@ -84,6 +84,15 @@ public final class LALClassGenerator {
     private String classNameHint;
     private Class<?> inputType;
     private Class<?> outputType;
+    /**
+     * The input type actually used for {@code parsed.*} proto getter codegen:
+     * equals {@link #inputType} for parser-less rules, and {@code null} when a
+     * json/yaml/text parser is present (the parser reads the map, so no proto
+     * cast is emitted). Exposed so the runtime can route a log to this rule
+     * only when the incoming object matches, without re-parsing the DSL. Set
+     * by {@link #compileFromModel}.
+     */
+    private Class<?> effectiveInputType;
     private String yamlSource;
     /**
      * Optional content hash threaded into every generated rule's {@code 
GateHolder}
@@ -248,6 +257,10 @@ public final class LALClassGenerator {
         this.outputType = outputType;
     }
 
+    public Class<?> getEffectiveInputType() {
+        return effectiveInputType;
+    }
+
     public void setYamlSource(final String yamlSource) {
         this.yamlSource = yamlSource;
     }
@@ -523,9 +536,9 @@ public final class LALClassGenerator {
         // generates direct proto getter calls.  When a parser is present 
(json/yaml/text),
         // parsed.* reads from the parsed map and tag() reads from 
LogData.Builder tags,
         // so inputType must be null to avoid mis-guarding codegen branches.
-        final Class<?> effectiveInputType =
+        this.effectiveInputType =
             parserType == ParserType.NONE ? this.inputType : null;
-        final GenCtx genCtx = new GenCtx(parserType, effectiveInputType, 
resolvedOutput);
+        final GenCtx genCtx = new GenCtx(parserType, this.effectiveInputType, 
resolvedOutput);
 
         if (parserType == ParserType.NONE && this.inputType != null) {
             log.info("LAL rule has no parser — using inputType {} for "
diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
index 0993bc6a0a..6dbaf23212 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
@@ -47,6 +47,19 @@ public class DSL {
     @Getter
     private final LalExpression expression;
     private final FilterSpec filterSpec;
+    /**
+     * The <b>effective</b> proto input type this rule's {@code parsed.*} 
getters
+     * cast to, or {@code null} for parser-based / untyped rules (which run
+     * against any input). This is NOT the declared/resolved input type from 
the
+     * YAML {@code inputType} field or the SPI ({@code 
LALConfig#getInputType()}):
+     * a parser-based rule has a declared type but a {@code null} effective 
type,
+     * because it reads the parsed map rather than casting the proto. The 
runtime
+     * skips a rule whose effective type doesn't match the incoming log — this 
is
+     * how HTTP and TCP envoy access logs, which share {@code Layer.MESH}, 
route
+     * to their own rules without cross-type {@code ClassCastException}.
+     */
+    @Getter
+    private final Class<?> effectiveInputType;
 
     public static DSL of(final ModuleManager moduleManager,
                          final LogAnalyzerModuleConfig config,
@@ -105,6 +118,7 @@ public class DSL {
             // inline. SHA-256 hash isn't useful to operators; raw text is.
             generator.setContent(dsl);
             final LalExpression expression = generator.compile(dsl);
+            final Class<?> effectiveInputType = 
generator.getEffectiveInputType();
             // Stamp the structured rule metadata onto the per-rule GateHolder 
so
             // dsl-debugging records render {ruleName, layer, outputClass} 
alongside
             // the verbatim DSL. Only effective when codegen injection is 
enabled
@@ -124,7 +138,7 @@ public class DSL {
                 holder.setMetadata(meta);
             }
             final FilterSpec filterSpec = new FilterSpec(moduleManager, 
config);
-            return new DSL(ruleName, expression, filterSpec);
+            return new DSL(ruleName, expression, filterSpec, 
effectiveInputType);
         } catch (Exception e) {
             throw new ModuleStartException(
                 "Failed to compile LAL expression: " + dsl, e);
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 6568b10bab..ec043d477f 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
@@ -76,6 +76,12 @@ public class LogFilterListener implements 
LogAnalysisListener {
      * not the provider.
      */
     private final LALSourceTypeProvider sourceTypeProvider;
+    /**
+     * Rules selected for the current log — the subset of {@link #dsls} whose
+     * declared input type matches the incoming object. Index-aligned with
+     * {@link #contexts}. Rebuilt on every {@link #parse}.
+     */
+    private List<DSL> activeDsls;
     private List<ExecutionContext> contexts;
 
     LogFilterListener(final Collection<DSL> dsls, final boolean autoMode,
@@ -87,11 +93,11 @@ public class LogFilterListener implements 
LogAnalysisListener {
 
     @Override
     public void build() {
-        for (int i = 0; i < dsls.size(); i++) {
+        for (int i = 0; i < activeDsls.size(); i++) {
             try {
-                dsls.get(i).evaluate(contexts.get(i));
+                activeDsls.get(i).evaluate(contexts.get(i));
             } catch (final Exception e) {
-                log.warn("Failed to evaluate dsl: {}", dsls.get(i), e);
+                log.warn("Failed to evaluate dsl: {}", activeDsls.get(i), e);
             }
         }
     }
@@ -113,13 +119,25 @@ public class LogFilterListener implements 
LogAnalysisListener {
     @Override
     public LogAnalysisListener parse(final LogMetadata metadata,
                                      final Object input) {
+        activeDsls = new ArrayList<>(dsls.size());
         contexts = new ArrayList<>(dsls.size());
-        for (int i = 0; i < dsls.size(); i++) {
+        for (final DSL dsl : dsls) {
+            // A rule whose parsed.* getters cast to a proto type only applies 
to
+            // logs of that type. Envoy HTTP and TCP access logs both dispatch
+            // under Layer.MESH, so without this guard a TCP entry would hit 
the
+            // HTTP rule (and vice versa) and throw ClassCastException on the
+            // generated proto cast. Parser-based / untyped rules have a null
+            // effective input type and run against any input, unchanged.
+            final Class<?> effectiveInputType = dsl.getEffectiveInputType();
+            if (effectiveInputType != null && 
!effectiveInputType.isInstance(input)) {
+                continue;
+            }
             final ExecutionContext ctx = new ExecutionContext().init(metadata, 
input);
             ctx.setSourceTypeProvider(sourceTypeProvider);
             if (autoMode) {
                 ctx.autoLayerMode(true);
             }
+            activeDsls.add(dsl);
             contexts.add(ctx);
         }
         return this;
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
index 179a6a0cdb..5798a929c1 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
+++ 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
@@ -19,7 +19,9 @@ package org.apache.skywalking.oap.log.analyzer.v2.compiler;
 
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -184,6 +186,66 @@ class LALClassGeneratorExtractorTest extends 
LALClassGeneratorTestBase {
         compileAndAssert(dsl);
     }
 
+    @Test
+    void effectiveInputTypeKeepsInputTypeWithoutParser() throws Exception {
+        // Parser-less rule: parsed.* casts to the declared proto type, so the
+        // effective input type equals inputType — this is the routing key the
+        // runtime uses to skip type-mismatched rules 
(LogFilterListener#parse).
+        generator.setInputType(
+            io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry.class);
+        compileAndAssert("filter {\n  sink {}\n}");
+        assertEquals(
+            io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry.class,
+            generator.getEffectiveInputType());
+    }
+
+    @Test
+    void compileEnvoyTcpAlsRule() throws Exception {
+        // Guards the shipped envoy-als-tcp rule: verifies its parsed.* chains
+        // resolve against TCPAccessLogEntry (commonProperties.responseFlags),
+        // so a field-name typo fails here instead of crashing OAP at startup.
+        generator.setInputType(
+            io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry.class);
+        final String dsl =
+            "filter {\n"
+            + "  if 
(!parsed?.commonProperties?.responseFlags?.toString()?.trim()) {\n"
+            + "    abort {}\n"
+            + "  }\n"
+            + "  extractor {\n"
+            + "    tag 'response.flag': 
parsed?.commonProperties?.responseFlags\n"
+            + "  }\n"
+            + "  sink {\n"
+            + "    sampler {\n"
+            + "      rateLimit(\"${log.service}:"
+            + "${parsed?.commonProperties?.responseFlags?.toString()}\") {\n"
+            + "        rpm 6000\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "}";
+        final String source = generator.generateSource(dsl);
+        final String fqcn =
+            "io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry";
+        assertTrue(source.contains(
+            fqcn + " _p = (" + fqcn + ") h.ctx().input()"),
+            "Expected _p cast to TCPAccessLogEntry but got: " + source);
+        assertTrue(source.contains("_p.getCommonProperties()"),
+            "Expected _p.getCommonProperties() but got: " + source);
+        compileAndAssert(dsl);
+    }
+
+    @Test
+    void effectiveInputTypeIsNullWhenParserPresent() throws Exception {
+        // A json/yaml/text parser reads the parsed map, not proto getters, so
+        // the effective input type must be null even when inputType is set —
+        // the rule then runs against any input (e.g. network-profiling json).
+        generator.setInputType(
+            io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry.class);
+        compileAndAssert("filter {\n  json {}\n  sink {}\n}");
+        assertNull(generator.getEffectiveInputType(),
+            "A json/yaml/text parser must null out the effective input type.");
+    }
+
     // ==================== Output field assignment ====================
 
     @Test
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListenerRoutingTest.java
 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListenerRoutingTest.java
new file mode 100644
index 0000000000..d079595702
--- /dev/null
+++ 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogFilterListenerRoutingTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.log.analyzer.v2.provider.log.listener;
+
+import io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry;
+import io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry;
+import java.util.Arrays;
+import java.util.Collections;
+import org.apache.skywalking.oap.log.analyzer.v2.dsl.DSL;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
+import org.junit.jupiter.api.Test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Routing tests for {@link LogFilterListener#parse}: a rule that declares a
+ * typed proto input only applies to logs of that type. Envoy HTTP and TCP
+ * access logs share {@code Layer.MESH}, so the listener must dispatch each
+ * entry to the rule whose input type matches and skip the others — otherwise
+ * the mismatched rule throws {@code ClassCastException} on its generated proto
+ * cast. Rules with a {@code null} input type (parser-based / untyped) run
+ * against any input.
+ *
+ * <p>{@link DSL} is mocked so the assertions observe exactly which rules the
+ * listener evaluated, independent of DSL compilation.
+ */
+class LogFilterListenerRoutingTest {
+
+    private static LogMetadata meshMetadata() {
+        return LogMetadata.builder().service("svc").layer("MESH").build();
+    }
+
+    private static DSL ruleWithEffectiveInputType(final Class<?> 
effectiveInputType) {
+        final DSL dsl = mock(DSL.class);
+        // doReturn avoids the Class<?> wildcard-capture mismatch that
+        // when(...).thenReturn(Class<?>) triggers on getEffectiveInputType().
+        doReturn(effectiveInputType).when(dsl).getEffectiveInputType();
+        return dsl;
+    }
+
+    @Test
+    void tcpEntryReachesOnlyTcpAndParserRules() {
+        // Mirrors the MESH bucket: envoy-als (HTTP), envoy-als-tcp (TCP), and
+        // network-profiling-slow-trace (json parser, null inputType).
+        final DSL httpRule = 
ruleWithEffectiveInputType(HTTPAccessLogEntry.class);
+        final DSL tcpRule = 
ruleWithEffectiveInputType(TCPAccessLogEntry.class);
+        final DSL parserRule = ruleWithEffectiveInputType(null);
+
+        final LogFilterListener listener = new LogFilterListener(
+            Arrays.asList(httpRule, tcpRule, parserRule), false, null);
+
+        listener.parse(meshMetadata(), TCPAccessLogEntry.newBuilder().build());
+        listener.build();
+
+        verify(tcpRule).evaluate(any());
+        verify(parserRule).evaluate(any());
+        verify(httpRule, never()).evaluate(any());
+    }
+
+    @Test
+    void httpEntryReachesOnlyHttpAndParserRules() {
+        final DSL httpRule = 
ruleWithEffectiveInputType(HTTPAccessLogEntry.class);
+        final DSL tcpRule = 
ruleWithEffectiveInputType(TCPAccessLogEntry.class);
+        final DSL parserRule = ruleWithEffectiveInputType(null);
+
+        final LogFilterListener listener = new LogFilterListener(
+            Arrays.asList(httpRule, tcpRule, parserRule), false, null);
+
+        listener.parse(meshMetadata(), 
HTTPAccessLogEntry.newBuilder().build());
+        listener.build();
+
+        verify(httpRule).evaluate(any());
+        verify(parserRule).evaluate(any());
+        verify(tcpRule, never()).evaluate(any());
+    }
+
+    @Test
+    void untypedRuleRunsForAnyInput() {
+        final DSL untyped = ruleWithEffectiveInputType(null);
+
+        final LogFilterListener listener = new LogFilterListener(
+            Collections.singletonList(untyped), false, null);
+
+        listener.parse(meshMetadata(), TCPAccessLogEntry.newBuilder().build());
+        listener.build();
+
+        verify(untyped).evaluate(any());
+    }
+}
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
 
b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
index 1b644ec757..ba4c2201f8 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
+++ 
b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
@@ -47,6 +47,29 @@ envoy-als:
       abort: false
       tag.response.flag: "upstream_connection_failure: true\n"
 
+# The envoy-als-tcp rule processes protobuf TCPAccessLogEntry as extraLog.
+# TCP entries have no HTTP status code; the rule filters on
+# commonProperties.responseFlags and tags it as response.flag.
+envoy-als-tcp:
+  - service: test-mesh-tcp-svc
+    body-type: none
+    extra-log:
+      proto-class: io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry
+      proto-json: 
'{"commonProperties":{"responseFlags":{"upstreamConnectionFailure":true}}}'
+    expect:
+      save: true
+      abort: false
+      tag.response.flag: "upstream_connection_failure: true\n"
+
+  - service: test-mesh-tcp-svc-abort
+    body-type: none
+    extra-log:
+      proto-class: io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry
+      proto-json: 
'{"commonProperties":{"upstreamCluster":"outbound|80||backend.default.svc"}}'
+    expect:
+      save: true
+      abort: true
+
 network-profiling-slow-trace:
   body-type: json
   body: 
'{"latency":800,"uri":"/mesh/api","reason":"SLOW","service":"envoy-mesh-svc","serviceInstance":"envoy-inst-1","client_process":{"process_id":"proc-c1","local":false,"address":"10.0.0.5:8080"},"server_process":{"process_id":"","local":true,"address":""},"detect_point":"SERVER","component":"http","ssl":false}'
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.yaml
 
b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.yaml
index ae23160841..92d865e6b7 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.yaml
+++ 
b/oap-server/analyzer/log-analyzer/src/test/resources/scripts/lal/test-lal/oap-cases/envoy-als.yaml
@@ -51,6 +51,33 @@ rules:
           }
         }
       }
+  # The TCP counterpart of envoy-als. HTTP and TCP access logs share
+  # Layer.MESH; the runtime routes each entry to the rule whose inputType
+  # matches (see LogFilterListener#parse), so this rule only ever sees
+  # TCPAccessLogEntry and the HTTP rule above only sees HTTPAccessLogEntry.
+  - name: envoy-als-tcp
+    layer: MESH
+    inputType: io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry
+    dsl: |
+      filter {
+        // TCP access logs have no HTTP status code; only collect abnormal
+        // connections (commonProperties?.responseFlags is not empty).
+        if (!parsed?.commonProperties?.responseFlags?.toString()?.trim()) {
+          abort {}
+        }
+        extractor {
+          tag 'response.flag': parsed?.commonProperties?.responseFlags
+        }
+        sink {
+          sampler {
+            // use service:responseFlags as sampler id so that each
+            // service:responseFlags has its own sampler.
+            
rateLimit("${log.service}:${parsed?.commonProperties?.responseFlags?.toString()}")
 {
+              rpm 6000
+            }
+          }
+        }
+      }
   - name: network-profiling-slow-trace
     layer: MESH
     outputType: SampledTrace
diff --git a/oap-server/server-starter/src/main/resources/lal/envoy-als.yaml 
b/oap-server/server-starter/src/main/resources/lal/envoy-als.yaml
index ae23160841..92d865e6b7 100644
--- a/oap-server/server-starter/src/main/resources/lal/envoy-als.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/envoy-als.yaml
@@ -51,6 +51,33 @@ rules:
           }
         }
       }
+  # The TCP counterpart of envoy-als. HTTP and TCP access logs share
+  # Layer.MESH; the runtime routes each entry to the rule whose inputType
+  # matches (see LogFilterListener#parse), so this rule only ever sees
+  # TCPAccessLogEntry and the HTTP rule above only sees HTTPAccessLogEntry.
+  - name: envoy-als-tcp
+    layer: MESH
+    inputType: io.envoyproxy.envoy.data.accesslog.v3.TCPAccessLogEntry
+    dsl: |
+      filter {
+        // TCP access logs have no HTTP status code; only collect abnormal
+        // connections (commonProperties?.responseFlags is not empty).
+        if (!parsed?.commonProperties?.responseFlags?.toString()?.trim()) {
+          abort {}
+        }
+        extractor {
+          tag 'response.flag': parsed?.commonProperties?.responseFlags
+        }
+        sink {
+          sampler {
+            // use service:responseFlags as sampler id so that each
+            // service:responseFlags has its own sampler.
+            
rateLimit("${log.service}:${parsed?.commonProperties?.responseFlags?.toString()}")
 {
+              rpm 6000
+            }
+          }
+        }
+      }
   - name: network-profiling-slow-trace
     layer: MESH
     outputType: SampledTrace

Reply via email to