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

wu-sheng pushed a commit to branch feat/lal-drop-reason
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit c9927efcdf11a4a52cfd55cb6096e159253955e9
Author: Wu Sheng <[email protected]>
AuthorDate: Fri Jul 3 12:11:16 2026 +0800

    Surface LAL drop reason in DSL live-debugging
    
    When a LAL rule stops a log at a parse step, live-debug could only show
    continueOn=false (that it stopped, never why). Add a human-readable reason:
    
    - ExecutionContext.dropReason(String)/dropReason(): set at the LAL drop 
sites
      (json/yaml parse failure, text regexp non-match, non-log-body input) in
      FilterSpec/TextParserSpec.
    - The shared debug Sample gains an optional reason field (non-final, so the
      5-arg constructor and MAL/OAL recorders are unchanged); the LAL recorder
      stamps it from ctx.dropReason() only when the step stopped 
(continueOn=false),
      so the reason lands on the parser step that halted the pipeline.
    - Reason flows through the dsl-debugging REST session response and the 
cluster
      forward proto (ClusterSample.reason) for single-node and cross-node 
sessions.
    - Document per-DSL TYPE_* usage inline on Sample.
    
    Co-Authored-By: Claude Fable 5 <[email protected]>
---
 docs/en/changes/changes.md                         |  1 +
 .../oap/log/analyzer/v2/dsl/ExecutionContext.java  | 18 +++++++++++++++
 .../analyzer/v2/dsl/spec/filter/FilterSpec.java    | 20 +++++++++++-----
 .../v2/dsl/spec/parser/TextParserSpec.java         |  1 +
 .../v2/compiler/LALScriptExecutionTest.java        |  8 +++++++
 .../feature-cases/execution-basic.data.yaml        | 11 ++++++---
 .../cluster/DSLDebuggingClusterServiceImpl.java    |  1 +
 .../dsl/debugging/lal/LALDebugRecorderImpl.java    | 27 ++++++++++++++++------
 .../debugging/rest/DSLDebuggingRestHandler.java    |  6 +++++
 .../server/admin/dsl/debugging/session/Sample.java | 24 ++++++++++++++++++-
 .../src/main/proto/dsl-debugging-cluster.proto     |  4 ++++
 11 files changed, 104 insertions(+), 17 deletions(-)

diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 2bf842f8db..950d00b557 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -265,6 +265,7 @@
 * Push runtime-rule convergence to peers on commit. After a successful 
structural apply — and on the `commit_deferred` path, where the DB row is 
durable but this node's commit-tail threw — the main broadcasts a 
`NotifyApplied` admin-internal RPC so peers reconcile against the 
just-persisted DB row immediately, instead of waiting up to one refresh tick 
(~30s) to notice it. The fan-out runs off the REST response thread 
(fire-and-forget on a daemon executor) so an unreachable peer's per-cal [...]
 * Fix BanyanDB peer nodes permanently flooding `<metric> is not registered`, 
and a follow-on case where a peer kept translating writes with a stale schema 
shape after a runtime-rule reshape, when a node held a live persist worker but 
its local `MetadataRegistry` schema cache was missing or stale for that model — 
a `withoutSchemaChange` peer apply or a runtime-rule bundled fall-over rebuilt 
the dispatch worker but skipped the local-cache populate, and the registry was 
insert-only (never e [...]
 * Support LAL `json {}` parsing JSON content delivered in a plain-text log 
body. The parser reads the native protocol's JSON body first; when that is 
empty, it tries the text body as JSON — e.g. the OTLP log receiver maps every 
OTLP string body to a text body, even JSON-shaped ones, so previously-aborting 
`json {}` rules on OTLP-fed layers now work without any receiver or protocol 
change. On a successful parse from a text body, the matching rule persists the 
log as a JSON body with conte [...]
+* Surface the drop reason in LAL live-debugging. When a LAL rule stops a log 
at a parse step (a `json {}` / `yaml {}` parse failure, a `text { regexp }` 
non-match, or a non-log-body input), the recorder now captures a human-readable 
`reason` (e.g. the parse exception) onto the DSL-debug `Sample`, exposed 
through the `dsl-debugging` REST session response and the cluster forward 
proto. Previously a live-debug watcher could only see `continueOn=false` — that 
a step stopped, never why — and  [...]
 * Fix a v2 MAL `CounterWindow` key collision: `rate()` / `increase()` / 
`irate()` keyed each counter's sliding window on the rule's output metric name 
(the same for every input metric of a rule) instead of the counter's own name, 
so two or more counters that reduce to the same label set after `.sum(...)` 
shared one window and computed rates against each other's values — fabricating 
non-zero rates from unchanged counters (e.g. the BanyanDB liaison gRPC error 
rate read a steady non-zero of [...]
 * Fix the v2 MAL Elvis operator `?:` to honor Groovy-falsy semantics. It 
compiled to `Optional.ofNullable(primary).orElse(fallback)`, applying the 
fallback only when the primary is `null`, so an empty-string primary kept `""` 
instead — e.g. a BanyanDB liaison `ServiceInstance` stored `node_type=""` 
rather than `n/a`, because `.sum([...,'node_type'])` fills an absent group-by 
label with `""`. The fallback now applies for falsy primaries such as null, 
false, numeric zero, and empty strings [...]
 * SWIP-15: rebuild BanyanDB self-observability around the cluster / container 
/ group model (requires BanyanDB 0.11+). A BanyanDB cluster is modeled as one 
`Service`, each container as a `ServiceInstance` (role/tier as attributes), and 
each storage group as an `Endpoint`. The `otel-rules/banyandb/` rules are 
category-separated by role (`node_*` / `liaison_*` / `data_*` / `lifecycle_*`) 
and by data type (`measure_*` / `stream_*` / `trace_*` / `property_*`), 
mirroring the upstream FODC-pro [...]
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 8e98c5b6a0..44c7985248 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
@@ -57,6 +57,7 @@ public class ExecutionContext {
     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";
+    public static final String KEY_DROP_REASON = "drop_reason";
 
     private final Map<String, Object> properties = new HashMap<>();
 
@@ -112,6 +113,7 @@ public class ExecutionContext {
         setProperty(KEY_METRICS_CONTAINER, null);
         setProperty(KEY_DRY_RUN, false);
         setProperty(KEY_OUTPUT, null);
+        setProperty(KEY_DROP_REASON, null);
         return this;
     }
 
@@ -174,6 +176,22 @@ public class ExecutionContext {
         return (boolean) getProperty(KEY_ABORT);
     }
 
+    /**
+     * Record a human-readable reason the pipeline stopped/dropped at the 
current step
+     * (e.g. a parse-failure message). Read by the DSL-debug recorder to 
populate
+     * {@code Sample.reason} so a live-debug watcher sees WHY a log was 
dropped, not just
+     * that it was. Independent of {@link #abort()}: callers set the reason 
then abort (or,
+     * on the continue-on-failure path, set it without aborting).
+     */
+    public ExecutionContext dropReason(final String reason) {
+        setProperty(KEY_DROP_REASON, reason);
+        return this;
+    }
+
+    public String dropReason() {
+        return (String) getProperty(KEY_DROP_REASON);
+    }
+
     public ExecutionContext metricsContainer(final List<SampleFamily> 
container) {
         setProperty(KEY_METRICS_CONTAINER, container);
         return this;
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 e1b95c719f..34c51a3133 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
@@ -157,7 +157,7 @@ public class FilterSpec extends AbstractSpec {
         }
         final Object rawInput = ctx.input();
         if (!(rawInput instanceof LogData.Builder)) {
-            abortOrContinueUnparsed(ctx, abortOnFailure);
+            abortOrContinueUnparsed(ctx, abortOnFailure, 
notLogBodyReason("json", rawInput));
             return;
         }
         try {
@@ -178,7 +178,7 @@ public class FilterSpec extends AbstractSpec {
             }
         } catch (final Exception e) {
             warnParseFailure(jsonWarnLimiter, "json", ctx, abortOnFailure, e);
-            abortOrContinueUnparsed(ctx, abortOnFailure);
+            abortOrContinueUnparsed(ctx, abortOnFailure, "json parse failed: " 
+ e);
         }
     }
 
@@ -194,7 +194,7 @@ public class FilterSpec extends AbstractSpec {
         }
         final Object rawInput = ctx.input();
         if (!(rawInput instanceof LogData.Builder)) {
-            abortOrContinueUnparsed(ctx, abortOnFailure);
+            abortOrContinueUnparsed(ctx, abortOnFailure, 
notLogBodyReason("yaml", rawInput));
             return;
         }
         try {
@@ -206,17 +206,20 @@ public class FilterSpec extends AbstractSpec {
             ctx.parsed(parsed);
         } catch (final Exception e) {
             warnParseFailure(yamlWarnLimiter, "yaml", ctx, abortOnFailure, e);
-            abortOrContinueUnparsed(ctx, abortOnFailure);
+            abortOrContinueUnparsed(ctx, abortOnFailure, "yaml parse failed: " 
+ e);
         }
     }
 
     /**
-     * Failed-parse epilogue: abort when the rule demands it; otherwise 
install a
+     * Failed-parse epilogue: record the drop reason (surfaced in live-debug 
via
+     * {@code Sample.reason}), then abort when the rule demands it; otherwise 
install a
      * metadata-only parsed map so downstream {@code parsed.*} reads stay 
null-safe on
      * the continuation path — without it the generated extractor would NPE on 
the null
      * map and drop the log despite {@code abortOnFailure false}.
      */
-    private void abortOrContinueUnparsed(final ExecutionContext ctx, final 
boolean abortOnFailure) {
+    private void abortOrContinueUnparsed(final ExecutionContext ctx, final 
boolean abortOnFailure,
+                                         final String reason) {
+        ctx.dropReason(reason);
         if (abortOnFailure) {
             ctx.abort();
             return;
@@ -226,6 +229,11 @@ public class FilterSpec extends AbstractSpec {
         ctx.parsed(parsed);
     }
 
+    private static String notLogBodyReason(final String parser, final Object 
rawInput) {
+        final String actual = rawInput == null ? "null" : 
rawInput.getClass().getSimpleName();
+        return parser + " parser: input is not a text/JSON log body (" + 
actual + ")";
+    }
+
     /**
      * Parse-failure logging shared by {@code json {}} / {@code yaml {}}: WARN 
when the
      * failure aborts the log (rate-limited, with the suppressed count 
reported on the
diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
index e8ee67037c..3ac0cbbbbf 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
@@ -50,6 +50,7 @@ public class TextParserSpec extends AbstractParserSpec {
         if (matched) {
             ctx.parsed(matcher);
         } else {
+            ctx.dropReason("text parser: regexp did not match the log body");
             if (abortOnFailure) {
                 final long suppressed = warnLimiter.acquire();
                 if (suppressed >= 0) {
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptExecutionTest.java
 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptExecutionTest.java
index 1cdca79c46..ef349ad002 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptExecutionTest.java
+++ 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptExecutionTest.java
@@ -52,6 +52,7 @@ import org.junit.jupiter.api.TestFactory;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
@@ -254,6 +255,13 @@ class LALScriptExecutionTest {
                     assertEquals(expected, bodyContentType(logBuilder),
                         ruleName + ": persisted body content type mismatch");
                     break;
+                case "dropReason": {
+                    final String reason = ctx.dropReason();
+                    assertNotNull(reason, ruleName + ": expected a drop reason 
on ctx");
+                    assertTrue(reason.contains(expected),
+                        ruleName + ": drop reason '" + reason + "' should 
contain '" + expected + "'");
+                    break;
+                }
                 default:
                     if (key.startsWith("tag.")) {
                         final String tagKey = key.substring(4);
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 066dd39033..243c7a4140 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
@@ -213,15 +213,18 @@ json-text-fallback:
     tag.env: prod
     contentType: JSON
 
-# Non-JSON text under the fallback still aborts (default abortOnFailure true).
+# Non-JSON text under the fallback still aborts (default abortOnFailure true), 
and the
+# drop reason is recorded on ctx so live-debug can surface WHY it dropped.
 json-text-fallback-invalid:
   body-type: text
   body: 'plain non-json log line'
   expect:
     abort: true
+    dropReason: "json parse failed"
 
 # abortOnFailure false: a json parse failure must NOT abort — the log flows 
through,
-# and parsed.* reads stay null-safe with metadata values as fallback.
+# and parsed.* reads stay null-safe with metadata values as fallback. The 
reason is
+# still recorded even though the pipeline continued.
 json-abort-on-failure-false:
   service: fallback-svc
   body-type: text
@@ -230,11 +233,13 @@ json-abort-on-failure-false:
     abort: false
     save: true
     service: fallback-svc
+    dropReason: "json parse failed"
 
-# abortOnFailure false on a text regexp: a no-match must NOT abort.
+# abortOnFailure false on a text regexp: a no-match must NOT abort, but 
records the reason.
 text-abort-on-failure-false:
   body-type: text
   body: 'a line that does not match the level pattern'
   expect:
     abort: false
     save: true
+    dropReason: "regexp did not match"
diff --git 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/cluster/DSLDebuggingClusterServiceImpl.java
 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/cluster/DSLDebuggingClusterServiceImpl.java
index f1badcd6e8..b6d18ff9fd 100644
--- 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/cluster/DSLDebuggingClusterServiceImpl.java
+++ 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/cluster/DSLDebuggingClusterServiceImpl.java
@@ -195,6 +195,7 @@ public final class DSLDebuggingClusterServiceImpl
             .setContinueOn(s.isContinueOn())
             .setPayloadJson(s.getPayloadJson() == null ? "{}" : 
s.getPayloadJson())
             .setSourceLine(s.getSourceLine())
+            .setReason(s.getReason() == null ? "" : s.getReason())
             .build();
     }
 }
diff --git 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/lal/LALDebugRecorderImpl.java
 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/lal/LALDebugRecorderImpl.java
index 202bfee8bd..071ef3c195 100644
--- 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/lal/LALDebugRecorderImpl.java
+++ 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/lal/LALDebugRecorderImpl.java
@@ -72,12 +72,12 @@ public final class LALDebugRecorderImpl extends 
AbstractDebugRecorder
         // subsequent samples render only the output (builder state) so the
         // wire doesn't repeat the raw input on every probe.
         publishCurrentExecution();
-        addSample(new Sample(Sample.TYPE_INPUT, "", continueOn(ctx), 
inputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_INPUT, "", ctx, inputPayload(ctx), 
sourceLine));
     }
 
     @Override
     public void appendParser(final String rule, final int sourceLine, final 
ExecutionContext ctx) {
-        addSample(new Sample(Sample.TYPE_FUNCTION, "", continueOn(ctx), 
outputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_FUNCTION, "", ctx, outputPayload(ctx), 
sourceLine));
     }
 
     @Override
@@ -87,7 +87,7 @@ public final class LALDebugRecorderImpl extends 
AbstractDebugRecorder
             // block-level synopsis to avoid noise.
             return;
         }
-        addSample(new Sample(Sample.TYPE_FUNCTION, "", continueOn(ctx), 
outputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_FUNCTION, "", ctx, outputPayload(ctx), 
sourceLine));
     }
 
     @Override
@@ -96,8 +96,8 @@ public final class LALDebugRecorderImpl extends 
AbstractDebugRecorder
         if (!statementGranularity) {
             return;
         }
-        addSample(new Sample(Sample.TYPE_FUNCTION, sourceText == null ? "" : 
sourceText,
-                             continueOn(ctx), outputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_FUNCTION, sourceText == null ? "" : 
sourceText,
+                         ctx, outputPayload(ctx), sourceLine));
     }
 
     @Override
@@ -106,14 +106,14 @@ public final class LALDebugRecorderImpl extends 
AbstractDebugRecorder
         // Terminal probe: append the output-type sample (sourceText empty,
         // type=output), then close. The output entity itself is captured in
         // the payload via ExecutionContext.outputPayloadJson().
-        addSample(new Sample(Sample.TYPE_OUTPUT, "", continueOn(ctx), 
outputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_OUTPUT, "", ctx, outputPayload(ctx), 
sourceLine));
         publishCurrentExecution();
     }
 
     @Override
     public void appendOutputMetric(final String rule, final int sourceLine, 
final ExecutionContext ctx,
                                    final SampleFamily family) {
-        addSample(new Sample(Sample.TYPE_OUTPUT, "", continueOn(ctx), 
outputPayload(ctx), sourceLine));
+        addSample(sample(Sample.TYPE_OUTPUT, "", ctx, outputPayload(ctx), 
sourceLine));
         publishCurrentExecution();
     }
 
@@ -121,6 +121,19 @@ public final class LALDebugRecorderImpl extends 
AbstractDebugRecorder
         return ctx == null || !ctx.shouldAbort();
     }
 
+    /** Build a sample and, when the step stopped the pipeline ({@code 
continueOn == false}),
+     *  stamp the drop reason off the context so a watcher sees WHY it 
stopped. */
+    private static Sample sample(final String type, final String sourceText,
+                                 final ExecutionContext ctx, final String 
payloadJson,
+                                 final int sourceLine) {
+        final boolean cont = continueOn(ctx);
+        final Sample s = new Sample(type, sourceText, cont, payloadJson, 
sourceLine);
+        if (!cont && ctx != null) {
+            s.setReason(ctx.dropReason());
+        }
+        return s;
+    }
+
     private static String inputPayload(final ExecutionContext ctx) {
         if (ctx == null) {
             return "{\"ctx\":\"null\"}";
diff --git 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
index d0f35b5614..a740071f6c 100644
--- 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
+++ 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/rest/DSLDebuggingRestHandler.java
@@ -589,6 +589,9 @@ public class DSLDebuggingRestHandler {
         }
         entry.addProperty("sourceText", s.getSourceText());
         entry.addProperty("continueOn", s.isContinueOn());
+        if (s.getReason() != null && !s.getReason().isEmpty()) {
+            entry.addProperty("reason", s.getReason());
+        }
         entry.add("payload", JsonParser.parseString(
             s.getPayloadJson() == null || s.getPayloadJson().isEmpty()
                 ? "{}" : s.getPayloadJson()));
@@ -622,6 +625,9 @@ public class DSLDebuggingRestHandler {
         }
         entry.addProperty("sourceText", s.getSourceText());
         entry.addProperty("continueOn", s.getContinueOn());
+        if (s.getReason() != null && !s.getReason().isEmpty()) {
+            entry.addProperty("reason", s.getReason());
+        }
         entry.add("payload", JsonParser.parseString(
             s.getPayloadJson() == null || s.getPayloadJson().isEmpty()
                 ? "{}" : s.getPayloadJson()));
diff --git 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/session/Sample.java
 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/session/Sample.java
index a4c8507d25..32b133d50e 100644
--- 
a/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/session/Sample.java
+++ 
b/oap-server/server-admin/dsl-debugging/src/main/java/org/apache/skywalking/oap/server/admin/dsl/debugging/session/Sample.java
@@ -20,6 +20,7 @@ package 
org.apache.skywalking.oap.server.admin.dsl.debugging.session;
 
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.Setter;
 
 /**
  * One step inside a single rule execution. Probes accumulate samples
@@ -53,16 +54,29 @@ import lombok.RequiredArgsConstructor;
  *       line; the REST response omits the field in that case (the
  *       parent {@link ExecutionRecord#getDsl()} carries the verbatim
  *       rule source for cross-reference).</li>
+ *   <li>{@code reason} — human-readable explanation of WHY the step
+ *       stopped the pipeline (a parse exception, a non-matching regexp,
+ *       an input-type mismatch). Optional and null on steps that
+ *       continued or that stopped without a recorded cause (e.g. an
+ *       explicit {@code abort {}}). Only LAL populates it today; the
+ *       shared 5-arg constructor is unchanged so MAL / OAL recorders
+ *       leave it null.</li>
  * </ul>
  */
 @Getter
 @RequiredArgsConstructor
 public final class Sample {
-    /** Coarse semantic step type: input / filter / function / aggregation / 
output. */
+    // Coarse semantic step type, shared across all three DSL recorders. Each 
DSL emits the
+    // subset that fits its pipeline shape (recorders in the sibling lal/ mal/ 
oal/ packages):
+    /** First event entering the rule. Used by LAL, MAL, OAL. */
     public static final String TYPE_INPUT = "input";
+    /** Filter-clause check (see {@code continueOn}). Used by MAL and OAL; LAL 
has no filter step. */
     public static final String TYPE_FILTER = "filter";
+    /** Chain transform — MAL sum/tag/etc., LAL parser/extractor statement. 
Used by LAL, MAL, OAL. */
     public static final String TYPE_FUNCTION = "function";
+    /** Terminal aggregation function. OAL only. */
     public static final String TYPE_AGGREGATION = "aggregation";
+    /** Terminal emit / sink / outputRecord. Used by LAL, MAL, OAL. */
     public static final String TYPE_OUTPUT = "output";
 
     private final String type;
@@ -71,6 +85,11 @@ public final class Sample {
     private final String payloadJson;
     private final int sourceLine;
 
+    /** Why this step stopped the pipeline; null when it continued or has no 
recorded cause.
+     *  Non-final optional add-on so the shared constructor stays 5-arg — see 
class Javadoc. */
+    @Setter
+    private String reason;
+
     /**
      * Char count this sample contributes to the session's reported {@code 
totalBytes}.
      * Reporting only — there is no byte-budget enforcement; sessions are 
bounded by
@@ -87,6 +106,9 @@ public final class Sample {
         if (payloadJson != null) {
             size += payloadJson.length();
         }
+        if (reason != null) {
+            size += reason.length();
+        }
         return size;
     }
 }
diff --git 
a/oap-server/server-admin/dsl-debugging/src/main/proto/dsl-debugging-cluster.proto
 
b/oap-server/server-admin/dsl-debugging/src/main/proto/dsl-debugging-cluster.proto
index ac24553cf9..1bed329ff7 100644
--- 
a/oap-server/server-admin/dsl-debugging/src/main/proto/dsl-debugging-cluster.proto
+++ 
b/oap-server/server-admin/dsl-debugging/src/main/proto/dsl-debugging-cluster.proto
@@ -149,6 +149,10 @@ message ClusterSample {
     // Coarse semantic step type — input / filter / function / aggregation /
     // output. Lets the UI group/colour samples without parsing source_text.
     string type = 5;
+    // Human-readable reason the step stopped the pipeline (parse exception,
+    // non-matching regexp, input-type mismatch). Empty when the step
+    // continued or stopped without a recorded cause. LAL-only today.
+    string reason = 6;
 }
 
 // One full pipeline execution — a single source/log/SampleFamily walked

Reply via email to