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/03/07 16:21:31 UTC

(skywalking) 01/01: LAL: replace slowSql/sampledTrace sub-DSLs with unified outputType mechanism

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

wusheng pushed a commit to branch feature/lal-unified-output-type
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit 68316aa68874ed42d9210a1cf8e0a4e0bca4f0dd
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun Mar 8 00:21:16 2026 +0800

    LAL: replace slowSql/sampledTrace sub-DSLs with unified outputType mechanism
    
    Remove hardcoded `slowSql {}` and `sampledTrace {}` blocks from the LAL grammar.
    Output types are now configured per-rule via `outputType` in YAML config, with
    SPI-based short name resolution (e.g., `outputType: SlowSQL`, `outputType: SampledTrace`).
    
    Key changes:
    - Add `LALOutputBuilder` SPI interface with `name()` for short name registry
    - Add `DatabaseSlowStatementBuilder` (SlowSQL) and `SampledTraceBuilder` (SampledTrace) as SPI implementations
    - Output fields are regular extractor assignments, validated against builder setters at compile time
    - Explicit `sink {}` block required for persistence (no sink = no save)
    - `RecordSinkListener` handles two output paths: AbstractLog (log) vs LALOutputBuilder (builder)
    - Update all bundled LAL scripts and test cases
    - Fix .gitignore for log-analyzer ANTLR4 gen/ directory
---
 .gitignore                                         |   4 +-
 docs/en/changes/changes.md                         |   9 +
 docs/en/concepts-and-designs/lal.md                | 356 ++++++++++++---------
 .../listener/DatabaseSlowStatementBuilder.java     |  43 ++-
 .../trace/parser/listener/SampledTraceBuilder.java |  48 ++-
 ...walking.oap.server.core.source.LALOutputBuilder |  15 +-
 oap-server/analyzer/log-analyzer/CLAUDE.md         |  75 +++--
 .../apache/skywalking/lal/rt/grammar/LALLexer.g4   |  12 -
 .../apache/skywalking/lal/rt/grammar/LALParser.g4  |  97 +-----
 .../log/analyzer/v2/compiler/LALBlockCodegen.java  | 214 ++++---------
 .../analyzer/v2/compiler/LALClassGenerator.java    |  40 ++-
 .../log/analyzer/v2/compiler/LALScriptModel.java   |  70 +---
 .../log/analyzer/v2/compiler/LALScriptParser.java  | 125 +-------
 .../skywalking/oap/log/analyzer/v2/dsl/DSL.java    |  17 +-
 .../oap/log/analyzer/v2/dsl/ExecutionContext.java  |  41 +--
 .../v2/dsl/spec/extractor/ExtractorSpec.java       | 118 -------
 .../extractor/sampledtrace/SampledTraceSpec.java   | 100 ------
 .../v2/dsl/spec/extractor/slowsql/SlowSqlSpec.java |  63 ----
 .../analyzer/v2/dsl/spec/filter/FilterSpec.java    |  15 +-
 .../oap/log/analyzer/v2/provider/LALConfig.java    |  14 +-
 .../provider/log/listener/LogFilterListener.java   | 107 +++++--
 .../v2/provider/log/listener/LogSinkListener.java  |  12 +
 .../provider/log/listener/RecordSinkListener.java  | 155 ++++++++-
 .../log/analyzer/v2/spi/LALSourceTypeProvider.java |  49 ++-
 .../v2/compiler/LALClassGeneratorTest.java         | 183 ++++-------
 .../v2/compiler/LALExpressionExecutionTest.java    | 103 +-----
 .../analyzer/v2/compiler/LALScriptParserTest.java  |  95 ++----
 .../v2/compiler/TestMeshLALSourceTypeProvider.java |   2 +-
 .../oap/server/core/source/AbstractLog.java        |  18 +-
 .../oap/server/core/source/LALOutputBuilder.java   |  59 ++++
 .../skywalking/oap/server/core/source/Log.java     |  12 -
 .../envoy/EnvoyHTTPLALSourceTypeProvider.java      |   2 +-
 .../src/main/resources/lal/envoy-als.yaml          |  57 ++--
 .../src/main/resources/lal/k8s-service.yaml        |  60 ++--
 .../src/main/resources/lal/mesh-dp.yaml            |  59 ++--
 .../src/main/resources/lal/mysql-slowsql.yaml      |  11 +-
 .../src/main/resources/lal/pgsql-slowsql.yaml      |  11 +-
 .../src/main/resources/lal/redis-slowsql.yaml      |  11 +-
 .../profiling/ebpf/network/kubernetes-values.yaml  |  65 ++--
 .../mal-lal-v1-v2-checker/CLAUDE.md                |   2 +-
 .../mal-lal-v1-v2-checker/pom.xml                  |   2 +-
 .../oap/server/checker/lal/LalBenchmark.java       |  26 +-
 .../oap/server/checker/lal/LalComparisonTest.java  | 300 +++++++----------
 .../checker/lal/TestMeshLALSourceTypeProvider.java |   2 +-
 .../feature-cases/execution-basic.data.yaml        |  21 --
 .../test-lal/feature-cases/execution-basic.yaml    |  18 --
 .../lal/test-lal/oap-cases/envoy-als.data.yaml     |  74 +----
 .../scripts/lal/test-lal/oap-cases/envoy-als.yaml  |  64 ++--
 .../lal/test-lal/oap-cases/k8s-service.data.yaml   | 118 +------
 .../lal/test-lal/oap-cases/k8s-service.yaml        |  60 ++--
 .../lal/test-lal/oap-cases/mesh-dp.data.yaml       |  85 +----
 .../scripts/lal/test-lal/oap-cases/mesh-dp.yaml    |  59 ++--
 .../lal/test-lal/oap-cases/mysql-slowsql.data.yaml |  11 +-
 .../lal/test-lal/oap-cases/mysql-slowsql.yaml      |  11 +-
 .../test-lal/oap-cases/network-profiling-e2e.yaml  |  65 ++--
 .../lal/test-lal/oap-cases/pgsql-slowsql.data.yaml |  11 +-
 .../lal/test-lal/oap-cases/pgsql-slowsql.yaml      |  11 +-
 .../lal/test-lal/oap-cases/redis-slowsql.data.yaml |  11 +-
 .../lal/test-lal/oap-cases/redis-slowsql.yaml      |  11 +-
 59 files changed, 1479 insertions(+), 2030 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0efd7f68c2..a40e4e2642 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,8 +18,8 @@ OALLexer.tokens
 .checkstyle
 .externalToolBuilders
 oap-server/oal-grammar/**/gen/
-oap-server/analyzer/log-analyzer/gen/
-oap-server/analyzer/meter-analyzer/gen/
+oap-server/analyzer/log-analyzer/**/gen/
+oap-server/analyzer/meter-analyzer/**/gen/
 MQELexer.tokens
 oap-server/mqe-grammar/**/gen/
 PromQLLexer.tokens
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 3598b3dcb2..e75f42f8f9 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -16,6 +16,15 @@
   - v1 (Groovy) and v2 (ANTLR4+Javassist) cross-version checker validates behavioral equivalence across 1,290+ expressions
   - JMH benchmarks confirm v2 runtime speedups: MAL execute ~6.8x, LAL compile ~39x / execute ~2.8x, Hierarchy execute ~2.6x faster than Groovy v1
   - Generated class names follow `{yamlFileName}_L{lineNo}_{ruleName}` pattern for all DSLs (MAL/LAL/Hierarchy) for stack trace traceability
+* **Breaking Change** — LAL: remove `slowSql {}` and `sampledTrace {}` sub-DSLs from the grammar. These are replaced by the configurable `outputType` mechanism:
+  - Set `outputType` at the rule level in YAML config to specify the output entity class. Use the short name registered by `LALOutputBuilder` SPI (e.g., `outputType: SlowSQL`, `outputType: SampledTrace`), or a fully qualified class name as fallback.
+  - `LALOutputBuilder` implementations are discovered via `ServiceLoader` and expose a `name()` method for short name resolution. Built-in types: `SlowSQL` (`DatabaseSlowStatementBuilder`), `SampledTrace` (`SampledTraceBuilder`).
+  - Output fields (e.g., `id`, `statement`, `latency`) are now regular field assignments in the extractor block, no longer wrapped in sub-DSL blocks.
+  - Custom output fields are validated against the output type's setters at compile time.
+  - An explicit `sink {}` block is now **required** for data to be persisted. Without `sink {}`, no data is saved — this applies to all LAL rules including those using `outputType`. In v1, `slowSql {}` and `sampledTrace {}` dispatched data as a side-effect inside the extractor; in v2, persistence is always handled by the sink pipeline.
+  - Output type resolution order: per-rule YAML `outputType` (short name via SPI or FQCN) > `LALSourceTypeProvider` SPI default > `Log.class`.
+  - All bundled LAL scripts (`mysql-slowsql.yaml`, `pgsql-slowsql.yaml`, `redis-slowsql.yaml`, `envoy-als.yaml`, `k8s-service.yaml`, `mesh-dp.yaml`) have been updated.
+  - Users with custom LAL scripts using `slowSql {}` or `sampledTrace {}` must migrate to the new syntax. See [LAL documentation](../concepts-and-designs/lal.md#output-type).
 * Fix E2E test metrics verify: make it failure if the metric values all null.
 * Support building, testing, and publishing with Java 25.
 * Add `CLAUDE.md` as AI assistant guide for the project.
diff --git a/docs/en/concepts-and-designs/lal.md b/docs/en/concepts-and-designs/lal.md
index 4dff550d3d..626b66f17d 100644
--- a/docs/en/concepts-and-designs/lal.md
+++ b/docs/en/concepts-and-designs/lal.md
@@ -65,12 +65,12 @@ We can add tags like following:
       ...
    }
 ]
-``` 
+```
 And we can use this method to get the value of the tag key `TEST_KEY`.
 ```
 filter {
     if (tag("TEST_KEY") == "TEST_VALUE") {
-         ...   
+         ...
     }
 }
 ```
@@ -145,6 +145,8 @@ filter {
 Extractors aim to extract metadata from the logs. The metadata can be a service name, a service instance name, an
 endpoint name, or even a trace ID, all of which can be associated with the existing traces and metrics.
 
+#### Standard fields
+
 - `service`
 
 `service` extracts the service name from the `parsed` result, and set it into the `LogData`, which will be persisted (if
@@ -222,7 +224,22 @@ filter {
 }
 ```
 
-- `metrics`
+#### Output fields
+
+When a rule declares a custom `outputType` (see [Output Type](#output-type)), the extractor can set fields specific to
+that output type. Any identifier in the extractor that is not a standard field (listed above) is treated as an
+**output field assignment**. The syntax is the same as standard fields:
+
+```
+fieldName valueExpression as Type
+```
+
+The LAL compiler validates at boot time that a matching setter exists on the output type class (e.g., `setStatement(String)`
+for field `statement`). If no setter is found, the OAP server will fail to start, ensuring early error detection.
+
+See [Slow SQL](#slow-sql-database-slow-statement) and [Sampled Trace](#sampled-trace-network-profiling) for concrete examples.
+
+#### `metrics`
 
 `metrics` extracts / generates metrics from the logs, and sends the generated metrics to the meter system. You may
 configure [MAL](mal.md) for further analysis of these metrics. The dedicated MAL config files are under
@@ -287,150 +304,13 @@ metrics:
     exp: http_response_time.sum(['le', 'service', 'instance']).increase('PT5M').histogram().histogram_percentile([50,75,90,95,99])
 ```
 
-- `slowSql`
-
-`slowSql` aims to convert LogData to DatabaseSlowStatement. It extracts data from `parsed` result and save them as DatabaseSlowStatement. SlowSql will not abort or edit logs, you can use other LAL for further processing.
-SlowSql will reuse `service`, `layer` and `timestamp` of extractor, so it is necessary to use `SlowSQL` after setting these.
-We require a log tag `"LOG_KIND" = "SLOW_SQL"` to make OAP distinguish slow SQL logs from other log reports.
-
-**Note**, slow SQL sampling would only flag this SQL in the candidate list. The OAP server would run statistic per service
-and only persistent the top 50 every 10(controlled by `topNReportPeriod: ${SW_CORE_TOPN_REPORT_PERIOD:10}`) minutes by default.  
-
-An example of JSON sent to OAP is as following:
-``` json
-[
-   {
-      "tags":{
-         "data":[
-            {
-               "key":"LOG_KIND",
-               "value":"SLOW_SQL"
-            }
-         ]
-      },
-      "layer":"MYSQL",
-      "body":{
-         "json":{
-            "json":"{\"time\":\"1663063011\",\"id\":\"cb92c1a5b-2691e-fb2f-457a-9c72a392d9ed\",\"service\":\"root[root]@[localhost]\",\"statement\":\"select sleep(2);\",\"layer\":\"MYSQL\",\"query_time\":2000}"
-         }
-      },
-      "service":"root[root]@[localhost]"
-   }
-]
-```
-
-- `statement`
-
-`statement` extracts the SQL statement from the `parsed` result, and set it into the `DatabaseSlowStatement`, which will be
-persisted (if not dropped) and is used to associate with TopNDatabaseStatement.
-
-- `latency`
-
-`latency` extracts the latency from the `parsed` result, and set it into the `DatabaseSlowStatement`, which will be
-persisted (if not dropped) and is used to associate with TopNDatabaseStatement.
-
-- `id`
-
-`id` extracts the id from the `parsed` result, and set it into the `DatabaseSlowStatement`, which will be persisted (if not
-dropped) and is used to associate with TopNDatabaseStatement.
-
-An example of LAL to distinguish slow logs:
-
-```
-filter {
-  json{
-  }
-  extractor{
-    layer parsed.layer as String
-    service parsed.service as String
-    timestamp parsed.time as String
-    if (tag("LOG_KIND") == "SLOW_SQL") {
-      slowSql {
-        id parsed.id as String
-        statement parsed.statement as String
-        latency parsed.query_time as Long
-      }
-    }
-  }
-}
-```
-- `sampledTrace`
-
-`sampledTrace` aims to convert LogData to SampledTrace Records. It extracts data from `parsed` result and save them as SampledTraceRecord. SampledTrace will not abort or edit logs, you can use other LAL for further processing.
-We require a log tag `"LOG_KIND" = "NET_PROFILING_SAMPLED_TRACE"` to make OAP distinguish slow trace logs from other log reports.
-An example of JSON sent to OAP is as following:
-``` json
-[
-   {
-      "tags":{
-         "data":[
-            {
-               "key":"LOG_KIND",
-               "value":"NET_PROFILING_SAMPLED_TRACE"
-            }
-         ]
-      },
-      "layer":"MESH",
-      "body":{
-         "json":{
-            "json":"{\"uri\":\"/provider\",\"reason\":\"slow\",\"latency\":2048,\"client_process\":{\"process_id\":\"c1519f4555ec11eda8df0242ac1d0002\",\"local\":false,\"address\":\"\"},\"server_process\":{\"process_id\":\"\",\"local\":false,\"address\":\"172.31.0.3:443\"},\"detect_point\":\"client\",\"component\":\"http\",\"ssl\":true}"
-         }
-      },
-      "service":"test-service",
-      "serviceInstance":"test-service-instance",
-      "timestamp": 1666916962406,
-   }
-]
-```
-Examples are as follows:
-
-```
-filter {
-    json {
-    }
-    if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-        sampledTrace {
-            latency parsed.latency as Long
-            uri parsed.uri as String
-            reason parsed.reason as String
-
-            if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-            } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-            } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-            }
-
-            if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-            } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-            } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-            }
-
-            detectPoint parsed.detect_point as String
-
-            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-            } else if (parsed.component as String == "http") {
-                componentId 49
-            } else if (parsed.ssl as Boolean) {
-                componentId 130
-            } else {
-                componentId 110
-            }
-        }
-    }
-}
-```
-
 ### Sink
 
-Sinks are the persistent layer of the LAL. By default, all the logs of each filter are persisted into the storage.
-However, some mechanisms allow you to selectively save some logs, or even drop all the logs after you've
-extracted useful information, such as metrics.
+Sinks are the persistent layer of the LAL. An explicit `sink {}` block is **required** for any data to be persisted.
+Without a `sink {}` block, no data is saved — this applies to all LAL rules, including those using custom `outputType`.
+
+Within the sink, you can use samplers, droppers, and enforcers to control which logs are persisted.
+An empty `sink {}` block means all logs are saved unconditionally.
 
 #### Sampler
 
@@ -552,3 +432,189 @@ filter {
     }
 }
 ```
+
+## Output Type
+
+By default, each LAL rule produces a `Log` source object that is persisted to storage. However, some use cases require
+transforming log data into a different entity type — for example, converting slow SQL logs into `DatabaseSlowStatement`
+records or network profiling logs into `SampledTraceRecord`. The `outputType` mechanism makes this configurable per rule,
+without requiring any DSL grammar changes.
+
+### Configuration
+
+Set `outputType` at the rule level in the YAML config. You can use the short name registered by
+the `LALOutputBuilder` SPI (recommended), or a fully qualified class name:
+
+```yaml
+rules:
+  - name: my-rule
+    layer: MYSQL
+    outputType: SlowSQL    # short name registered by DatabaseSlowStatementBuilder
+    dsl: |
+      filter {
+        // ...
+      }
+```
+
+### Resolution order
+
+The output type is resolved per-rule in the following priority:
+
+1. **Per-rule YAML config** — the `outputType` field shown above (highest priority).
+   Short names (no `.`) are resolved via `ServiceLoader<LALOutputBuilder>`; fully qualified class names
+   are resolved via `Class.forName()` as a fallback.
+2. **`LALSourceTypeProvider` SPI** — a default output type registered by receiver plugins for a specific layer
+3. **`Log.class`** — the fallback if not specified anywhere
+
+### Two output paths
+
+LAL supports two kinds of output types:
+
+| Output path | Base type | How it works |
+|---|---|---|
+| **Log path** | Subclass of `AbstractLog` | The sink populates standard log fields (service, instance, endpoint, tags, body, etc.) from `LogData` and persists via `SourceReceiver` |
+| **Builder path** | Implements `LALOutputBuilder` | The sink creates the builder, calls `init(LogData, NamingControl)` to pre-populate standard fields, applies output field values via setters, then calls `complete(SourceReceiver)` to validate and dispatch |
+
+The builder path is used when the output type implements the `LALOutputBuilder` interface. This is how SkyWalking's
+built-in slow SQL and sampled trace features work.
+
+### Built-in output types
+
+#### Slow SQL (Database Slow Statement)
+
+SkyWalking converts slow SQL logs into `DatabaseSlowStatement` records for database slow query analysis (MySQL, PostgreSQL, Redis, etc.).
+
+Use `outputType: SlowSQL` in your rule config.
+The available output fields are: `id`, `statement`, `latency`. Standard fields (`service`, `layer`, `timestamp`) are handled
+by the extractor as usual and pre-populated via `init()` from `LogData`.
+
+We require a log tag `"LOG_KIND" = "SLOW_SQL"` to make OAP distinguish slow SQL logs from other log reports.
+
+**Note**, slow SQL sampling would only flag this SQL in the candidate list. The OAP server would run statistic per service
+and only persistent the top 50 every 10(controlled by `topNReportPeriod: ${SW_CORE_TOPN_REPORT_PERIOD:10}`) minutes by default.
+
+See bundled LAL scripts for complete examples:
+[mysql-slowsql.yaml](../../../oap-server/server-starter/src/main/resources/lal/mysql-slowsql.yaml),
+[pgsql-slowsql.yaml](../../../oap-server/server-starter/src/main/resources/lal/pgsql-slowsql.yaml),
+[redis-slowsql.yaml](../../../oap-server/server-starter/src/main/resources/lal/redis-slowsql.yaml).
+
+#### Sampled Trace (Network Profiling)
+
+SkyWalking converts network profiling sampled trace logs into `SampledTraceRecord` for process-level network analysis.
+
+Use `outputType: SampledTrace` in your rule config.
+The available output fields are: `latency`, `uri`, `reason`, `processId`, `destProcessId`, `detectPoint`, `componentId`.
+
+We require a log tag `"LOG_KIND" = "NET_PROFILING_SAMPLED_TRACE"` to make OAP distinguish sampled trace logs from other log reports.
+
+See bundled LAL scripts for complete examples:
+[envoy-als.yaml](../../../oap-server/server-starter/src/main/resources/lal/envoy-als.yaml),
+[k8s-service.yaml](../../../oap-server/server-starter/src/main/resources/lal/k8s-service.yaml),
+[mesh-dp.yaml](../../../oap-server/server-starter/src/main/resources/lal/mesh-dp.yaml).
+
+### Extending: custom output types
+
+You can create custom output types to transform logs into any entity type for your own use cases. There are two approaches:
+
+#### Approach 1: Implement `LALOutputBuilder`
+
+Use this when you need full control over validation and dispatching, or when the output is not a simple `AbstractLog` subclass.
+
+1. Create a class that implements `org.apache.skywalking.oap.server.core.source.LALOutputBuilder`:
+
+```java
+public class MyCustomBuilder implements LALOutputBuilder {
+    public static final String NAME = "MyCustom";
+
+    @Setter @Getter
+    private String myField;
+    @Setter @Getter
+    private long myMetric;
+
+    public MyCustomBuilder() {} // no-arg constructor required
+
+    @Override
+    public String name() { return NAME; }
+
+    @Override
+    public void init(LogData logData, NamingControl namingControl) {
+        // Pre-populate from LogData (service, timestamp, traceId, etc.)
+    }
+
+    @Override
+    public void complete(SourceReceiver sourceReceiver) {
+        // Validate, build final Source/Record, and dispatch
+    }
+}
+```
+
+2. Register it as a `ServiceLoader` provider so the short name can be used in YAML config.
+   Create `META-INF/services/org.apache.skywalking.oap.server.core.source.LALOutputBuilder` containing:
+   ```
+   com.example.MyCustomBuilder
+   ```
+
+3. Reference it in your LAL YAML config by short name:
+
+```yaml
+rules:
+  - name: my-custom-rule
+    layer: GENERAL
+    outputType: MyCustom    # short name from name() method
+    dsl: |
+      filter {
+        json {}
+        extractor {
+          myField parsed.someField as String
+          myMetric parsed.someValue as Long
+        }
+        sink {}
+      }
+```
+
+The LAL compiler validates at boot time that `setMyField(String)` and `setMyMetric(Long)` exist on `MyCustomBuilder`.
+Fully qualified class names (e.g., `outputType: com.example.MyCustomBuilder`) are also supported as a fallback.
+
+#### Approach 2: Extend `AbstractLog`
+
+Use this for simpler cases where you want to produce a log-like record with additional fields:
+
+1. Create a subclass of `AbstractLog` with the extra fields.
+2. Set `outputType` to your subclass. Standard log fields are populated automatically.
+
+#### Custom input types
+
+For rules that receive structured protobuf data (not JSON/YAML log bodies), you can configure the input type
+so that `parsed.*` generates optimized direct getter calls:
+
+```yaml
+rules:
+  - name: my-proto-rule
+    layer: MY_LAYER
+    inputType: com.example.MyProtoMessage
+    dsl: |
+      filter {
+        extractor {
+          service parsed.serviceName as String
+          endpoint parsed.requestPath as String
+        }
+      }
+```
+
+Alternatively, register a `LALSourceTypeProvider` SPI implementation to set the default input and output types
+for an entire layer, so individual rules don't need to repeat the configuration:
+
+```java
+public class MyLayerSourceTypeProvider implements LALSourceTypeProvider {
+    @Override
+    public Layer layer() { return Layer.MY_LAYER; }
+
+    @Override
+    public Class<?> inputType() { return MyProtoMessage.class; }
+
+    @Override
+    public Class<? extends Source> outputType() { return MyCustomBuilder.class; }
+}
+```
+
+Register it in `META-INF/services/org.apache.skywalking.oap.log.analyzer.v2.spi.LALSourceTypeProvider`.
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
index 2b4c16d194..e39c2dc900 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/DatabaseSlowStatementBuilder.java
@@ -19,16 +19,21 @@
 package org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener;
 
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import lombok.Setter;
+import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.analysis.DownSampling;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
 import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
 import org.apache.skywalking.oap.server.core.config.NamingControl;
 import org.apache.skywalking.oap.server.core.source.DatabaseSlowStatement;
+import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
 
-@RequiredArgsConstructor
-public class DatabaseSlowStatementBuilder {
-    private final NamingControl namingControl;
+public class DatabaseSlowStatementBuilder implements LALOutputBuilder {
+    public static final String NAME = "SlowSQL";
+
+    private NamingControl namingControl;
 
     @Getter
     @Setter
@@ -55,6 +60,36 @@ public class DatabaseSlowStatementBuilder {
     @Setter
     private long timestamp;
 
+    public DatabaseSlowStatementBuilder() {
+    }
+
+    public DatabaseSlowStatementBuilder(final NamingControl namingControl) {
+        this.namingControl = namingControl;
+    }
+
+    @Override
+    public String name() {
+        return NAME;
+    }
+
+    @Override
+    public void init(final LogData logData, final NamingControl namingControl) {
+        this.namingControl = namingControl;
+        this.serviceName = logData.getService();
+        this.traceId = logData.getTraceContext().getTraceId();
+        this.timestamp = logData.getTimestamp();
+        this.timeBucket = TimeBucket.getTimeBucket(logData.getTimestamp(), DownSampling.Second);
+    }
+
+    @Override
+    public void complete(final SourceReceiver sourceReceiver) {
+        if (id == null || latency < 1 || statement == null) {
+            return;
+        }
+        prepare();
+        sourceReceiver.receive(toDatabaseSlowStatement());
+    }
+
     public void prepare() {
         this.serviceName = namingControl.formatServiceName(serviceName);
     }
diff --git a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/SampledTraceBuilder.java b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/SampledTraceBuilder.java
index 8a64d84eb9..8c4f8772c6 100644
--- a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/SampledTraceBuilder.java
+++ b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/SampledTraceBuilder.java
@@ -21,8 +21,9 @@ package org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.oap.server.core.analysis.DownSampling;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
 import org.apache.skywalking.oap.server.core.analysis.Layer;
@@ -31,15 +32,20 @@ import org.apache.skywalking.oap.server.core.analysis.manual.trace.SampledSlowTr
 import org.apache.skywalking.oap.server.core.analysis.manual.trace.SampledStatus4xxTraceRecord;
 import org.apache.skywalking.oap.server.core.analysis.manual.trace.SampledStatus5xxTraceRecord;
 import org.apache.skywalking.oap.server.core.analysis.record.Record;
+import org.apache.skywalking.oap.server.core.analysis.worker.RecordStreamProcessor;
 import org.apache.skywalking.oap.server.core.config.NamingControl;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
 import org.apache.skywalking.oap.server.core.source.DetectPoint;
 import org.apache.skywalking.oap.server.core.source.ISource;
+import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
 import org.apache.skywalking.oap.server.core.source.ProcessRelation;
+import org.apache.skywalking.oap.server.core.source.SourceReceiver;
 
-@RequiredArgsConstructor
-public class SampledTraceBuilder {
-    private final NamingControl namingControl;
+@Slf4j
+public class SampledTraceBuilder implements LALOutputBuilder {
+    public static final String NAME = "SampledTrace";
+
+    private NamingControl namingControl;
 
     @Setter
     @Getter
@@ -80,6 +86,40 @@ public class SampledTraceBuilder {
     @Getter
     private long timestamp;
 
+    public SampledTraceBuilder() {
+    }
+
+    public SampledTraceBuilder(final NamingControl namingControl) {
+        this.namingControl = namingControl;
+    }
+
+    @Override
+    public String name() {
+        return NAME;
+    }
+
+    @Override
+    public void init(final LogData logData, final NamingControl namingControl) {
+        this.namingControl = namingControl;
+        this.traceId = logData.getTraceContext().getTraceId();
+        this.serviceName = logData.getService();
+        this.serviceInstanceName = logData.getServiceInstance();
+        this.timestamp = logData.getTimestamp();
+    }
+
+    @Override
+    public void complete(final SourceReceiver sourceReceiver) {
+        if (Strings.isNullOrEmpty(traceId) || reason == null
+                || Strings.isNullOrEmpty(processId) || Strings.isNullOrEmpty(destProcessId)
+                || componentId <= 0 || detectPoint == null || timestamp <= 0) {
+            return;
+        }
+        validate();
+        final Record record = toRecord();
+        RecordStreamProcessor.getInstance().in(record);
+        sourceReceiver.receive(toEntity());
+    }
+
     public void validate() {
         Preconditions.checkArgument(!Strings.isNullOrEmpty(traceId), "traceId can't be empty");
         Preconditions.checkArgument(!Strings.isNullOrEmpty(uri), "uri can't be empty");
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml b/oap-server/analyzer/agent-analyzer/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.core.source.LALOutputBuilder
similarity index 71%
copy from test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml
copy to oap-server/analyzer/agent-analyzer/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.core.source.LALOutputBuilder
index 49d3f2ec27..3e5d4d60d2 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml
+++ b/oap-server/analyzer/agent-analyzer/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.core.source.LALOutputBuilder
@@ -1,3 +1,4 @@
+#
 # 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.
@@ -12,15 +13,7 @@
 # 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.
+#
 
-# Mock input data for mysql-slowsql.yaml rules.
-
-mysql-slowsql:
-  body-type: json
-  body: '{"layer":"MYSQL","service":"db-svc","time":"1609459200000","id":"slow-1","statement":"SELECT 1","query_time":500}'
-  tags:
-    LOG_KIND: SLOW_SQL
-  expect:
-    service: db-svc
-    layer: MYSQL
-    timestamp: 1609459200000
+org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.DatabaseSlowStatementBuilder
+org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder
diff --git a/oap-server/analyzer/log-analyzer/CLAUDE.md b/oap-server/analyzer/log-analyzer/CLAUDE.md
index ee16ef9e0c..a3b1dcb05f 100644
--- a/oap-server/analyzer/log-analyzer/CLAUDE.md
+++ b/oap-server/analyzer/log-analyzer/CLAUDE.md
@@ -49,9 +49,9 @@ oap-server/analyzer/log-analyzer/
     spec/sink/SamplerSpec.java          — Rate-limit sampler
 
   src/test/java/.../compiler/
-    LALScriptParserTest.java            — 20 parser tests
-    LALClassGeneratorTest.java          — 37 generator tests
-    LALExpressionExecutionTest.java     — 27 data-driven execution tests (from YAML + .input.data)
+    LALScriptParserTest.java            — 19 parser tests
+    LALClassGeneratorTest.java          — 35 generator tests
+    LALExpressionExecutionTest.java     — 25 data-driven execution tests (from YAML + .data.yaml)
 ```
 
 ## Package & Class Naming
@@ -76,7 +76,10 @@ The generator produces a single class per LAL script. Extractor and sink blocks
 
 Method naming: `_extractor`, `_extractor_2`, `_extractor_3` (no `_0` suffix for single methods).
 
-Sub-blocks (slowSql, sampledTrace, metrics, sampler, rateLimit) are inlined within their parent method.
+Sub-blocks (metrics, sampler, rateLimit) are inlined within their parent method.
+
+Note: `slowSql` and `sampledTrace` sub-DSLs have been removed from the grammar. Custom output
+fields are now handled via the `outputType` mechanism with `outputFieldStatement` grammar rule.
 
 ## Explicit Context Passing (No ThreadLocal)
 
@@ -110,24 +113,61 @@ The generator detects the parser type from the AST at compile time and generates
 | JSON/YAML | `parsed.service` | `h.mapVal("service")` |
 | JSON/YAML nested | `parsed.a.b` | `h.mapVal("a", "b")` |
 | TEXT (regexp) | `parsed.level` | `h.group("level")` |
-| NONE + extraLogType | `parsed.response.code` | `((ExtraLogType) h.ctx().extraLog()).getResponse().getCode()` |
-| NONE + no extraLogType | `parsed.service` | `h.ctx().log().getService()` (LogData.Builder fallback) |
+| NONE + inputType | `parsed.response.code` | `((ExtraLogType) h.ctx().extraLog()).getResponse().getCode()` |
+| NONE + no inputType | `parsed.service` | `h.ctx().log().getService()` (LogData.Builder fallback) |
 | log fields | `log.service` | `h.ctx().log().getService()` |
 | log trace | `log.traceContext.traceId` | `h.ctx().log().getTraceContext().getTraceId()` |
 | tags | `tag("KEY")` | `h.tagValue("KEY")` |
 
-### extraLogType and LALSourceTypeProvider SPI
+### inputType and LALSourceTypeProvider SPI
 
 For LAL rules with no DSL parser (`json{}`/`yaml{}`/`text{}`), the compiler needs a type to generate direct getter calls on `parsed.*` fields. Per-rule resolution order:
 
-1. **DSL parser** (`json{}`, `yaml{}`, `text{}`) — parser wins, extraLogType is ignored
-2. **Explicit `extraLogType`** in YAML rule config — FQCN string, resolved via `Class.forName()`
-3. **`LALSourceTypeProvider` SPI** — default extraLogType for a layer, discovered via `ServiceLoader`
+1. **DSL parser** (`json{}`, `yaml{}`, `text{}`) — parser wins, inputType is ignored
+2. **Explicit `inputType`** in YAML rule config — FQCN string, resolved via `Class.forName()`
+3. **`LALSourceTypeProvider` SPI** — default inputType for a layer, discovered via `ServiceLoader`
 4. **`LogData.Builder` fallback** — if none of the above, `parsed.*` generates getter chains on `LogData.Builder` with compile-time reflection validation. Fields not found on `LogData.Builder` cause `IllegalArgumentException` at boot.
 
 The SPI interface is in `org.apache.skywalking.oap.log.analyzer.v2.spi.LALSourceTypeProvider`. Receiver plugins implement it and register in `META-INF/services/`. Example: `EnvoyHTTPLALSourceTypeProvider` registers `HTTPAccessLogEntry` for `Layer.MESH`.
 
-A single YAML file can have rules with different input types (e.g., `envoy-als.yaml` has a proto-based rule and a `json{}` rule, both in layer MESH). Resolution is per-rule, not per-file.
+Resolution is per-rule, not per-file.
+
+### outputType — Configurable Output Entity
+
+The output entity type (`AbstractLog` subclass) produced by the LAL sink. Resolution order:
+
+1. **Per-rule YAML config** `outputType` field — FQCN string, highest priority
+2. **`LALSourceTypeProvider` SPI** `outputType()` — default for a layer
+3. **`Log.class`** — fallback if not specified anywhere
+
+inputType is per-layer (all rules share the same input proto), but outputType is per-rule
+(different rules in the same layer may produce different output types).
+
+### Output Field Assignments
+
+Custom fields specific to the output subclass are set via `outputFieldStatement` in the extractor:
+
+```
+extractor {
+  statement parsed.statement as String    // output field — stored in ctx.outputFields map
+  latency parsed.latency as Long          // output field
+  service parsed.service as String        // standard field — handled by ExtractorSpec
+}
+```
+
+Unknown identifiers in the extractor (not `service`, `instance`, `endpoint`, `layer`, etc.)
+are parsed as `OutputFieldAssignment`. The compiler generates `h.ctx().setOutputField("name", value)`.
+
+**Compile-time validation**: When `outputType` is set, the compiler validates that a matching
+setter exists on the output type class (e.g., `setStatement(String)` for field `statement`).
+If no setter is found, compilation fails with an `IllegalArgumentException` at boot.
+
+**Runtime application**: `RecordSinkListener.parse()` creates the output source via
+`outputType.newInstance()`, populates standard fields from LogData.Builder, then applies
+output fields from `ExecutionContext.outputFields()` via reflection (`applyOutputFields()`).
+
+Note: `slowSql {}` and `sampledTrace {}` sub-DSLs were removed. Custom output types use
+the `outputType` + output field mechanism instead of dedicated DSL blocks.
 
 ## Example
 
@@ -189,12 +229,12 @@ whereas `String.valueOf(null)` would produce the string `"null"`.
 test/script-cases/scripts/lal/test-lal/
   oap-cases/                     — copies of shipped LAL configs (each with .input.data)
   feature-cases/
-    execution-basic.yaml         — 17 LAL feature-coverage rules
-    execution-basic.input.data   — mock input + expected output per rule
+    execution-basic.yaml         — 16 LAL feature-coverage rules
+    execution-basic.data.yaml    — mock input + expected output per rule
 ```
 
 Each `.input.data` entry specifies `body-type`, `body`, optional `tags`, and `expect` assertions
-(service, instance, endpoint, layer, tags, abort, save, timestamp, sampledTrace fields).
+(service, instance, endpoint, layer, tags, abort, save, timestamp).
 
 ## LAL Input Data Mock Principles
 
@@ -221,7 +261,6 @@ rule-name:
       service: expected-svc          # Extracted service name
       layer: MESH                    # Extracted layer
       tag.status.code: "500"         # Extracted tag value
-      sampledTrace.traceId: trace-001  # SampledTrace field
 ```
 
 ### Principles
@@ -229,10 +268,8 @@ rule-name:
 1. **`body-type` determines parsing**: `json` → `json{}` block, `text` → `text{}` block, `none` → proto extraLog or raw LogData access.
 2. **`extra-log` for proto types**: When rules access `parsed.*` on protobuf types (e.g., `HTTPAccessLogEntry`), provide `proto-class` and `proto-json`. The test harness parses via `JsonFormat`.
 3. **`expect` section is mandatory**: Every entry must have `expect` with at least `save` and `abort`.
-4. **Enum fields as strings**: Fields like `sampledTrace.reason` and `sampledTrace.detectPoint` are enums. Expected values use enum names (e.g., `slow`, `client`) compared via `.name()`.
-5. **Tag assertions**: `tag.KEY` in expect asserts extracted tag values (e.g., `tag.status.code: "500"`).
-6. **SampledTrace assertions**: `sampledTrace.FIELD` asserts fields on `NativeSampledTrace` (traceId, processId, destProcessId, componentId, etc.).
-7. **v1 is the truth**: Both v1 (Groovy) and v2 (ANTLR4) must produce identical results. If v1 produces different output than expected, the expected data has a bug.
+4. **Tag assertions**: `tag.KEY` in expect asserts extracted tag values (e.g., `tag.status.code: "500"`).
+5. **v1 is the truth**: Both v1 (Groovy) and v2 (ANTLR4) must produce identical results. If v1 produces different output than expected, the expected data has a bug.
 
 ### Directory Structure
 
diff --git a/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALLexer.g4 b/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALLexer.g4
index 4df6f7113a..c6edd0ad60 100644
--- a/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALLexer.g4
+++ b/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALLexer.g4
@@ -41,23 +41,11 @@ SPAN_ID:       'spanId';
 TIMESTAMP:     'timestamp';
 TAG:           'tag';
 METRICS:       'metrics';
-SLOW_SQL:      'slowSql';
-SAMPLED_TRACE: 'sampledTrace';
 REGEXP:        'regexp';
 ABORT_ON_FAILURE: 'abortOnFailure';
 NAME:          'name';
 VALUE:         'value';
 LABELS:        'labels';
-ID:            'id';
-STATEMENT:     'statement';
-LATENCY:       'latency';
-URI:           'uri';
-REASON:        'reason';
-PROCESS_ID:    'processId';
-DEST_PROCESS_ID: 'destProcessId';
-DETECT_POINT:  'detectPoint';
-COMPONENT_ID:  'componentId';
-REPORT_SERVICE: 'reportService';
 
 // Keywords - sink statements
 SAMPLER:       'sampler';
diff --git a/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALParser.g4 b/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALParser.g4
index b11802a616..fa2f615e16 100644
--- a/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALParser.g4
+++ b/oap-server/analyzer/log-analyzer/src/main/antlr4/org/apache/skywalking/lal/rt/grammar/LALParser.g4
@@ -25,8 +25,6 @@
 //   json { abortOnFailure true }
 //   extractor { service parsed.service as String; tag 'key': value }
 //   metrics { name "metric_name"; value 1; labels key: val }
-//   slowSql { id parsed.id as String; statement parsed.statement as String; latency parsed.query_time as Long }
-//   sampledTrace { latency parsed.latency as Long; uri parsed.uri as String; ... }
 //   sink { sampler { rateLimit("id") { rpm 6000 } } }
 parser grammar LALParser;
 
@@ -128,9 +126,8 @@ extractorStatement
     | timestampStatement
     | tagStatement
     | metricsBlock
-    | slowSqlBlock
-    | sampledTraceBlock
     | ifStatement
+    | outputFieldStatement
     ;
 
 serviceStatement
@@ -165,6 +162,10 @@ timestampStatement
     : TIMESTAMP valueAccess typeCast? (COMMA STRING)?
     ;
 
+outputFieldStatement
+    : anyIdentifier valueAccess typeCast?
+    ;
+
 tagStatement
     : TAG tagMap
     | TAG STRING COLON valueAccess typeCast?
@@ -215,88 +216,6 @@ metricsValueStatement
     : VALUE valueAccess typeCast?
     ;
 
-// ==================== Slow SQL block ====================
-
-slowSqlBlock
-    : SLOW_SQL L_BRACE slowSqlContent R_BRACE
-    ;
-
-slowSqlContent
-    : slowSqlStatement*
-    ;
-
-slowSqlStatement
-    : slowSqlIdStatement
-    | slowSqlStatementStatement
-    | slowSqlLatencyStatement
-    ;
-
-slowSqlIdStatement
-    : ID valueAccess typeCast?
-    ;
-
-slowSqlStatementStatement
-    : STATEMENT valueAccess typeCast?
-    ;
-
-slowSqlLatencyStatement
-    : LATENCY valueAccess typeCast?
-    ;
-
-// ==================== Sampled trace block ====================
-
-sampledTraceBlock
-    : SAMPLED_TRACE L_BRACE sampledTraceContent R_BRACE
-    ;
-
-sampledTraceContent
-    : sampledTraceStatement*
-    ;
-
-sampledTraceStatement
-    : sampledTraceLatencyStatement
-    | sampledTraceUriStatement
-    | sampledTraceReasonStatement
-    | sampledTraceProcessIdStatement
-    | sampledTraceDestProcessIdStatement
-    | sampledTraceDetectPointStatement
-    | sampledTraceComponentIdStatement
-    | reportServiceStatement
-    | ifStatement
-    ;
-
-sampledTraceLatencyStatement
-    : LATENCY valueAccess typeCast?
-    ;
-
-sampledTraceUriStatement
-    : URI valueAccess typeCast?
-    ;
-
-sampledTraceReasonStatement
-    : REASON valueAccess typeCast?
-    ;
-
-sampledTraceProcessIdStatement
-    : PROCESS_ID valueAccess typeCast?
-    ;
-
-sampledTraceDestProcessIdStatement
-    : DEST_PROCESS_ID valueAccess typeCast?
-    ;
-
-sampledTraceDetectPointStatement
-    : DETECT_POINT valueAccess typeCast?
-    ;
-
-sampledTraceComponentIdStatement
-    : COMPONENT_ID valueAccess typeCast?
-    ;
-
-reportServiceStatement
-    : REPORT_SERVICE valueAccess typeCast?
-    ;
-
 // ==================== Sink block ====================
 
 sinkBlock
@@ -360,7 +279,6 @@ ifBody
     : filterStatement*
     | extractorStatement*
     | sinkStatement*
-    | sampledTraceStatement*
     | samplerContent
     ;
 
@@ -461,12 +379,9 @@ anyIdentifier
     : IDENTIFIER
     | SERVICE | INSTANCE | ENDPOINT | LAYER
     | TRACE_ID | SEGMENT_ID | SPAN_ID | TIMESTAMP
-    | TAG | METRICS | SLOW_SQL | SAMPLED_TRACE
+    | TAG | METRICS
     | REGEXP | ABORT_ON_FAILURE
     | NAME | VALUE | LABELS
-    | ID | STATEMENT | LATENCY
-    | URI | REASON | PROCESS_ID | DEST_PROCESS_ID
-    | DETECT_POINT | COMPONENT_ID | REPORT_SERVICE
     | SAMPLER | RATE_LIMIT | RPM | ENFORCER | DROPPER
     | TEXT | JSON | YAML | FILTER | EXTRACTOR | SINK | ABORT
     ;
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java
index e0b0d54605..8ed2b26db1 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java
@@ -72,8 +72,8 @@ final class LALBlockCodegen {
         lvtVars.add(new String[]{"h", "L" + H.replace('.', '/') + ";"});
 
         if (genCtx.usedProtoAccess) {
-            if (genCtx.extraLogType != null) {
-                final String elTypeName = genCtx.extraLogType.getName();
+            if (genCtx.inputType != null) {
+                final String elTypeName = genCtx.inputType.getName();
                 body.append("  ").append(elTypeName).append(" _p = (")
                     .append(elTypeName).append(") h.ctx().extraLog();\n");
                 lvtVars.add(new String[]{"_p",
@@ -119,11 +119,9 @@ final class LALBlockCodegen {
                 generateIfBlockInExtractor(sb, (LALScriptModel.IfBlock) stmt, genCtx);
             } else if (stmt instanceof LALScriptModel.MetricsBlock) {
                 generateMetricsInline(sb, (LALScriptModel.MetricsBlock) stmt, genCtx);
-            } else if (stmt instanceof LALScriptModel.SlowSqlBlock) {
-                generateSlowSqlInline(sb, (LALScriptModel.SlowSqlBlock) stmt, genCtx);
-            } else if (stmt instanceof LALScriptModel.SampledTraceBlock) {
-                generateSampledTraceInline(sb,
-                    (LALScriptModel.SampledTraceBlock) stmt, genCtx);
+            } else if (stmt instanceof LALScriptModel.OutputFieldAssignment) {
+                generateOutputFieldAssignment(
+                    sb, (LALScriptModel.OutputFieldAssignment) stmt, genCtx);
             }
         }
     }
@@ -199,165 +197,71 @@ final class LALBlockCodegen {
         sb.append("  } }\n");
     }
 
-    // ==================== SlowSql inline ====================
+    // ==================== Tag assignment ====================
 
-    static void generateSlowSqlInline(
-            final StringBuilder sb,
-            final LALScriptModel.SlowSqlBlock block,
-            final LALClassGenerator.GenCtx genCtx) {
-        sb.append("  _e.prepareSlowSql(h.ctx());\n");
-        if (block.getId() != null) {
-            sb.append("  _e.slowSqlSpec().id(h.ctx(), ");
-            generateCastedValueAccess(sb, block.getId(), block.getIdCast(), genCtx);
-            sb.append(");\n");
-        }
-        if (block.getStatement() != null) {
-            sb.append("  _e.slowSqlSpec().statement(h.ctx(), ");
-            generateCastedValueAccess(sb, block.getStatement(),
-                block.getStatementCast(), genCtx);
+    static void generateTagAssignment(final StringBuilder sb,
+                                       final LALScriptModel.TagAssignment tag,
+                                       final LALClassGenerator.GenCtx genCtx) {
+        for (final Map.Entry<String, LALScriptModel.TagValue> entry
+                : tag.getTags().entrySet()) {
+            sb.append("  _e.tag(h.ctx(), \"")
+              .append(LALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
+            generateStringValueAccess(sb, entry.getValue().getValue(),
+                entry.getValue().getCastType(), genCtx);
             sb.append(");\n");
         }
-        if (block.getLatency() != null) {
-            sb.append("  _e.slowSqlSpec().latency(h.ctx(), Long.valueOf(h.toLong(");
-            generateValueAccess(sb, block.getLatency(), genCtx);
-            sb.append(")));\n");
-        }
-        sb.append("  _e.submitSlowSql(h.ctx());\n");
     }
 
-    // ==================== SampledTrace inline ====================
-
-    static void generateSampledTraceInline(
-            final StringBuilder sb,
-            final LALScriptModel.SampledTraceBlock block,
-            final LALClassGenerator.GenCtx genCtx) {
-        sb.append("  _e.prepareSampledTrace(h.ctx());\n");
-        generateSampledTraceBody(sb, block.getStatements(), genCtx);
-        sb.append("  _e.submitSampledTrace(h.ctx());\n");
-    }
+    // ==================== Output field assignment ====================
 
-    static void generateSampledTraceBody(
+    static void generateOutputFieldAssignment(
             final StringBuilder sb,
-            final List<LALScriptModel.SampledTraceStatement> stmts,
+            final LALScriptModel.OutputFieldAssignment field,
             final LALClassGenerator.GenCtx genCtx) {
-        for (final LALScriptModel.SampledTraceStatement stmt : stmts) {
-            if (stmt instanceof LALScriptModel.SampledTraceField) {
-                generateSampledTraceField(sb, (LALScriptModel.SampledTraceField) stmt,
-                    genCtx);
-            } else if (stmt instanceof LALScriptModel.IfBlock) {
-                generateSampledTraceIfBlock(sb, (LALScriptModel.IfBlock) stmt, genCtx);
+        // Compile-time validation: if outputType is set, verify the setter exists
+        if (genCtx.outputType != null) {
+            final String setterName = "set"
+                + Character.toUpperCase(field.getFieldName().charAt(0))
+                + field.getFieldName().substring(1);
+            if (findSetter(genCtx.outputType, setterName) == null) {
+                throw new IllegalArgumentException(
+                    "Output type " + genCtx.outputType.getName()
+                    + " has no setter " + setterName
+                    + "() for output field '" + field.getFieldName() + "'");
             }
         }
-    }
-
-    static void generateSampledTraceField(
-            final StringBuilder sb,
-            final LALScriptModel.SampledTraceField field,
-            final LALClassGenerator.GenCtx genCtx) {
-        switch (field.getFieldType()) {
-            case LATENCY:
-                sb.append("  _e.sampledTraceSpec().latency(h.ctx(), Long.valueOf(h.toLong(");
-                generateValueAccess(sb, field.getValue(), genCtx);
-                sb.append(")));\n");
-                return;
-            case COMPONENT_ID:
-                sb.append("  _e.sampledTraceSpec().componentId(h.ctx(), h.toInt(");
-                generateValueAccess(sb, field.getValue(), genCtx);
-                sb.append("));\n");
-                return;
-            case URI:
-                sb.append("  _e.sampledTraceSpec().uri(h.ctx(), ");
-                break;
-            case REASON:
-                sb.append("  _e.sampledTraceSpec().reason(h.ctx(), ");
-                break;
-            case PROCESS_ID:
-                sb.append("  _e.sampledTraceSpec().processId(h.ctx(), ");
-                break;
-            case DEST_PROCESS_ID:
-                sb.append("  _e.sampledTraceSpec().destProcessId(h.ctx(), ");
-                break;
-            case DETECT_POINT:
-                sb.append("  _e.sampledTraceSpec().detectPoint(h.ctx(), ");
-                break;
-            case REPORT_SERVICE:
-                sb.append("  _e.sampledTraceSpec().")
-                  .append(field.getFieldType().name().toLowerCase())
-                  .append("(h.ctx(), ");
-                break;
-            default:
-                return;
+        sb.append("  h.ctx().setOutputField(\"")
+          .append(LALCodegenHelper.escapeJava(field.getFieldName()))
+          .append("\", ");
+        // Box primitives for Object parameter — toLong() returns long, toInt() returns int
+        final String castType = field.getCastType();
+        if ("Long".equals(castType)) {
+            sb.append("Long.valueOf(");
+            generateCastedValueAccess(sb, field.getValue(), castType, genCtx);
+            sb.append(")");
+        } else if ("Integer".equals(castType)) {
+            sb.append("Integer.valueOf(");
+            generateCastedValueAccess(sb, field.getValue(), castType, genCtx);
+            sb.append(")");
+        } else {
+            generateCastedValueAccess(sb, field.getValue(), castType, genCtx);
         }
-        generateCastedValueAccess(sb, field.getValue(), field.getCastType(), genCtx);
         sb.append(");\n");
     }
 
-    static void generateSampledTraceIfBlock(
-            final StringBuilder sb,
-            final LALScriptModel.IfBlock ifBlock,
-            final LALClassGenerator.GenCtx genCtx) {
-        sb.append("  if (");
-        generateCondition(sb, ifBlock.getCondition(), genCtx);
-        sb.append(") {\n");
-        generateSampledTraceBodyFromFilterStmts(sb, ifBlock.getThenBranch(), genCtx);
-        sb.append("  }\n");
-        if (!ifBlock.getElseBranch().isEmpty()) {
-            sb.append("  else {\n");
-            generateSampledTraceBodyFromFilterStmts(sb, ifBlock.getElseBranch(), genCtx);
-            sb.append("  }\n");
-        }
-    }
-
-    static void generateSampledTraceBodyFromFilterStmts(
-            final StringBuilder sb,
-            final List<? extends LALScriptModel.FilterStatement> stmts,
-            final LALClassGenerator.GenCtx genCtx) {
-        for (final LALScriptModel.FilterStatement stmt : stmts) {
-            if (stmt instanceof LALScriptModel.SampledTraceField) {
-                generateSampledTraceField(sb,
-                    (LALScriptModel.SampledTraceField) stmt, genCtx);
-            } else if (stmt instanceof LALScriptModel.FieldAssignment) {
-                generateSampledTraceFieldFromAssignment(sb,
-                    (LALScriptModel.FieldAssignment) stmt, genCtx);
-            } else if (stmt instanceof LALScriptModel.IfBlock) {
-                generateSampledTraceIfBlock(sb, (LALScriptModel.IfBlock) stmt, genCtx);
+    private static java.lang.reflect.Method findSetter(
+            final Class<?> clazz, final String setterName) {
+        Class<?> c = clazz;
+        while (c != null && c != Object.class) {
+            for (final java.lang.reflect.Method m : c.getDeclaredMethods()) {
+                if (m.getName().equals(setterName)
+                        && m.getParameterCount() == 1) {
+                    return m;
+                }
             }
+            c = c.getSuperclass();
         }
-    }
-
-    static void generateSampledTraceFieldFromAssignment(
-            final StringBuilder sb,
-            final LALScriptModel.FieldAssignment fa,
-            final LALClassGenerator.GenCtx genCtx) {
-        switch (fa.getFieldType()) {
-            case TIMESTAMP:
-                sb.append("  _e.sampledTraceSpec().latency(h.ctx(), Long.valueOf(h.toLong(");
-                generateValueAccess(sb, fa.getValue(), genCtx);
-                sb.append(")));\n");
-                break;
-            default:
-                sb.append("  _e.sampledTraceSpec().")
-                  .append(fa.getFieldType().name().toLowerCase())
-                  .append("(h.ctx(), ");
-                generateCastedValueAccess(sb, fa.getValue(), fa.getCastType(), genCtx);
-                sb.append(");\n");
-                break;
-        }
-    }
-
-    // ==================== Tag assignment ====================
-
-    static void generateTagAssignment(final StringBuilder sb,
-                                       final LALScriptModel.TagAssignment tag,
-                                       final LALClassGenerator.GenCtx genCtx) {
-        for (final Map.Entry<String, LALScriptModel.TagValue> entry
-                : tag.getTags().entrySet()) {
-            sb.append("  _e.tag(h.ctx(), \"")
-              .append(LALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
-            generateStringValueAccess(sb, entry.getValue().getValue(),
-                entry.getValue().getCastType(), genCtx);
-            sb.append(");\n");
-        }
+        return null;
     }
 
     // ==================== Sink method generation ====================
@@ -387,8 +291,8 @@ final class LALBlockCodegen {
         lvtVars.add(new String[]{"h", "L" + H.replace('.', '/') + ";"});
 
         if (genCtx.usedProtoAccess) {
-            if (genCtx.extraLogType != null) {
-                final String elTypeName = genCtx.extraLogType.getName();
+            if (genCtx.inputType != null) {
+                final String elTypeName = genCtx.inputType.getName();
                 body.append("  ").append(elTypeName).append(" _p = (")
                     .append(elTypeName).append(") h.ctx().extraLog();\n");
                 lvtVars.add(new String[]{"_p",
@@ -947,11 +851,11 @@ final class LALBlockCodegen {
                 }
                 break;
             case NONE:
-                if (genCtx.extraLogType != null) {
-                    current = generateExtraLogAccess(fieldSegments, genCtx.extraLogType,
+                if (genCtx.inputType != null) {
+                    current = generateExtraLogAccess(fieldSegments, genCtx.inputType,
                         "_p", true, genCtx);
                 } else {
-                    // No parser and no extraLogType — fall back to LogData proto
+                    // No parser and no inputType — fall back to LogData proto
                     current = generateExtraLogAccess(fieldSegments, LogData.Builder.class,
                         "h.ctx().log()", false, genCtx);
                 }
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 74839f7f12..cc19b1329a 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
@@ -67,7 +67,8 @@ public final class LALClassGenerator {
     private final ClassPool classPool;
     private File classOutputDir;
     private String classNameHint;
-    private Class<?> extraLogType;
+    private Class<?> inputType;
+    private Class<?> outputType;
     private String yamlSource;
 
     // ==================== Parser type detection ====================
@@ -86,7 +87,8 @@ public final class LALClassGenerator {
 
     static class GenCtx {
         final ParserType parserType;
-        final Class<?> extraLogType;
+        final Class<?> inputType;
+        final Class<?> outputType;
         final List<PrivateMethod> privateMethods = new ArrayList<>();
         final Map<String, Integer> methodCounts = new HashMap<>();
 
@@ -96,7 +98,7 @@ public final class LALClassGenerator {
         String lastNullChecks;
         String lastRawChain;
 
-        // Per-method proto field variable caching (NONE + extraLogType only).
+        // Per-method proto field variable caching (NONE + inputType only).
         // Maps chain key ("response", "response.responseCode") to variable name ("_t0", "_t1").
         // Enables dedup: the same chain accessed multiple times reuses the same variable.
         final Map<String, String> protoVars = new HashMap<>();
@@ -105,9 +107,11 @@ public final class LALClassGenerator {
         int protoVarCounter;
         boolean usedProtoAccess;
 
-        GenCtx(final ParserType parserType, final Class<?> extraLogType) {
+        GenCtx(final ParserType parserType, final Class<?> inputType,
+               final Class<?> outputType) {
             this.parserType = parserType;
-            this.extraLogType = extraLogType;
+            this.inputType = inputType;
+            this.outputType = outputType;
         }
 
         String nextMethodName(final String prefix) {
@@ -171,8 +175,12 @@ public final class LALClassGenerator {
         this.classNameHint = hint;
     }
 
-    public void setExtraLogType(final Class<?> extraLogType) {
-        this.extraLogType = extraLogType;
+    public void setInputType(final Class<?> inputType) {
+        this.inputType = inputType;
+    }
+
+    public void setOutputType(final Class<?> outputType) {
+        this.outputType = outputType;
     }
 
     public void setYamlSource(final String yamlSource) {
@@ -399,11 +407,11 @@ public final class LALClassGenerator {
     public LalExpression compileFromModel(final LALScriptModel model) throws Exception {
         final String className = makeClassName("LalExpr_");
         final ParserType parserType = detectParserType(model.getStatements());
-        final GenCtx genCtx = new GenCtx(parserType, this.extraLogType);
+        final GenCtx genCtx = new GenCtx(parserType, this.inputType, this.outputType);
 
-        if (parserType == ParserType.NONE && this.extraLogType != null) {
-            log.info("LAL rule has no parser — using extraLogType {} for "
-                + "direct getter calls.", this.extraLogType.getName());
+        if (parserType == ParserType.NONE && this.inputType != null) {
+            log.info("LAL rule has no parser — using inputType {} for "
+                + "direct getter calls.", this.inputType.getName());
         }
 
         final String executeBody = generateExecuteMethod(model, genCtx);
@@ -438,9 +446,9 @@ public final class LALClassGenerator {
         execLvt.add(new String[]{"ctx", "L" + EXEC_CTX.replace('.', '/') + ";"});
         execLvt.add(new String[]{"h", "L" + H.replace('.', '/') + ";"});
         if (genCtx.usedProtoAccess) {
-            if (genCtx.extraLogType != null) {
+            if (genCtx.inputType != null) {
                 execLvt.add(new String[]{"_p",
-                    "L" + genCtx.extraLogType.getName().replace('.', '/') + ";"});
+                    "L" + genCtx.inputType.getName().replace('.', '/') + ";"});
             }
             execLvt.addAll(genCtx.protoLvtVars);
         }
@@ -494,8 +502,8 @@ public final class LALClassGenerator {
 
         // Insert _p + proto var declarations if any proto field access was used
         if (genCtx.usedProtoAccess) {
-            if (genCtx.extraLogType != null) {
-                final String elTypeName = genCtx.extraLogType.getName();
+            if (genCtx.inputType != null) {
+                final String elTypeName = genCtx.inputType.getName();
                 sb.append("  ").append(elTypeName).append(" _p = (")
                   .append(elTypeName).append(") h.ctx().extraLog();\n");
             }
@@ -568,7 +576,7 @@ public final class LALClassGenerator {
     public String generateSource(final String dsl) {
         final LALScriptModel model = LALScriptParser.parse(dsl);
         final GenCtx genCtx = new GenCtx(
-            detectParserType(model.getStatements()), this.extraLogType);
+            detectParserType(model.getStatements()), this.inputType, this.outputType);
         final String execute = generateExecuteMethod(model, genCtx);
         if (genCtx.privateMethods.isEmpty()) {
             return execute;
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptModel.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptModel.java
index 762e9cbd0b..e20232e56b 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptModel.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptModel.java
@@ -114,6 +114,21 @@ public final class LALScriptModel {
         }
     }
 
+    @Getter
+    public static final class OutputFieldAssignment implements ExtractorStatement, FilterStatement {
+        private final String fieldName;
+        private final ValueAccess value;
+        private final String castType;
+
+        public OutputFieldAssignment(final String fieldName,
+                                     final ValueAccess value,
+                                     final String castType) {
+            this.fieldName = fieldName;
+            this.value = value;
+            this.castType = castType;
+        }
+    }
+
     public enum FieldType {
         SERVICE, INSTANCE, ENDPOINT, LAYER,
         TRACE_ID, SEGMENT_ID, SPAN_ID, TIMESTAMP
@@ -163,59 +178,6 @@ public final class LALScriptModel {
         }
     }
 
-    @Getter
-    public static final class SlowSqlBlock implements ExtractorStatement, FilterStatement {
-        private final ValueAccess id;
-        private final String idCast;
-        private final ValueAccess statement;
-        private final String statementCast;
-        private final ValueAccess latency;
-        private final String latencyCast;
-
-        public SlowSqlBlock(final ValueAccess id, final String idCast,
-                            final ValueAccess statement, final String statementCast,
-                            final ValueAccess latency, final String latencyCast) {
-            this.id = id;
-            this.idCast = idCast;
-            this.statement = statement;
-            this.statementCast = statementCast;
-            this.latency = latency;
-            this.latencyCast = latencyCast;
-        }
-    }
-
-    @Getter
-    public static final class SampledTraceBlock implements ExtractorStatement, FilterStatement {
-        private final List<SampledTraceStatement> statements;
-
-        public SampledTraceBlock(final List<SampledTraceStatement> statements) {
-            this.statements = Collections.unmodifiableList(statements);
-        }
-    }
-
-    public interface SampledTraceStatement {
-    }
-
-    @Getter
-    public static final class SampledTraceField implements SampledTraceStatement, FilterStatement {
-        private final SampledTraceFieldType fieldType;
-        private final ValueAccess value;
-        private final String castType;
-
-        public SampledTraceField(final SampledTraceFieldType fieldType,
-                                 final ValueAccess value,
-                                 final String castType) {
-            this.fieldType = fieldType;
-            this.value = value;
-            this.castType = castType;
-        }
-    }
-
-    public enum SampledTraceFieldType {
-        LATENCY, URI, REASON, PROCESS_ID, DEST_PROCESS_ID,
-        DETECT_POINT, COMPONENT_ID, REPORT_SERVICE
-    }
-
     // ==================== Sink block ====================
 
     @Getter
@@ -295,7 +257,7 @@ public final class LALScriptModel {
 
     @Getter
     public static final class IfBlock implements FilterStatement, ExtractorStatement,
-            SinkStatement, SampledTraceStatement, SamplerContent {
+            SinkStatement, SamplerContent {
         private final Condition condition;
         private final List<FilterStatement> thenBranch;
         private final List<FilterStatement> elseBranch;
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParser.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParser.java
index 55644c7fe5..fd91feb3c5 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParser.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParser.java
@@ -55,7 +55,6 @@ import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.Sampler
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.SamplerContent;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.SinkBlock;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.SinkStatement;
-import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.SlowSqlBlock;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.StringConditionValue;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.TagAssignment;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALScriptModel.TagValue;
@@ -227,11 +226,8 @@ public final class LALScriptParser {
         if (ctx.metricsBlock() != null) {
             return visitMetricsBlock(ctx.metricsBlock());
         }
-        if (ctx.slowSqlBlock() != null) {
-            return visitSlowSqlBlock(ctx.slowSqlBlock());
-        }
-        if (ctx.sampledTraceBlock() != null) {
-            return visitSampledTraceBlock(ctx.sampledTraceBlock());
+        if (ctx.outputFieldStatement() != null) {
+            return visitOutputFieldStatement(ctx.outputFieldStatement());
         }
         // ifStatement
         return (ExtractorStatement) visitIfStatement(ctx.ifStatement());
@@ -246,6 +242,14 @@ public final class LALScriptParser {
         return new FieldAssignment(type, va, cast, null);
     }
 
+    private static LALScriptModel.OutputFieldAssignment visitOutputFieldStatement(
+            final LALParser.OutputFieldStatementContext ctx) {
+        final String fieldName = ctx.anyIdentifier().getText();
+        final ValueAccess value = visitValueAccess(ctx.valueAccess());
+        final String castType = ctx.typeCast() != null ? extractCastType(ctx.typeCast()) : null;
+        return new LALScriptModel.OutputFieldAssignment(fieldName, value, castType);
+    }
+
     // ==================== Tag statement ====================
 
     private static TagAssignment visitTagStatement(final LALParser.TagStatementContext ctx) {
@@ -306,107 +310,6 @@ public final class LALScriptParser {
         return new MetricsBlock(name, timestampValue, timestampCast, labels, value, valueCast);
     }
 
-    // ==================== Slow SQL block ====================
-
-    private static SlowSqlBlock visitSlowSqlBlock(final LALParser.SlowSqlBlockContext ctx) {
-        ValueAccess id = null;
-        String idCast = null;
-        ValueAccess statement = null;
-        String statementCast = null;
-        ValueAccess latency = null;
-        String latencyCast = null;
-
-        for (final LALParser.SlowSqlStatementContext ssc :
-                ctx.slowSqlContent().slowSqlStatement()) {
-            if (ssc.slowSqlIdStatement() != null) {
-                id = visitValueAccess(ssc.slowSqlIdStatement().valueAccess());
-                idCast = ssc.slowSqlIdStatement().typeCast() != null
-                    ? extractCastType(ssc.slowSqlIdStatement().typeCast()) : null;
-            }
-            if (ssc.slowSqlStatementStatement() != null) {
-                statement = visitValueAccess(ssc.slowSqlStatementStatement().valueAccess());
-                statementCast = ssc.slowSqlStatementStatement().typeCast() != null
-                    ? extractCastType(ssc.slowSqlStatementStatement().typeCast()) : null;
-            }
-            if (ssc.slowSqlLatencyStatement() != null) {
-                latency = visitValueAccess(ssc.slowSqlLatencyStatement().valueAccess());
-                latencyCast = ssc.slowSqlLatencyStatement().typeCast() != null
-                    ? extractCastType(ssc.slowSqlLatencyStatement().typeCast()) : null;
-            }
-        }
-
-        return new SlowSqlBlock(id, idCast, statement, statementCast, latency, latencyCast);
-    }
-
-    // ==================== Sampled trace block ====================
-
-    private static LALScriptModel.SampledTraceBlock visitSampledTraceBlock(
-            final LALParser.SampledTraceBlockContext ctx) {
-        final List<LALScriptModel.SampledTraceStatement> stmts = new ArrayList<>();
-        for (final LALParser.SampledTraceStatementContext stc :
-                ctx.sampledTraceContent().sampledTraceStatement()) {
-            if (stc.ifStatement() != null) {
-                stmts.add((LALScriptModel.SampledTraceStatement) visitIfStatement(
-                    stc.ifStatement()));
-            } else {
-                stmts.add(visitSampledTraceField(stc));
-            }
-        }
-        return new LALScriptModel.SampledTraceBlock(stmts);
-    }
-
-    private static LALScriptModel.SampledTraceField visitSampledTraceField(
-            final LALParser.SampledTraceStatementContext ctx) {
-        if (ctx.sampledTraceLatencyStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.LATENCY,
-                ctx.sampledTraceLatencyStatement().valueAccess(),
-                ctx.sampledTraceLatencyStatement().typeCast());
-        }
-        if (ctx.sampledTraceUriStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.URI,
-                ctx.sampledTraceUriStatement().valueAccess(),
-                ctx.sampledTraceUriStatement().typeCast());
-        }
-        if (ctx.sampledTraceReasonStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.REASON,
-                ctx.sampledTraceReasonStatement().valueAccess(),
-                ctx.sampledTraceReasonStatement().typeCast());
-        }
-        if (ctx.sampledTraceProcessIdStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.PROCESS_ID,
-                ctx.sampledTraceProcessIdStatement().valueAccess(),
-                ctx.sampledTraceProcessIdStatement().typeCast());
-        }
-        if (ctx.sampledTraceDestProcessIdStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.DEST_PROCESS_ID,
-                ctx.sampledTraceDestProcessIdStatement().valueAccess(),
-                ctx.sampledTraceDestProcessIdStatement().typeCast());
-        }
-        if (ctx.sampledTraceDetectPointStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.DETECT_POINT,
-                ctx.sampledTraceDetectPointStatement().valueAccess(),
-                ctx.sampledTraceDetectPointStatement().typeCast());
-        }
-        if (ctx.sampledTraceComponentIdStatement() != null) {
-            return makeSampledField(LALScriptModel.SampledTraceFieldType.COMPONENT_ID,
-                ctx.sampledTraceComponentIdStatement().valueAccess(),
-                ctx.sampledTraceComponentIdStatement().typeCast());
-        }
-        // reportService
-        return makeSampledField(LALScriptModel.SampledTraceFieldType.REPORT_SERVICE,
-            ctx.reportServiceStatement().valueAccess(),
-            ctx.reportServiceStatement().typeCast());
-    }
-
-    private static LALScriptModel.SampledTraceField makeSampledField(
-            final LALScriptModel.SampledTraceFieldType type,
-            final LALParser.ValueAccessContext vaCtx,
-            final LALParser.TypeCastContext tcCtx) {
-        return new LALScriptModel.SampledTraceField(
-            type, visitValueAccess(vaCtx),
-            tcCtx != null ? extractCastType(tcCtx) : null);
-    }
-
     // ==================== Sink block ====================
 
     private static SinkBlock visitSinkBlock(final LALParser.SinkBlockContext ctx) {
@@ -500,14 +403,6 @@ public final class LALScriptParser {
                 stmts.add((FilterStatement) new DropperStatement());
             }
         }
-        for (final LALParser.SampledTraceStatementContext stc :
-                ctx.sampledTraceStatement()) {
-            if (stc.ifStatement() != null) {
-                stmts.add((FilterStatement) visitIfStatement(stc.ifStatement()));
-            } else {
-                stmts.add((FilterStatement) visitSampledTraceField(stc));
-            }
-        }
         // Handle samplerContent alternative (rateLimit blocks inside if within sampler)
         final LALParser.SamplerContentContext sc = ctx.samplerContent();
         if (sc != null) {
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 24e6936dfa..09a2642ed9 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
@@ -24,6 +24,7 @@ import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALClassGenerator;
 import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.filter.FilterSpec;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
+import org.apache.skywalking.oap.server.core.source.Log;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 
@@ -46,30 +47,34 @@ public class DSL {
     public static DSL of(final ModuleManager moduleManager,
                          final LogAnalyzerModuleConfig config,
                          final String dsl) throws ModuleStartException {
-        return of(moduleManager, config, dsl, null, "unknown", null);
+        return of(moduleManager, config, dsl, null, null, "unknown", null);
     }
 
     public static DSL of(final ModuleManager moduleManager,
                          final LogAnalyzerModuleConfig config,
                          final String dsl,
-                         final Class<?> extraLogType,
+                         final Class<?> inputType,
+                         final Class<?> outputType,
                          final String ruleName) throws ModuleStartException {
-        return of(moduleManager, config, dsl, extraLogType, ruleName, null);
+        return of(moduleManager, config, dsl, inputType, outputType, ruleName, null);
     }
 
     public static DSL of(final ModuleManager moduleManager,
                          final LogAnalyzerModuleConfig config,
                          final String dsl,
-                         final Class<?> extraLogType,
+                         final Class<?> inputType,
+                         final Class<?> outputType,
                          final String ruleName,
                          final String yamlSource) throws ModuleStartException {
         try {
             final LALClassGenerator generator = new LALClassGenerator();
-            generator.setExtraLogType(extraLogType);
+            generator.setInputType(inputType);
+            generator.setOutputType(outputType);
             generator.setClassNameHint(ruleName);
             generator.setYamlSource(yamlSource);
             final LalExpression expression = generator.compile(dsl);
-            final FilterSpec filterSpec = new FilterSpec(moduleManager, config);
+            final Class<?> resolvedOutput = outputType != null ? outputType : Log.class;
+            final FilterSpec filterSpec = new FilterSpec(moduleManager, config, resolvedOutput);
             return new DSL(ruleName, expression, filterSpec);
         } catch (Exception e) {
             throw new ModuleStartException(
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 449a13cc80..0508f9ac97 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
@@ -27,9 +27,7 @@ import java.util.regex.Matcher;
 import lombok.Getter;
 import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.DatabaseSlowStatementBuilder;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder;
-import org.apache.skywalking.oap.server.core.source.Log;
+import org.apache.skywalking.oap.server.core.source.AbstractLog;
 
 /**
  * Mutable property storage for a single LAL script execution cycle.
@@ -51,8 +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_LOG_CONTAINER = "log_container";
-    public static final String KEY_DATABASE_SLOW_STATEMENT = "database_slow_statement";
-    public static final String KEY_SAMPLED_TRACE = "sampled_trace";
+    public static final String KEY_OUTPUT_FIELDS = "output_fields";
 
     private final Map<String, Object> properties = new HashMap<>();
 
@@ -74,6 +71,7 @@ public class ExecutionContext {
         setProperty(KEY_ABORT, false);
         setProperty(KEY_METRICS_CONTAINER, null);
         setProperty(KEY_LOG_CONTAINER, null);
+        setProperty(KEY_OUTPUT_FIELDS, new HashMap<String, Object>());
         return this;
     }
 
@@ -108,24 +106,6 @@ public class ExecutionContext {
         return (Parsed) getProperty(KEY_PARSED);
     }
 
-    public DatabaseSlowStatementBuilder databaseSlowStatement() {
-        return (DatabaseSlowStatementBuilder) getProperty(KEY_DATABASE_SLOW_STATEMENT);
-    }
-
-    public ExecutionContext databaseSlowStatement(final DatabaseSlowStatementBuilder databaseSlowStatementBuilder) {
-        setProperty(KEY_DATABASE_SLOW_STATEMENT, databaseSlowStatementBuilder);
-        return this;
-    }
-
-    public SampledTraceBuilder sampledTraceBuilder() {
-        return (SampledTraceBuilder) getProperty(KEY_SAMPLED_TRACE);
-    }
-
-    public ExecutionContext sampledTrace(final SampledTraceBuilder sampledTraceBuilder) {
-        setProperty(KEY_SAMPLED_TRACE, sampledTraceBuilder);
-        return this;
-    }
-
     public ExecutionContext save() {
         setProperty(KEY_SAVE, true);
         return this;
@@ -159,14 +139,23 @@ public class ExecutionContext {
         return Optional.ofNullable((List<SampleFamily>) getProperty(KEY_METRICS_CONTAINER));
     }
 
-    public ExecutionContext logContainer(final AtomicReference<Log> container) {
+    public ExecutionContext logContainer(final AtomicReference<AbstractLog> container) {
         setProperty(KEY_LOG_CONTAINER, container);
         return this;
     }
 
     @SuppressWarnings("unchecked")
-    public Optional<AtomicReference<Log>> logContainer() {
-        return Optional.ofNullable((AtomicReference<Log>) getProperty(KEY_LOG_CONTAINER));
+    public Optional<AtomicReference<AbstractLog>> logContainer() {
+        return Optional.ofNullable((AtomicReference<AbstractLog>) getProperty(KEY_LOG_CONTAINER));
+    }
+
+    public void setOutputField(final String name, final Object value) {
+        outputFields().put(name, value);
+    }
+
+    @SuppressWarnings("unchecked")
+    public Map<String, Object> outputFields() {
+        return (Map<String, Object>) getProperty(KEY_OUTPUT_FIELDS);
     }
 
     public static class Parsed {
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/ExtractorSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/ExtractorSpec.java
index c4156acf10..2447b9af07 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/ExtractorSpec.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/ExtractorSpec.java
@@ -33,8 +33,6 @@ import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.apm.network.logging.v3.TraceContext;
 import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
 import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.AbstractSpec;
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.extractor.sampledtrace.SampledTraceSpec;
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.extractor.slowsql.SlowSqlSpec;
 import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleProvider;
@@ -42,39 +40,15 @@ import org.apache.skywalking.oap.meter.analyzer.v2.MetricConvert;
 import org.apache.skywalking.oap.meter.analyzer.v2.dsl.Sample;
 import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamily;
 import org.apache.skywalking.oap.meter.analyzer.v2.dsl.SampleFamilyBuilder;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.DatabaseSlowStatementBuilder;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder;
-import org.apache.skywalking.oap.server.core.CoreModule;
-import org.apache.skywalking.oap.server.core.analysis.DownSampling;
-import org.apache.skywalking.oap.server.core.analysis.Layer;
-import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
-import org.apache.skywalking.oap.server.core.analysis.record.Record;
-import org.apache.skywalking.oap.server.core.analysis.worker.RecordStreamProcessor;
-import org.apache.skywalking.oap.server.core.config.NamingControl;
-import org.apache.skywalking.oap.server.core.source.ISource;
-import org.apache.skywalking.oap.server.core.source.ServiceMeta;
-import org.apache.skywalking.oap.server.core.source.SourceReceiver;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import static java.util.Objects.nonNull;
 import static org.apache.skywalking.oap.server.library.util.StringUtil.isNotBlank;
 
 public class ExtractorSpec extends AbstractSpec {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ExtractorSpec.class);
-
     private final List<MetricConvert> metricConverts;
 
-    private final SlowSqlSpec slowSql;
-    private final SampledTraceSpec sampledTrace;
-
-    private final NamingControl namingControl;
-
-    private final SourceReceiver sourceReceiver;
-
     public ExtractorSpec(final ModuleManager moduleManager,
                          final LogAnalyzerModuleConfig moduleConfig) throws ModuleStartException {
         super(moduleManager, moduleConfig);
@@ -83,15 +57,6 @@ public class ExtractorSpec extends AbstractSpec {
             .find(LogAnalyzerModule.NAME).provider();
 
         metricConverts = provider.getMetricConverts();
-
-        slowSql = new SlowSqlSpec(moduleManager(), moduleConfig());
-        sampledTrace = new SampledTraceSpec(moduleManager(), moduleConfig());
-
-        namingControl = moduleManager.find(CoreModule.NAME)
-                                     .provider()
-                                     .getService(NamingControl.class);
-
-        sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class);
     }
 
     public void service(final ExecutionContext ctx, final String service) {
@@ -233,89 +198,6 @@ public class ExtractorSpec extends AbstractSpec {
         }
     }
 
-    public SampledTraceSpec sampledTraceSpec() {
-        return sampledTrace;
-    }
-
-    public SlowSqlSpec slowSqlSpec() {
-        return slowSql;
-    }
-
-    public void prepareSampledTrace(final ExecutionContext ctx) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        final LogData.Builder log = ctx.log();
-        final SampledTraceBuilder builder = new SampledTraceBuilder(namingControl);
-        builder.setLayer(log.getLayer());
-        builder.setTimestamp(log.getTimestamp());
-        builder.setServiceName(log.getService());
-        builder.setServiceInstanceName(log.getServiceInstance());
-        builder.setTraceId(log.getTraceContext().getTraceId());
-        ctx.sampledTrace(builder);
-    }
-
-    public void submitSampledTrace(final ExecutionContext ctx) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        final SampledTraceBuilder builder = ctx.sampledTraceBuilder();
-        if (builder == null) {
-            return;
-        }
-        builder.validate();
-        final Record record = builder.toRecord();
-        final ISource entity = builder.toEntity();
-        RecordStreamProcessor.getInstance().in(record);
-        sourceReceiver.receive(entity);
-    }
-
-    public void prepareSlowSql(final ExecutionContext ctx) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        final LogData.Builder log = ctx.log();
-        if (log.getLayer() == null
-            || log.getService() == null
-            || log.getTimestamp() < 1) {
-            LOGGER.warn("SlowSql extracts failed, maybe something is not configured.");
-            return;
-        }
-        final DatabaseSlowStatementBuilder builder = new DatabaseSlowStatementBuilder(namingControl);
-        builder.setLayer(Layer.nameOf(log.getLayer()));
-        builder.setServiceName(log.getService());
-        ctx.databaseSlowStatement(builder);
-    }
-
-    public void submitSlowSql(final ExecutionContext ctx) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        final DatabaseSlowStatementBuilder builder = ctx.databaseSlowStatement();
-        if (builder == null) {
-            return;
-        }
-        if (builder.getId() == null
-            || builder.getLatency() < 1
-            || builder.getStatement() == null) {
-            LOGGER.warn("SlowSql extracts failed, maybe something is not configured.");
-            return;
-        }
-        final LogData.Builder log = ctx.log();
-        final long timeBucketForDB = TimeBucket.getTimeBucket(log.getTimestamp(), DownSampling.Second);
-        builder.setTimeBucket(timeBucketForDB);
-        builder.setTimestamp(log.getTimestamp());
-        builder.prepare();
-        sourceReceiver.receive(builder.toDatabaseSlowStatement());
-
-        final ServiceMeta serviceMeta = new ServiceMeta();
-        serviceMeta.setName(builder.getServiceName());
-        serviceMeta.setLayer(builder.getLayer());
-        final long timeBucket = TimeBucket.getTimeBucket(log.getTimestamp(), DownSampling.Minute);
-        serviceMeta.setTimeBucket(timeBucket);
-        sourceReceiver.receive(serviceMeta);
-    }
-
     public static class SampleBuilder {
         @Delegate
         private final Sample.SampleBuilder sampleBuilder = Sample.builder();
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/sampledtrace/SampledTraceSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/sampledtrace/SampledTraceSpec.java
deleted file mode 100644
index 43a2b7af88..0000000000
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/sampledtrace/SampledTraceSpec.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.dsl.spec.extractor.sampledtrace;
-
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.AbstractSpec;
-import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder;
-import org.apache.skywalking.oap.server.core.source.DetectPoint;
-import org.apache.skywalking.oap.server.library.module.ModuleManager;
-
-import static java.util.Objects.nonNull;
-
-public class SampledTraceSpec extends AbstractSpec {
-    public SampledTraceSpec(ModuleManager moduleManager, LogAnalyzerModuleConfig moduleConfig) {
-        super(moduleManager, moduleConfig);
-    }
-
-    public void latency(final ExecutionContext ctx, final Long latency) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(latency)) {
-            ctx.sampledTraceBuilder().setLatency(latency);
-        }
-    }
-
-    public void uri(final ExecutionContext ctx, final String uri) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(uri)) {
-            ctx.sampledTraceBuilder().setUri(uri);
-        }
-    }
-
-    public void reason(final ExecutionContext ctx, final String reason) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(reason)) {
-            ctx.sampledTraceBuilder().setReason(
-                SampledTraceBuilder.Reason.valueOf(reason.toUpperCase()));
-        }
-    }
-
-    public void processId(final ExecutionContext ctx, final String id) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(id)) {
-            ctx.sampledTraceBuilder().setProcessId(id);
-        }
-    }
-
-    public void destProcessId(final ExecutionContext ctx, final String id) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(id)) {
-            ctx.sampledTraceBuilder().setDestProcessId(id);
-        }
-    }
-
-    public void detectPoint(final ExecutionContext ctx, final String detectPoint) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(detectPoint)) {
-            final DetectPoint point = DetectPoint.valueOf(detectPoint.toUpperCase());
-            ctx.sampledTraceBuilder().setDetectPoint(point);
-        }
-    }
-
-    public void componentId(final ExecutionContext ctx, final int id) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (id > 0) {
-            ctx.sampledTraceBuilder().setComponentId(id);
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/slowsql/SlowSqlSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/slowsql/SlowSqlSpec.java
deleted file mode 100644
index 1dee2e5ecc..0000000000
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/extractor/slowsql/SlowSqlSpec.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.dsl.spec.extractor.slowsql;
-
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
-import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.AbstractSpec;
-
-import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
-import org.apache.skywalking.oap.server.library.module.ModuleManager;
-
-import static java.util.Objects.nonNull;
-
-public class SlowSqlSpec extends AbstractSpec {
-
-    public SlowSqlSpec(final ModuleManager moduleManager,
-                       final LogAnalyzerModuleConfig moduleConfig) {
-        super(moduleManager, moduleConfig);
-    }
-
-    public void latency(final ExecutionContext ctx, final Long latency) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(latency)) {
-            ctx.databaseSlowStatement().setLatency(latency);
-        }
-    }
-
-    public void statement(final ExecutionContext ctx, final String statement) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(statement)) {
-            ctx.databaseSlowStatement().setStatement(statement);
-        }
-    }
-
-    public void id(final ExecutionContext ctx, final String id) {
-        if (ctx.shouldAbort()) {
-            return;
-        }
-        if (nonNull(id)) {
-            ctx.databaseSlowStatement().setId(id);
-        }
-    }
-
-}
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 f63f99492b..b7e5a8c8be 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
@@ -39,6 +39,7 @@ import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfi
 import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogSinkListenerFactory;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.RecordSinkListener;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.TrafficSinkListener;
+import org.apache.skywalking.oap.server.core.source.AbstractLog;
 import org.apache.skywalking.oap.server.core.source.Log;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
@@ -71,13 +72,19 @@ public class FilterSpec extends AbstractSpec {
 
     public FilterSpec(final ModuleManager moduleManager,
                       final LogAnalyzerModuleConfig moduleConfig) throws ModuleStartException {
+        this(moduleManager, moduleConfig, Log.class);
+    }
+
+    public FilterSpec(final ModuleManager moduleManager,
+                      final LogAnalyzerModuleConfig moduleConfig,
+                      final Class<?> outputType) throws ModuleStartException {
         super(moduleManager, moduleConfig);
 
         parsedType = new TypeReference<Map<String, Object>>() {
         };
 
         sinkListenerFactories = Arrays.asList(
-            new RecordSinkListener.Factory(moduleManager(), moduleConfig()),
+            new RecordSinkListener.Factory(moduleManager(), moduleConfig(), outputType),
             new TrafficSinkListener.Factory(moduleManager(), moduleConfig())
         );
 
@@ -183,12 +190,12 @@ public class FilterSpec extends AbstractSpec {
             return;
         }
 
-        final Optional<AtomicReference<Log>> container = ctx.logContainer();
+        final Optional<AtomicReference<AbstractLog>> container = ctx.logContainer();
         if (container.isPresent()) {
             sinkListenerFactories.stream()
                      .map(LogSinkListenerFactory::create)
                      .filter(it -> it instanceof RecordSinkListener)
-                     .map(it -> it.parse(logData, extraLog))
+                     .map(it -> it.parse(logData, extraLog, ctx))
                      .map(it -> (RecordSinkListener) it)
                      .map(RecordSinkListener::getLog)
                      .findFirst()
@@ -196,7 +203,7 @@ public class FilterSpec extends AbstractSpec {
         } else {
             sinkListenerFactories.stream()
                      .map(LogSinkListenerFactory::create)
-                     .forEach(it -> it.parse(logData, extraLog).build());
+                     .forEach(it -> it.parse(logData, extraLog, 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 27b35c525a..c19030eddc 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
@@ -28,7 +28,19 @@ public class LALConfig {
 
     private String layer;
 
-    private String extraLogType;
+    /**
+     * Fully qualified class name of the input type (the extra log proto/POJO)
+     * for compile-time {@code parsed.*} getter resolution.
+     * Was previously named {@code extraLogType}.
+     */
+    private String inputType;
+
+    /**
+     * Fully qualified class name of the output {@link org.apache.skywalking.oap.server.core.source.Source}
+     * subclass that the LAL sink should produce.
+     * Defaults to {@link org.apache.skywalking.oap.server.core.source.Log} when not specified.
+     */
+    private String outputType;
 
     /**
      * Source YAML file name (without extension), set during loading by
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 81c997e5f6..04496a5810 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
@@ -37,6 +37,8 @@ import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfi
 import org.apache.skywalking.oap.log.analyzer.v2.spi.LALSourceTypeProvider;
 
 import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
+import org.apache.skywalking.oap.server.core.source.Source;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleStartException;
 
@@ -54,14 +56,6 @@ import org.apache.skywalking.oap.server.library.module.ModuleStartException;
  *       which invokes the compiled {@link org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression}
  *       to run the filter/extractor/sink pipeline.</li>
  * </ol>
- *
- * <p>The inner {@link Factory} is created once at startup by
- * {@link org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleProvider#start()}.
- * It loads all {@code .yaml} LAL config files, compiles each rule's DSL string
- * into a {@link DSL} instance via
- * {@link DSL#of(org.apache.skywalking.oap.server.library.module.ModuleManager,
- *   org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig, String)},
- * and organizes them by {@link Layer}.
  */
 @Slf4j
 public class LogFilterListener implements LogAnalysisListener {
@@ -86,10 +80,10 @@ public class LogFilterListener implements LogAnalysisListener {
     @Override
     public LogAnalysisListener parse(final LogData.Builder logData,
                                      final Message extraLog) {
-        final LogData log = logData.build();
+        final LogData logDataSnapshot = logData.build();
         contexts = new ArrayList<>(dsls.size());
         for (int i = 0; i < dsls.size(); i++) {
-            contexts.add(new ExecutionContext().log(log).extraLog(extraLog));
+            contexts.add(new ExecutionContext().log(logDataSnapshot).extraLog(extraLog));
         }
         return this;
     }
@@ -100,12 +94,13 @@ public class LogFilterListener implements LogAnalysisListener {
         public Factory(final ModuleManager moduleManager, final LogAnalyzerModuleConfig config) throws Exception {
             dsls = new HashMap<>();
 
-            // Scan SPI providers for default extraLogType per layer
-            final Map<Layer, Class<?>> spiTypes = new HashMap<>();
+            // Scan SPI providers for default inputType/outputType per layer
+            final Map<Layer, LALSourceTypeProvider> spiProviders = new HashMap<>();
             for (final LALSourceTypeProvider p : ServiceLoader.load(LALSourceTypeProvider.class)) {
-                spiTypes.put(p.layer(), p.extraLogType());
-                log.info("LALSourceTypeProvider: layer={} -> {}",
-                    p.layer().name(), p.extraLogType().getName());
+                spiProviders.put(p.layer(), p);
+                log.info("LALSourceTypeProvider: layer={}, inputType={}, outputType={}",
+                    p.layer().name(), p.inputType().getName(),
+                    p.outputType() != null ? p.outputType().getName() : "default(Log)");
             }
 
             final List<LALConfig> configList = LALConfigs.load(config.getLalPath(), config.lalFiles())
@@ -114,30 +109,98 @@ public class LogFilterListener implements LogAnalysisListener {
                                                          .collect(Collectors.toList());
             for (final LALConfig c : configList) {
                 final Layer layer = Layer.nameOf(c.getLayer());
+                final LALSourceTypeProvider spiProvider = spiProviders.get(layer);
 
                 // Per-rule resolution: explicit YAML > SPI > null
-                Class<?> resolvedType = resolveExtraLogType(c, spiTypes.get(layer));
+                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(), resolvedType, c.getName(), c.getSourceName())) != null) {
+                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.");
                 }
             }
         }
 
-        private static Class<?> resolveExtraLogType(final LALConfig config,
-                                                     final Class<?> spiType) throws ModuleStartException {
-            final String yamlType = config.getExtraLogType();
+        private static Class<?> resolveInputType(final LALConfig config,
+                                                  final LALSourceTypeProvider spiProvider) throws ModuleStartException {
+            final String yamlType = config.getInputType();
             if (yamlType != null && !yamlType.isEmpty()) {
                 try {
                     return Class.forName(yamlType);
                 } catch (ClassNotFoundException e) {
                     throw new ModuleStartException(
-                        "LAL rule '" + config.getName() + "' declares extraLogType '"
+                        "LAL rule '" + config.getName() + "' declares inputType '"
                             + yamlType + "' but the class was not found.", e);
                 }
             }
-            return spiType;
+            return spiProvider != null ? spiProvider.inputType() : null;
+        }
+
+        /**
+         * Short name → implementation class map built from {@link ServiceLoader}{@code <LALOutputBuilder>}.
+         * Populated once on first call to {@link #resolveOutputType}.
+         */
+        private static volatile Map<String, Class<?>> OUTPUT_BUILDER_NAMES;
+
+        private static Map<String, Class<?>> loadOutputBuilderNames() {
+            if (OUTPUT_BUILDER_NAMES != null) {
+                return OUTPUT_BUILDER_NAMES;
+            }
+            synchronized (Factory.class) {
+                if (OUTPUT_BUILDER_NAMES != null) {
+                    return OUTPUT_BUILDER_NAMES;
+                }
+                final Map<String, Class<?>> map = new HashMap<>();
+                for (final LALOutputBuilder builder : ServiceLoader.load(LALOutputBuilder.class)) {
+                    final String name = builder.name();
+                    final Class<?> prev = map.put(name, builder.getClass());
+                    if (prev != null) {
+                        log.warn("Duplicate LALOutputBuilder name '{}': {} vs {}",
+                            name, prev.getName(), builder.getClass().getName());
+                    }
+                    log.info("LALOutputBuilder registered: name={}, class={}",
+                        name, builder.getClass().getName());
+                }
+                OUTPUT_BUILDER_NAMES = map;
+                return map;
+            }
+        }
+
+        private static Class<?> resolveOutputType(
+                final LALConfig config,
+                final LALSourceTypeProvider spiProvider) throws ModuleStartException {
+            // Per-rule YAML config takes priority
+            final String yamlType = config.getOutputType();
+            if (yamlType != null && !yamlType.isEmpty()) {
+                // Try short name first (no '.' means it's not a FQCN)
+                if (!yamlType.contains(".")) {
+                    final Class<?> byName = loadOutputBuilderNames().get(yamlType);
+                    if (byName != null) {
+                        return byName;
+                    }
+                }
+                // Fall back to FQCN
+                try {
+                    return Class.forName(yamlType);
+                } catch (ClassNotFoundException e) {
+                    throw new ModuleStartException(
+                        "LAL rule '" + config.getName() + "' declares outputType '"
+                            + yamlType + "' but neither a registered LALOutputBuilder name"
+                            + " nor a class was found.", e);
+                }
+            }
+            // Fall back to SPI default for the layer
+            if (spiProvider != null) {
+                final Class<? extends Source> spiOutput = spiProvider.outputType();
+                if (spiOutput != null) {
+                    return spiOutput;
+                }
+            }
+            return null; // DSL.of() will default to Log.class
         }
 
         @Override
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogSinkListener.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogSinkListener.java
index 69830edd10..6ace505bd7 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogSinkListener.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogSinkListener.java
@@ -19,6 +19,7 @@ package org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener;
 
 import com.google.protobuf.Message;
 import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
 
 public interface LogSinkListener {
     /**
@@ -32,4 +33,15 @@ public interface LogSinkListener {
      * @return {@code this} for chaining.
      */
     LogSinkListener parse(LogData.Builder logData, final Message extraLog);
+
+    /**
+     * Parse the raw data from the probe with access to the execution context.
+     * Implementations can use the context to apply output fields or other
+     * per-execution state to the sink output.
+     * @return {@code this} for chaining.
+     */
+    default LogSinkListener parse(final LogData.Builder logData, final Message extraLog,
+                                  final ExecutionContext ctx) {
+        return parse(logData, extraLog);
+    }
 }
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/RecordSinkListener.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/RecordSinkListener.java
index cce46468fd..94c916dcc0 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/RecordSinkListener.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/RecordSinkListener.java
@@ -22,15 +22,18 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import lombok.Getter;
-import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.apm.network.logging.v3.LogDataBody;
 import org.apache.skywalking.apm.network.logging.v3.TraceContext;
 
+import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
 import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.TagType;
+import org.apache.skywalking.oap.server.core.source.AbstractLog;
+import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
 import org.apache.skywalking.oap.server.core.source.TagAutocomplete;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
@@ -52,26 +55,71 @@ import static org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils.to
 
 /**
  * RecordSinkListener forwards the log data to the persistence layer with the query required conditions.
+ *
+ * <p>Supports two output paths based on the {@code outputType}:
+ * <ul>
+ *   <li>{@link AbstractLog} subclass — standard log record path: common fields populated from
+ *       LogData, subclass-specific fields via {@link AbstractLog#prepare} and output field assignments</li>
+ *   <li>{@link LALOutputBuilder} implementation — builder path: {@code init()} pre-populates from
+ *       LogData, output fields applied via reflection, {@code complete()} dispatches final source(s)</li>
+ * </ul>
  */
-@RequiredArgsConstructor
 public class RecordSinkListener implements LogSinkListener {
     private static final Logger LOGGER = LoggerFactory.getLogger(RecordSinkListener.class);
     private final SourceReceiver sourceReceiver;
     private final NamingControl namingControl;
     private final List<String> searchableTagKeys;
+    private final Class<?> outputType;
+    private final boolean isBuilderMode;
+
     @Getter
-    private final Log log = new Log();
+    private AbstractLog log;
+    private LALOutputBuilder builder;
+
+    RecordSinkListener(final SourceReceiver sourceReceiver,
+                       final NamingControl namingControl,
+                       final List<String> searchableTagKeys,
+                       final Class<?> outputType) {
+        this.sourceReceiver = sourceReceiver;
+        this.namingControl = namingControl;
+        this.searchableTagKeys = searchableTagKeys;
+        this.outputType = outputType;
+        this.isBuilderMode = LALOutputBuilder.class.isAssignableFrom(outputType);
+    }
 
     @Override
     public void build() {
-        sourceReceiver.receive(log);
-        addAutocompleteTags();
+        if (isBuilderMode) {
+            if (builder != null) {
+                builder.complete(sourceReceiver);
+            }
+        } else {
+            sourceReceiver.receive(log);
+            addAutocompleteTags();
+        }
     }
 
     @Override
     @SneakyThrows
     public LogSinkListener parse(final LogData.Builder logData,
                                      final Message extraLog) {
+        if (isBuilderMode) {
+            return parseBuilder(logData);
+        }
+        return parseAbstractLog(logData, extraLog);
+    }
+
+    @SneakyThrows
+    private LogSinkListener parseBuilder(final LogData.Builder logData) {
+        builder = (LALOutputBuilder) outputType.getDeclaredConstructor().newInstance();
+        builder.init(logData.build(), namingControl);
+        return this;
+    }
+
+    @SneakyThrows
+    private LogSinkListener parseAbstractLog(final LogData.Builder logData,
+                                              final Message extraLog) {
+        log = createOutputSource();
         LogDataBody body = logData.getBody();
         log.setUniqueId(UUID.randomUUID().toString().replace("-", ""));
         // timestamp
@@ -121,9 +169,96 @@ public class RecordSinkListener implements LogSinkListener {
             log.setTagsRawData(logData.getTags().toByteArray());
         }
         log.getTags().addAll(appendSearchableTags(logData));
+
+        // Let the output source class populate its custom fields
+        log.prepare(logData, namingControl);
+
+        return this;
+    }
+
+    @Override
+    @SneakyThrows
+    public LogSinkListener parse(final LogData.Builder logData,
+                                 final Message extraLog,
+                                 final ExecutionContext ctx) {
+        parse(logData, extraLog);
+        if (ctx != null) {
+            final Object target = isBuilderMode ? builder : log;
+            applyOutputFields(ctx.outputFields(), target);
+        }
         return this;
     }
 
+    private void applyOutputFields(final Map<String, Object> outputFields, final Object target) {
+        if (outputFields == null || outputFields.isEmpty() || target == null) {
+            return;
+        }
+        for (final Map.Entry<String, Object> entry : outputFields.entrySet()) {
+            final String fieldName = entry.getKey();
+            final Object value = entry.getValue();
+            if (value == null) {
+                continue;
+            }
+            final String setterName = "set" + Character.toUpperCase(fieldName.charAt(0))
+                + fieldName.substring(1);
+            try {
+                java.lang.reflect.Method setter = findSetter(target.getClass(), setterName);
+                if (setter != null) {
+                    setter.setAccessible(true);
+                    setter.invoke(target, convertValue(value, setter.getParameterTypes()[0]));
+                }
+            } catch (Exception e) {
+                LOGGER.warn("Failed to set output field '{}' on {}: {}",
+                    fieldName, target.getClass().getSimpleName(), e.getMessage());
+            }
+        }
+    }
+
+    static java.lang.reflect.Method findSetter(final Class<?> clazz, final String name) {
+        for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
+            for (final java.lang.reflect.Method m : c.getDeclaredMethods()) {
+                if (m.getName().equals(name) && m.getParameterCount() == 1) {
+                    return m;
+                }
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    static Object convertValue(final Object value, final Class<?> targetType) {
+        if (targetType.isInstance(value)) {
+            return value;
+        }
+        final String str = String.valueOf(value);
+        if (targetType == long.class || targetType == Long.class) {
+            return Long.parseLong(str);
+        }
+        if (targetType == int.class || targetType == Integer.class) {
+            return Integer.parseInt(str);
+        }
+        if (targetType == double.class || targetType == Double.class) {
+            return Double.parseDouble(str);
+        }
+        if (targetType == boolean.class || targetType == Boolean.class) {
+            return Boolean.parseBoolean(str);
+        }
+        if (targetType.isEnum()) {
+            return Enum.valueOf((Class<Enum>) targetType, str);
+        }
+        return str;
+    }
+
+    @SuppressWarnings("unchecked")
+    private AbstractLog createOutputSource() {
+        try {
+            return ((Class<? extends AbstractLog>) outputType).getDeclaredConstructor().newInstance();
+        } catch (Exception e) {
+            throw new IllegalStateException(
+                "Failed to create output source instance: " + outputType.getName(), e);
+        }
+    }
+
     private Collection<Tag> appendSearchableTags(LogData.Builder logData) {
         HashSet<Tag> logTags = new HashSet<>();
         logData.getTags().getDataList().forEach(tag -> {
@@ -156,8 +291,15 @@ public class RecordSinkListener implements LogSinkListener {
         private final SourceReceiver sourceReceiver;
         private final NamingControl namingControl;
         private final List<String> searchableTagKeys;
+        private final Class<?> outputType;
 
         public Factory(ModuleManager moduleManager, LogAnalyzerModuleConfig moduleConfig) {
+            this(moduleManager, moduleConfig, Log.class);
+        }
+
+        public Factory(ModuleManager moduleManager,
+                       LogAnalyzerModuleConfig moduleConfig,
+                       Class<?> outputType) {
             this.sourceReceiver = moduleManager.find(CoreModule.NAME)
                                                .provider()
                                                .getService(SourceReceiver.class);
@@ -168,11 +310,12 @@ public class RecordSinkListener implements LogSinkListener {
                                                        .provider()
                                                        .getService(ConfigService.class);
             this.searchableTagKeys = Arrays.asList(configService.getSearchableLogsTags().split(Const.COMMA));
+            this.outputType = outputType;
         }
 
         @Override
         public RecordSinkListener create() {
-            return new RecordSinkListener(sourceReceiver, namingControl, searchableTagKeys);
+            return new RecordSinkListener(sourceReceiver, namingControl, searchableTagKeys, outputType);
         }
     }
 }
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/spi/LALSourceTypeProvider.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/spi/LALSourceTypeProvider.java
index bc5a3e6726..81b6030553 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/spi/LALSourceTypeProvider.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/spi/LALSourceTypeProvider.java
@@ -18,21 +18,39 @@
 package org.apache.skywalking.oap.log.analyzer.v2.spi;
 
 import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.Source;
 
 /**
- * SPI for receiver plugins to declare the Java type of the {@code extraLog}
- * they pass to LAL via {@code ILogAnalyzerService.doAnalysis(LogData, Message)}.
+ * SPI for receiver plugins to declare the input and default output types for
+ * LAL rules on a specific {@link Layer}.
  *
- * <p>The LAL compiler uses this at compile time to generate optimized direct
- * getter calls instead of runtime reflection. Implementations are discovered
- * via {@link java.util.ServiceLoader} and matched by {@link Layer}.
+ * <p><b>Input type</b> ({@link #inputType()}): The Java type of the {@code extraLog}
+ * passed to LAL via {@code ILogAnalyzerService.doAnalysis(LogData, Message)}.
+ * The LAL compiler uses this at compile time to generate optimized direct
+ * getter calls for {@code parsed.*} field access. This is per-layer because
+ * all rules for a layer share the same input proto type.
  *
- * <p>Per-rule type resolution order:
+ * <p><b>Output type</b> ({@link #outputType()}): The default output type for
+ * the layer. Individual rules can override this via the {@code outputType}
+ * field in YAML rule config. The compiler validates output field assignments
+ * against the resolved output type at compile time.
+ *
+ * <p>Implementations are discovered via {@link java.util.ServiceLoader} and
+ * matched by {@link Layer}.
+ *
+ * <p>Input type resolution order:
+ * <ol>
+ *   <li>DSL parser ({@code json{}}, {@code yaml{}}, {@code text{}}) wins over all</li>
+ *   <li>Explicit {@code inputType} declared in the YAML rule config</li>
+ *   <li>This SPI — acts as the default for a layer</li>
+ *   <li>{@code LogData.Builder} fallback if none of the above</li>
+ * </ol>
+ *
+ * <p>Output type resolution order:
  * <ol>
- *   <li>DSL parser ({@code json{}}, {@code yaml{}}, {@code text{}}) — parser wins</li>
- *   <li>Explicit {@code extraLogType} declared in the YAML rule config</li>
- *   <li>This SPI — acts as the default {@code extraLogType} for a layer</li>
- *   <li>Compile error if none of the above and the rule accesses {@code parsed.*}</li>
+ *   <li>Explicit {@code outputType} declared in the YAML rule config (per-rule)</li>
+ *   <li>This SPI — acts as the default for a layer</li>
+ *   <li>{@code Log} if not specified anywhere</li>
  * </ol>
  *
  * <p>Receiver plugins register implementations in
@@ -49,5 +67,14 @@ public interface LALSourceTypeProvider {
      * this layer. The compiler resolves getter chains on this type at
      * compile time.
      */
-    Class<?> extraLogType();
+    Class<?> inputType();
+
+    /**
+     * The default {@link Source} subclass that LAL rules on this layer produce.
+     * Individual rules can override this via the {@code outputType} YAML config field.
+     * Returns {@code null} by default, meaning the standard {@code Log} source is used.
+     */
+    default Class<? extends Source> outputType() {
+        return null;
+    }
 }
diff --git a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorTest.java b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorTest.java
index b3bf2bdbef..1af3cb6ea6 100644
--- a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorTest.java
+++ b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorTest.java
@@ -270,67 +270,6 @@ class LALClassGeneratorTest {
         assertNotNull(expr);
     }
 
-    // ==================== SlowSql block ====================
-
-    @Test
-    void compileSlowSqlBlock() throws Exception {
-        final LalExpression expr = generator.compile(
-            "filter {\n"
-            + "  json {}\n"
-            + "  extractor {\n"
-            + "    slowSql {\n"
-            + "      id parsed.id as String\n"
-            + "      statement parsed.statement as String\n"
-            + "      latency parsed.query_time as Long\n"
-            + "    }\n"
-            + "  }\n"
-            + "  sink {}\n"
-            + "}");
-        assertNotNull(expr);
-    }
-
-    // ==================== SampledTrace block ====================
-
-    @Test
-    void compileSampledTraceBlock() throws Exception {
-        final LalExpression expr = generator.compile(
-            "filter {\n"
-            + "  json {}\n"
-            + "  extractor {\n"
-            + "    sampledTrace {\n"
-            + "      latency parsed.latency as Long\n"
-            + "      uri parsed.uri as String\n"
-            + "      reason parsed.reason as String\n"
-            + "      detectPoint parsed.detect_point as String\n"
-            + "      componentId 49\n"
-            + "    }\n"
-            + "  }\n"
-            + "  sink {}\n"
-            + "}");
-        assertNotNull(expr);
-    }
-
-    @Test
-    void compileSampledTraceWithIfBlocks() throws Exception {
-        final LalExpression expr = generator.compile(
-            "filter {\n"
-            + "  json {}\n"
-            + "  extractor {\n"
-            + "    sampledTrace {\n"
-            + "      latency parsed.latency as Long\n"
-            + "      if (parsed.client_process.process_id as String != \"\") {\n"
-            + "        processId parsed.client_process.process_id as String\n"
-            + "      } else {\n"
-            + "        processId parsed.fallback as String\n"
-            + "      }\n"
-            + "      detectPoint parsed.detect_point as String\n"
-            + "    }\n"
-            + "  }\n"
-            + "  sink {}\n"
-            + "}");
-        assertNotNull(expr);
-    }
-
     // ==================== Sampler / rateLimit ====================
 
     @Test
@@ -476,29 +415,7 @@ class LALClassGeneratorTest {
     }
 
     @Test
-    void compileSlowSqlProductionRule() throws Exception {
-        final LalExpression expr = generator.compile(
-            "filter {\n"
-            + "  json {}\n"
-            + "  extractor {\n"
-            + "    if (tag(\"LOG_KIND\") == \"SLOW_SQL\") {\n"
-            + "      layer parsed.layer as String\n"
-            + "      service parsed.service as String\n"
-            + "      timestamp parsed.time as String\n"
-            + "      slowSql {\n"
-            + "        id parsed.id as String\n"
-            + "        statement parsed.statement as String\n"
-            + "        latency parsed.query_time as Long\n"
-            + "      }\n"
-            + "    }\n"
-            + "  }\n"
-            + "  sink {}\n"
-            + "}");
-        assertNotNull(expr);
-    }
-
-    @Test
-    void compileEnvoyAlsAbortRuleFailsWithoutExtraLogType() {
+    void compileEnvoyAlsAbortRuleFailsWithoutInputType() {
         // envoy-als pattern has no parser (json/yaml/text) — falls back to LogData
         // but LogData.Builder doesn't have getResponse(), so compile fails
         assertThrows(IllegalArgumentException.class, () -> generator.compile(
@@ -519,7 +436,7 @@ class LALClassGeneratorTest {
 
     @Test
     void compileNoParserFallsBackToLogDataProto() throws Exception {
-        // No parser (json/yaml/text) and no extraLogType — should use LogData.Builder
+        // No parser (json/yaml/text) and no inputType — should use LogData.Builder
         final String dsl =
             "filter {\n"
             + "  extractor {\n"
@@ -543,8 +460,8 @@ class LALClassGeneratorTest {
     }
 
     @Test
-    void compileExtraLogTypeGeneratesDirectGetterCalls() throws Exception {
-        generator.setExtraLogType(
+    void compileInputTypeGeneratesDirectGetterCalls() throws Exception {
+        generator.setInputType(
             io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry.class);
         final String dsl =
             "filter {\n"
@@ -566,7 +483,7 @@ class LALClassGeneratorTest {
         // Proto field access uses _p local variable, not inline cast
         assertTrue(source.contains(
             fqcn + " _p = (" + fqcn + ") h.ctx().extraLog()"),
-            "Expected _p local variable for extraLog cast but got: " + source);
+            "Expected _p local variable for inputType cast but got: " + source);
         // Intermediate fields cached in _tN local variables
         assertTrue(source.contains("_p.getResponse()"),
             "Expected _p.getResponse() via cached variable but got: " + source);
@@ -616,32 +533,6 @@ class LALClassGeneratorTest {
         assertNotNull(expr);
     }
 
-    @Test
-    void compileElseIfInSampledTrace() throws Exception {
-        final LalExpression expr = generator.compile(
-            "filter {\n"
-            + "  json {}\n"
-            + "  extractor {\n"
-            + "    sampledTrace {\n"
-            + "      latency parsed.latency as Long\n"
-            + "      if (parsed.client_process.process_id as String != \"\") {\n"
-            + "        processId parsed.client_process.process_id as String\n"
-            + "      } else if (parsed.client_process.local as Boolean) {\n"
-            + "        processId ProcessRegistry.generateVirtualLocalProcess("
-            + "parsed.service as String, parsed.serviceInstance as String) as String\n"
-            + "      } else {\n"
-            + "        processId ProcessRegistry.generateVirtualRemoteProcess("
-            + "parsed.service as String, parsed.serviceInstance as String, "
-            + "parsed.client_process.address as String) as String\n"
-            + "      }\n"
-            + "      detectPoint parsed.detect_point as String\n"
-            + "    }\n"
-            + "  }\n"
-            + "  sink {}\n"
-            + "}");
-        assertNotNull(expr);
-    }
-
     @Test
     void generateSourceElseIfEmitsNestedBranches() {
         final String source = generator.generateSource(
@@ -669,4 +560,68 @@ class LALClassGeneratorTest {
             "Expected at least 2 if-conditions for else-if chain but got "
             + ifCount + " in: " + source);
     }
+
+    @Test
+    void compileOutputFieldAssignment() {
+        final String source = generator.generateSource(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    statement parsed.statement as String\n"
+                + "    latency parsed.latency as Long\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+        assertTrue(source.contains("setOutputField(\"statement\""),
+            "Expected setOutputField for 'statement' but got: " + source);
+        assertTrue(source.contains("setOutputField(\"latency\""),
+            "Expected setOutputField for 'latency' but got: " + source);
+        assertTrue(source.contains("h.toStr("),
+            "Expected toStr() cast for String but got: " + source);
+        assertTrue(source.contains("h.toLong("),
+            "Expected toLong() cast for Long but got: " + source);
+    }
+
+    @Test
+    void compileOutputFieldWithOutputTypeValidation() {
+        generator.setOutputType(TestOutputType.class);
+        final String source = generator.generateSource(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    statement parsed.stmt as String\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+        assertTrue(source.contains("setOutputField(\"statement\""),
+            "Expected setOutputField for 'statement' but got: " + source);
+    }
+
+    @Test
+    void compileOutputFieldWithInvalidSetter() {
+        generator.setOutputType(TestOutputType.class);
+        final Exception ex = assertThrows(RuntimeException.class, () ->
+            generator.generateSource(
+                "filter {\n"
+                    + "  json {}\n"
+                    + "  extractor {\n"
+                    + "    nonExistentField parsed.x as String\n"
+                    + "  }\n"
+                    + "  sink {}\n"
+                    + "}"));
+        assertTrue(ex.getMessage().contains("setNonExistentField"),
+            "Expected error about missing setter but got: " + ex.getMessage());
+    }
+
+    public static class TestOutputType {
+        private String statement;
+
+        public void setStatement(final String statement) {
+            this.statement = statement;
+        }
+
+        public String getStatement() {
+            return statement;
+        }
+    }
 }
diff --git a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALExpressionExecutionTest.java b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALExpressionExecutionTest.java
index fb4453843f..4a93674ef5 100644
--- a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALExpressionExecutionTest.java
+++ b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALExpressionExecutionTest.java
@@ -42,7 +42,6 @@ import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
 import org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression;
 import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.filter.FilterSpec;
 import org.apache.skywalking.oap.log.analyzer.v2.spi.LALSourceTypeProvider;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder;
 import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleProvider;
@@ -181,7 +180,7 @@ class LALExpressionExecutionTest {
                     final String ruleName = rule.get("name");
                     final String dsl = rule.get("dsl");
                     final String ruleLayer = rule.get("layer");
-                    final String extraLogType = rule.get("extraLogType");
+                    final String inputType = rule.get("inputType");
                     if (ruleName == null || dsl == null) {
                         continue;
                     }
@@ -203,7 +202,7 @@ class LALExpressionExecutionTest {
                                 () -> executeAndAssert(
                                     generator, filterSpec,
                                     ruleName + " [" + idx + "]",
-                                    dsl, ruleLayer, extraLogType, input)
+                                    dsl, ruleLayer, inputType, input)
                             ));
                         }
                     } else {
@@ -214,7 +213,7 @@ class LALExpressionExecutionTest {
                             category + "/" + baseName + " | " + ruleName,
                             () -> executeAndAssert(
                                 generator, filterSpec, ruleName,
-                                dsl, ruleLayer, extraLogType, input)
+                                dsl, ruleLayer, inputType, input)
                         ));
                     }
                 }
@@ -229,15 +228,15 @@ class LALExpressionExecutionTest {
             final String ruleName,
             final String dsl,
             final String ruleLayer,
-            final String extraLogType,
+            final String inputType,
             final Map<String, Object> input) throws Exception {
-        if (extraLogType != null) {
-            generator.setExtraLogType(Class.forName(extraLogType));
+        if (inputType != null) {
+            generator.setInputType(Class.forName(inputType));
         } else if (ruleLayer != null) {
             // Resolve via LALSourceTypeProvider SPI
-            generator.setExtraLogType(spiExtraLogTypes().get(ruleLayer));
+            generator.setInputType(spiInputTypes().get(ruleLayer));
         } else {
-            generator.setExtraLogType(null);
+            generator.setInputType(null);
         }
         final LalExpression expr = generator.compile(dsl);
         final LogData.Builder logData = buildLogData(input);
@@ -301,10 +300,7 @@ class LALExpressionExecutionTest {
                         ruleName + ": timestamp mismatch");
                     break;
                 default:
-                    if (key.startsWith("sampledTrace.")) {
-                        assertSampledTrace(
-                            ruleName, key, expected, ctx);
-                    } else if (key.startsWith("tag.")) {
+                    if (key.startsWith("tag.")) {
                         final String tagKey = key.substring(4);
                         final List<KeyStringValuePair> tags =
                             ctx.log().getTags().getDataList();
@@ -322,83 +318,6 @@ class LALExpressionExecutionTest {
         }
     }
 
-    // ==================== SampledTrace assertions ====================
-
-    private static void assertSampledTrace(
-            final String ruleName,
-            final String key,
-            final String expected,
-            final ExecutionContext ctx) {
-        final SampledTraceBuilder builder =
-            ctx.sampledTraceBuilder();
-        assertTrue(builder != null,
-            ruleName + ": sampledTraceBuilder is null"
-                + " but expected " + key + "=" + expected);
-
-        final String field = key.substring("sampledTrace.".length());
-        switch (field) {
-            case "latency":
-                assertEquals(Long.parseLong(expected),
-                    builder.getLatency(),
-                    ruleName + ": sampledTrace.latency mismatch");
-                break;
-            case "uri":
-                assertEquals(expected, builder.getUri(),
-                    ruleName + ": sampledTrace.uri mismatch");
-                break;
-            case "reason":
-                assertEquals(expected,
-                    builder.getReason().name(),
-                    ruleName + ": sampledTrace.reason mismatch");
-                break;
-            case "processId":
-                assertEquals(expected, builder.getProcessId(),
-                    ruleName + ": sampledTrace.processId mismatch");
-                break;
-            case "destProcessId":
-                assertEquals(expected, builder.getDestProcessId(),
-                    ruleName + ": sampledTrace.destProcessId mismatch");
-                break;
-            case "detectPoint":
-                assertEquals(expected,
-                    builder.getDetectPoint().name(),
-                    ruleName + ": sampledTrace.detectPoint mismatch");
-                break;
-            case "componentId":
-                assertEquals(Integer.parseInt(expected),
-                    builder.getComponentId(),
-                    ruleName
-                        + ": sampledTrace.componentId mismatch");
-                break;
-            case "traceId":
-                assertEquals(expected, builder.getTraceId(),
-                    ruleName + ": sampledTrace.traceId mismatch");
-                break;
-            case "serviceName":
-                assertEquals(expected, builder.getServiceName(),
-                    ruleName
-                        + ": sampledTrace.serviceName mismatch");
-                break;
-            case "serviceInstanceName":
-                assertEquals(expected,
-                    builder.getServiceInstanceName(),
-                    ruleName
-                        + ": sampledTrace.serviceInstanceName"
-                        + " mismatch");
-                break;
-            case "timestamp":
-                assertEquals(Long.parseLong(expected),
-                    builder.getTimestamp(),
-                    ruleName
-                        + ": sampledTrace.timestamp mismatch");
-                break;
-            default:
-                throw new IllegalArgumentException(
-                    ruleName + ": unknown sampledTrace field: "
-                        + field);
-        }
-    }
-
     // ==================== LogData builder ====================
 
     @SuppressWarnings("unchecked")
@@ -484,12 +403,12 @@ class LALExpressionExecutionTest {
 
     private Map<String, Class<?>> spiTypes;
 
-    private Map<String, Class<?>> spiExtraLogTypes() {
+    private Map<String, Class<?>> spiInputTypes() {
         if (spiTypes == null) {
             spiTypes = new HashMap<>();
             for (final LALSourceTypeProvider p :
                     ServiceLoader.load(LALSourceTypeProvider.class)) {
-                spiTypes.put(p.layer().name(), p.extraLogType());
+                spiTypes.put(p.layer().name(), p.inputType());
             }
         }
         return spiTypes;
diff --git a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParserTest.java b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParserTest.java
index 24d59d63d3..bc39b443a3 100644
--- a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParserTest.java
+++ b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALScriptParserTest.java
@@ -78,42 +78,6 @@ class LALScriptParserTest {
         assertNotNull(textParser.getRegexpPattern());
     }
 
-    @Test
-    void parseSlowSql() {
-        final LALScriptModel model = LALScriptParser.parse(
-            "filter {\n"
-                + "  json {}\n"
-                + "  extractor {\n"
-                + "    layer parsed.layer as String\n"
-                + "    service parsed.service as String\n"
-                + "    timestamp parsed.time as String\n"
-                + "    slowSql {\n"
-                + "      id parsed.id as String\n"
-                + "      statement parsed.statement as String\n"
-                + "      latency parsed.query_time as Long\n"
-                + "    }\n"
-                + "  }\n"
-                + "}");
-
-        final LALScriptModel.ExtractorBlock extractor =
-            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
-
-        // Find the slowSql block
-        LALScriptModel.SlowSqlBlock slowSql = null;
-        for (final LALScriptModel.ExtractorStatement stmt : extractor.getStatements()) {
-            if (stmt instanceof LALScriptModel.SlowSqlBlock) {
-                slowSql = (LALScriptModel.SlowSqlBlock) stmt;
-            }
-        }
-        assertNotNull(slowSql);
-        assertNotNull(slowSql.getId());
-        assertEquals("String", slowSql.getIdCast());
-        assertNotNull(slowSql.getStatement());
-        assertEquals("String", slowSql.getStatementCast());
-        assertNotNull(slowSql.getLatency());
-        assertEquals("Long", slowSql.getLatencyCast());
-    }
-
     @Test
     void parseMetricsBlock() {
         final LALScriptModel model = LALScriptParser.parse(
@@ -450,32 +414,6 @@ class LALScriptParserTest {
         assertEquals("String", method.getArguments().get(1).getCastType());
     }
 
-    // ==================== Sampled trace parsing ====================
-
-    @Test
-    void parseSampledTrace() {
-        final LALScriptModel model = LALScriptParser.parse(
-            "filter {\n"
-                + "  json {}\n"
-                + "  extractor {\n"
-                + "    sampledTrace {\n"
-                + "      latency parsed.latency as Long\n"
-                + "      uri parsed.uri as String\n"
-                + "      reason parsed.reason as String\n"
-                + "      detectPoint parsed.detect_point as String\n"
-                + "      componentId 49\n"
-                + "    }\n"
-                + "  }\n"
-                + "  sink {}\n"
-                + "}");
-
-        final LALScriptModel.ExtractorBlock extractor =
-            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
-        final LALScriptModel.SampledTraceBlock st =
-            (LALScriptModel.SampledTraceBlock) extractor.getStatements().get(0);
-        assertEquals(5, st.getStatements().size());
-    }
-
     // ==================== If in extractor/sink parsing ====================
 
     @Test
@@ -526,4 +464,37 @@ class LALScriptParserTest {
         assertEquals(1, sampler.getContents().size());
         assertInstanceOf(LALScriptModel.IfBlock.class, sampler.getContents().get(0));
     }
+
+    @Test
+    void parseOutputFieldAssignment() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    service parsed.service as String\n"
+                + "    statement parsed.statement as String\n"
+                + "    latency parsed.latency as Long\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
+        assertEquals(3, extractor.getStatements().size());
+
+        // 'service' is a known field → FieldAssignment
+        assertInstanceOf(LALScriptModel.FieldAssignment.class,
+            extractor.getStatements().get(0));
+
+        // 'statement' is not a known field → OutputFieldAssignment
+        final LALScriptModel.OutputFieldAssignment stmt =
+            (LALScriptModel.OutputFieldAssignment) extractor.getStatements().get(1);
+        assertEquals("statement", stmt.getFieldName());
+        assertEquals("String", stmt.getCastType());
+
+        // 'latency' is not a known field → OutputFieldAssignment
+        final LALScriptModel.OutputFieldAssignment latency =
+            (LALScriptModel.OutputFieldAssignment) extractor.getStatements().get(2);
+        assertEquals("latency", latency.getFieldName());
+        assertEquals("Long", latency.getCastType());
+    }
 }
diff --git a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/TestMeshLALSourceTypeProvider.java b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/TestMeshLALSourceTypeProvider.java
index e1e5df4cfb..7b0010d71e 100644
--- a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/TestMeshLALSourceTypeProvider.java
+++ b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/TestMeshLALSourceTypeProvider.java
@@ -28,7 +28,7 @@ public class TestMeshLALSourceTypeProvider implements LALSourceTypeProvider {
     }
 
     @Override
-    public Class<?> extraLogType() {
+    public Class<?> inputType() {
         return HTTPAccessLogEntry.class;
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/AbstractLog.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/AbstractLog.java
index 63a36d1871..b5b902d292 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/AbstractLog.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/AbstractLog.java
@@ -22,13 +22,15 @@ import java.util.ArrayList;
 import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
-import org.apache.skywalking.oap.server.core.UnexpectedException;
+import org.apache.skywalking.apm.network.logging.v3.LogData;
 import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
 import org.apache.skywalking.oap.server.core.query.type.ContentType;
 
 @Setter
 @Getter
 public abstract class AbstractLog extends Source {
+    private String uniqueId;
     private long timestamp;
     private String serviceId;
     private String serviceInstanceId;
@@ -44,6 +46,18 @@ public abstract class AbstractLog extends Source {
 
     @Override
     public String getEntityId() {
-        throw new UnexpectedException("getEntityId is not supported in AbstractLog source");
+        return uniqueId;
+    }
+
+    /**
+     * Called by the sink listener after common {@link AbstractLog} fields have been populated
+     * from the {@link LogData.Builder}. Subclasses override this to pull custom fields
+     * from tags, body content, or other LogData fields.
+     *
+     * <p>Default implementation is a no-op — the standard {@code Log} class does not need
+     * additional preparation.
+     */
+    public void prepare(final LogData.Builder logData, final NamingControl namingControl) {
+        // No-op by default. Subclasses override to populate custom fields.
     }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LALOutputBuilder.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LALOutputBuilder.java
new file mode 100644
index 0000000000..18d2819985
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LALOutputBuilder.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.source;
+
+import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.config.NamingControl;
+
+/**
+ * Interface for LAL output builders that produce {@link Source} objects from
+ * LAL extractor output fields.
+ *
+ * <p>Implementations are discovered via {@link java.util.ServiceLoader} and
+ * can be referenced in LAL rule YAML config by their short {@link #name()}
+ * (e.g., {@code outputType: SlowSQL}) instead of the fully qualified class name.
+ *
+ * <p>The LAL compiler validates output field assignments against the builder's
+ * setters at compile time. At runtime:
+ * <ol>
+ *   <li>{@link #init} is called to pre-populate standard fields from LogData</li>
+ *   <li>Output field values are applied via reflection (setter invocation)</li>
+ *   <li>{@link #complete} is called to validate, create final Source/Record,
+ *       and dispatch via {@link SourceReceiver}</li>
+ * </ol>
+ */
+public interface LALOutputBuilder {
+    /**
+     * Short name used in LAL rule YAML config {@code outputType} field.
+     * Must be unique across all implementations.
+     */
+    String name();
+
+    /**
+     * Pre-populate standard fields from the log data before custom output
+     * fields are applied. Called once per log entry.
+     */
+    void init(LogData logData, NamingControl namingControl);
+
+    /**
+     * Validate the builder state and dispatch the final output source(s).
+     * Called after all output fields have been set via reflection.
+     */
+    void complete(SourceReceiver sourceReceiver);
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Log.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Log.java
index 6fb5b54939..1b251be6ef 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Log.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/Log.java
@@ -17,21 +17,9 @@
 
 package org.apache.skywalking.oap.server.core.source;
 
-import lombok.Getter;
-import lombok.Setter;
-
-@Setter
-@Getter
 @ScopeDeclaration(id = DefaultScopeDefine.LOG, name = "Log")
 public class Log extends AbstractLog {
 
-    private String uniqueId;
-
-    @Override
-    public String getEntityId() {
-        return uniqueId;
-    }
-
     @Override
     public int scope() {
         return DefaultScopeDefine.LOG;
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyHTTPLALSourceTypeProvider.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyHTTPLALSourceTypeProvider.java
index 9d42d09927..d2a73d568b 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyHTTPLALSourceTypeProvider.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/EnvoyHTTPLALSourceTypeProvider.java
@@ -33,7 +33,7 @@ public class EnvoyHTTPLALSourceTypeProvider implements LALSourceTypeProvider {
     }
 
     @Override
-    public Class<?> extraLogType() {
+    public Class<?> inputType() {
         return HTTPAccessLogEntry.class;
     }
 }
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 31843e8888..ae23160841 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
@@ -53,45 +53,46 @@ rules:
       }
   - name: network-profiling-slow-trace
     layer: MESH
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
+        sink {
+        }
       }
diff --git a/oap-server/server-starter/src/main/resources/lal/k8s-service.yaml b/oap-server/server-starter/src/main/resources/lal/k8s-service.yaml
index 2992b39ed7..ff2d88a8ad 100644
--- a/oap-server/server-starter/src/main/resources/lal/k8s-service.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/k8s-service.yaml
@@ -13,49 +13,49 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# The default LAL script to save all logs, behaving like the versions before 8.5.0.
 rules:
   - name: network-profiling-slow-trace
     layer: K8S_SERVICE
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
-      }
\ No newline at end of file
+        sink {
+        }
+      }
diff --git a/oap-server/server-starter/src/main/resources/lal/mesh-dp.yaml b/oap-server/server-starter/src/main/resources/lal/mesh-dp.yaml
index e8271ef8a7..fcaef7f362 100644
--- a/oap-server/server-starter/src/main/resources/lal/mesh-dp.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/mesh-dp.yaml
@@ -16,45 +16,46 @@
 rules:
   - name: network-profiling-slow-trace
     layer: MESH_DP
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
-      }
\ No newline at end of file
+        sink {
+        }
+      }
diff --git a/oap-server/server-starter/src/main/resources/lal/mysql-slowsql.yaml b/oap-server/server-starter/src/main/resources/lal/mysql-slowsql.yaml
index 774da2955d..de70243206 100644
--- a/oap-server/server-starter/src/main/resources/lal/mysql-slowsql.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/mysql-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: mysql-slowsql
     layer: MYSQL
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }
diff --git a/oap-server/server-starter/src/main/resources/lal/pgsql-slowsql.yaml b/oap-server/server-starter/src/main/resources/lal/pgsql-slowsql.yaml
index be3aeb291d..ed84072251 100644
--- a/oap-server/server-starter/src/main/resources/lal/pgsql-slowsql.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/pgsql-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: pgsql-slowsql
     layer: POSTGRESQL
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }
diff --git a/oap-server/server-starter/src/main/resources/lal/redis-slowsql.yaml b/oap-server/server-starter/src/main/resources/lal/redis-slowsql.yaml
index b4873ad88c..c669e515a9 100644
--- a/oap-server/server-starter/src/main/resources/lal/redis-slowsql.yaml
+++ b/oap-server/server-starter/src/main/resources/lal/redis-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: redis-slowsql
     layer: REDIS
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }
diff --git a/test/e2e-v2/cases/profiling/ebpf/network/kubernetes-values.yaml b/test/e2e-v2/cases/profiling/ebpf/network/kubernetes-values.yaml
index 9a01383129..18d41440bc 100644
--- a/test/e2e-v2/cases/profiling/ebpf/network/kubernetes-values.yaml
+++ b/test/e2e-v2/cases/profiling/ebpf/network/kubernetes-values.yaml
@@ -23,51 +23,52 @@ oap:
         rules:
           - name: network-profiling-slow-trace
             layer: K8S_SERVICE
+            outputType: SampledTrace
             dsl: |
               filter {
                 json{
                 }
                 extractor{
                   if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-                    sampledTrace {
-                      latency parsed.latency as Long
-                      uri ((parsed.trace_provider as String) + "-" + (parsed.uri as String))
-                      reason parsed.reason as String
+                    latency parsed.latency as Long
+                    uri ((parsed.trace_provider as String) + "-" + (parsed.uri as String))
+                    reason parsed.reason as String
 
-                      if (parsed.client_process.process_id as String != "") {
-                       processId parsed.client_process.process_id as String
-                      } else if (parsed.client_process.local as Boolean
-                          || (parsed.client_process.address as String).split(":")[0].endsWith('.1')
-                          || (parsed.client_process.address as String).split(":")[1] == "53") {
-                        processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-                      } else {
-                        processId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
-                      }
+                    if (parsed.client_process.process_id as String != "") {
+                     processId parsed.client_process.process_id as String
+                    } else if (parsed.client_process.local as Boolean
+                        || (parsed.client_process.address as String).split(":")[0].endsWith('.1')
+                        || (parsed.client_process.address as String).split(":")[1] == "53") {
+                      processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+                    } else {
+                      processId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
+                    }
 
-                      if (parsed.server_process.process_id as String != "") {
-                        destProcessId parsed.server_process.process_id as String
-                      } else if (parsed.server_process.local as Boolean
-                          || (parsed.server_process.address as String).split(":")[0].endsWith('.1')
-                          || (parsed.server_process.address as String).split(":")[1] == "53") {
-                        destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-                      } else {
-                        destProcessId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
-                      }
+                    if (parsed.server_process.process_id as String != "") {
+                      destProcessId parsed.server_process.process_id as String
+                    } else if (parsed.server_process.local as Boolean
+                        || (parsed.server_process.address as String).split(":")[0].endsWith('.1')
+                        || (parsed.server_process.address as String).split(":")[1] == "53") {
+                      destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+                    } else {
+                      destProcessId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
+                    }
 
-                      detectPoint parsed.detect_point as String
+                    detectPoint parsed.detect_point as String
 
-                      if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                        componentId 129
-                      } else if (parsed.component as String == "http") {
-                        componentId 49
-                      } else if (parsed.ssl as Boolean) {
-                        componentId 130
-                      } else {
-                        componentId 110
-                      }
+                    if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+                      componentId 129
+                    } else if (parsed.component as String == "http") {
+                      componentId 49
+                    } else if (parsed.ssl as Boolean) {
+                      componentId 130
+                    } else {
+                      componentId 110
                     }
                   }
                 }
+                sink {
+                }
               }
 
     meter-analyzer-config:
diff --git a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/CLAUDE.md b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/CLAUDE.md
index f986ffbf56..967e767e9f 100644
--- a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/CLAUDE.md
+++ b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/CLAUDE.md
@@ -62,7 +62,7 @@ Each LAL rule YAML has a companion `.data.yaml` with per-rule test entries.
 
 **Entry structure:** service, body-type, body, optional tags/extra-log, expect assertions.
 
-**Expect assertions:** save, abort, service, instance, endpoint, layer, tag.*, sampledTrace.*
+**Expect assertions:** save, abort, service, instance, endpoint, layer, tag.*
 
 **Proto-typed rules:** Use `extra-log.proto-class` + `extra-log.proto-json` for protobuf extraLog.
 
diff --git a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/pom.xml b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/pom.xml
index 84c2a3bc25..524eda1522 100644
--- a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/pom.xml
+++ b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/pom.xml
@@ -63,7 +63,7 @@
             <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
-        <!-- Envoy proto types needed to compile envoy-als LAL rules with extraLogType -->
+        <!-- Envoy proto types needed to compile envoy-als LAL rules with inputType -->
         <dependency>
             <groupId>org.apache.skywalking</groupId>
             <artifactId>receiver-proto</artifactId>
diff --git a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalBenchmark.java b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalBenchmark.java
index 5b2d7b9bce..b8178394b2 100644
--- a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalBenchmark.java
+++ b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalBenchmark.java
@@ -147,13 +147,13 @@ public class LalBenchmark {
             final Map<String, Object> ruleInput =
                 inputData != null ? inputData.get(name) : null;
 
-            // Resolve extraLogType via SPI
-            Class<?> extraLogType = null;
+            // Resolve inputType via SPI
+            Class<?> inputType = null;
             if (layer != null) {
-                extraLogType = spiExtraLogTypes().get(layer);
+                inputType = spiInputTypes().get(layer);
             }
 
-            rules.add(new RuleEntry(name, dsl, layer, extraLogType));
+            rules.add(new RuleEntry(name, dsl, layer, inputType));
             testLogs.add(buildLogData(ruleInput, dsl));
             extraLogs.add(buildExtraLog(ruleInput));
         }
@@ -181,8 +181,8 @@ public class LalBenchmark {
             v1Dsls.add(v1Dsl);
 
             final LALClassGenerator gen = new LALClassGenerator();
-            if (rule.extraLogType != null) {
-                gen.setExtraLogType(rule.extraLogType);
+            if (rule.inputType != null) {
+                gen.setInputType(rule.inputType);
             }
             v2Exprs.add(gen.compile(rule.dsl));
         }
@@ -208,8 +208,8 @@ public class LalBenchmark {
         for (final RuleEntry rule : rules) {
             try {
                 final LALClassGenerator gen = new LALClassGenerator();
-                if (rule.extraLogType != null) {
-                    gen.setExtraLogType(rule.extraLogType);
+                if (rule.inputType != null) {
+                    gen.setInputType(rule.inputType);
                 }
                 bh.consume(gen.compile(rule.dsl));
             } catch (Exception ignored) {
@@ -390,12 +390,12 @@ public class LalBenchmark {
 
     // ==================== SPI lookup ====================
 
-    private Map<String, Class<?>> spiExtraLogTypes() {
+    private Map<String, Class<?>> spiInputTypes() {
         if (spiTypes == null) {
             spiTypes = new HashMap<>();
             for (final LALSourceTypeProvider p :
                     ServiceLoader.load(LALSourceTypeProvider.class)) {
-                spiTypes.put(p.layer().name(), p.extraLogType());
+                spiTypes.put(p.layer().name(), p.inputType());
             }
         }
         return spiTypes;
@@ -478,14 +478,14 @@ public class LalBenchmark {
         final String name;
         final String dsl;
         final String layer;
-        final Class<?> extraLogType;
+        final Class<?> inputType;
 
         RuleEntry(final String name, final String dsl,
-                  final String layer, final Class<?> extraLogType) {
+                  final String layer, final Class<?> inputType) {
             this.name = name;
             this.dsl = dsl;
             this.layer = layer;
-            this.extraLogType = extraLogType;
+            this.inputType = inputType;
         }
     }
 
diff --git a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalComparisonTest.java b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalComparisonTest.java
index 51e9c4aa04..5bca085de2 100644
--- a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalComparisonTest.java
+++ b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/LalComparisonTest.java
@@ -37,8 +37,6 @@ import org.apache.skywalking.apm.network.logging.v3.LogDataBody;
 import org.apache.skywalking.apm.network.logging.v3.LogTags;
 import org.apache.skywalking.apm.network.logging.v3.TextLog;
 import org.apache.skywalking.apm.network.logging.v3.TraceContext;
-import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.SampledTraceBuilder;
-import org.apache.skywalking.oap.server.core.analysis.record.Record;
 import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALClassGenerator;
 import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
 import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
@@ -46,6 +44,7 @@ import org.apache.skywalking.oap.log.analyzer.v2.spi.LALSourceTypeProvider;
 import org.apache.skywalking.oap.server.core.CoreModule;
 import org.apache.skywalking.oap.server.core.config.ConfigService;
 import org.apache.skywalking.oap.server.core.config.NamingControl;
+import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
 import org.apache.skywalking.oap.server.core.source.SourceReceiver;
 import org.apache.skywalking.oap.server.library.module.ModuleManager;
 import org.apache.skywalking.oap.server.library.module.ModuleProviderHolder;
@@ -55,13 +54,9 @@ import org.junit.jupiter.api.TestFactory;
 import org.yaml.snakeyaml.Yaml;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 /**
@@ -99,21 +94,24 @@ class LalComparisonTest {
                 if (rule.inputs.isEmpty()) {
                     final org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression expr = v2Expr;
                     final String err = v2CompileError;
+                    final boolean v2Only = rule.outputType != null;
                     tests.add(DynamicTest.dynamicTest(
                         yamlFile + " | " + rule.name,
-                        () -> compareExecution(rule.name, rule.dsl, null, expr, err)
+                        () -> compareExecution(rule.name, rule.dsl,
+                            null, expr, err, v2Only)
                     ));
                 } else {
                     for (int i = 0; i < rule.inputs.size(); i++) {
                         final int idx = i;
                         final org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression expr = v2Expr;
                         final String err = v2CompileError;
+                        final boolean v2Only = rule.outputType != null;
                         final String testName = rule.inputs.size() == 1
                             ? rule.name : rule.name + " [" + i + "]";
                         tests.add(DynamicTest.dynamicTest(
                             yamlFile + " | " + testName,
                             () -> compareExecution(testName, rule.dsl,
-                                rule.inputs.get(idx), expr, err)
+                                rule.inputs.get(idx), expr, err, v2Only)
                         ));
                     }
                 }
@@ -126,12 +124,16 @@ class LalComparisonTest {
     private org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression compileV2(
             final LalRule rule) throws Exception {
         final LALClassGenerator generator = new LALClassGenerator();
-        if (rule.extraLogType != null) {
-            generator.setExtraLogType(Class.forName(rule.extraLogType));
+        if (rule.inputType != null) {
+            generator.setInputType(Class.forName(rule.inputType));
         } else if (rule.layer != null) {
-            generator.setExtraLogType(spiExtraLogTypes().get(rule.layer));
+            generator.setInputType(spiInputTypes().get(rule.layer));
         } else {
-            generator.setExtraLogType(null);
+            generator.setInputType(null);
+        }
+        final Class<?> resolvedOutput = resolveOutputType(rule.outputType);
+        if (resolvedOutput != null) {
+            generator.setOutputType(resolvedOutput);
         }
         if (rule.sourceFile != null) {
             final String baseName = rule.sourceFile.getName()
@@ -151,36 +153,39 @@ class LalComparisonTest {
             final String testName, final String dsl,
             final Map<String, Object> inputData,
             final org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression v2Expr,
-            final String v2CompileError) throws Exception {
+            final String v2CompileError,
+            final boolean v2Only) throws Exception {
 
         final LogData testLog = buildLogData(inputData, dsl);
 
         // Build proto extraLog from input data if available
         final Message extraLog = buildExtraLog(inputData);
 
-        // ---- V1: Groovy path ----
-        // v1 uses original packages: org.apache.skywalking.oap.log.analyzer.dsl.*
-        final ModuleManager v1Manager = buildMockModuleManager(true);
-        final org.apache.skywalking.oap.log.analyzer.dsl.Binding v1Ctx;
-        try {
-            final org.apache.skywalking.oap.log.analyzer.dsl.DSL v1Dsl =
-                org.apache.skywalking.oap.log.analyzer.dsl.DSL.of(
-                    v1Manager,
-                    new org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig(),
-                    dsl);
-            disableSinkListeners(v1Dsl);
-
-            v1Ctx = new org.apache.skywalking.oap.log.analyzer.dsl.Binding().log(testLog);
-            if (extraLog != null) {
-                v1Ctx.extraLog(extraLog);
+        // ---- V1: Groovy path (skipped for v2-only rules like outputType) ----
+        org.apache.skywalking.oap.log.analyzer.dsl.Binding v1Ctx = null;
+        if (!v2Only) {
+            // v1 uses original packages: org.apache.skywalking.oap.log.analyzer.dsl.*
+            final ModuleManager v1Manager = buildMockModuleManager(true);
+            try {
+                final org.apache.skywalking.oap.log.analyzer.dsl.DSL v1Dsl =
+                    org.apache.skywalking.oap.log.analyzer.dsl.DSL.of(
+                        v1Manager,
+                        new org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig(),
+                        dsl);
+                disableSinkListeners(v1Dsl);
+
+                v1Ctx = new org.apache.skywalking.oap.log.analyzer.dsl.Binding().log(testLog);
+                if (extraLog != null) {
+                    v1Ctx.extraLog(extraLog);
+                }
+                v1Dsl.bind(v1Ctx);
+                v1Dsl.evaluate();
+            } catch (Exception e) {
+                final Throwable cause = e.getCause() != null ? e.getCause() : e;
+                fail(testName + ": v1 (Groovy) failed — "
+                    + cause.getClass().getSimpleName() + ": " + cause.getMessage());
+                return;
             }
-            v1Dsl.bind(v1Ctx);
-            v1Dsl.evaluate();
-        } catch (Exception e) {
-            final Throwable cause = e.getCause() != null ? e.getCause() : e;
-            fail(testName + ": v1 (Groovy) failed — "
-                + cause.getClass().getSimpleName() + ": " + cause.getMessage());
-            return;
         }
 
         // ---- V2: ANTLR4 + Javassist path ----
@@ -209,95 +214,33 @@ class LalComparisonTest {
 
         // ---- Compare ----
         if (v2Ctx == null) {
-            fail(testName + ": v2 execution failed but v1 succeeded — " + v2Error);
+            final String context = v2Only ? "v2 execution failed" : "v2 execution failed but v1 succeeded";
+            fail(testName + ": " + context + " — " + v2Error);
             return;
         }
 
-        // Compare binding state
-        assertEquals(v1Ctx.shouldAbort(), v2Ctx.shouldAbort(),
-            testName + ": shouldAbort mismatch");
-        assertEquals(v1Ctx.shouldSave(), v2Ctx.shouldSave(),
-            testName + ": shouldSave mismatch");
-
-        final LogData.Builder v1Log = v1Ctx.log();
-        final LogData.Builder v2Log = v2Ctx.log();
-
-        assertEquals(v1Log.getService(), v2Log.getService(),
-            testName + ": service mismatch");
-        assertEquals(v1Log.getServiceInstance(), v2Log.getServiceInstance(),
-            testName + ": serviceInstance mismatch");
-        assertEquals(v1Log.getEndpoint(), v2Log.getEndpoint(),
-            testName + ": endpoint mismatch");
-        assertEquals(v1Log.getLayer(), v2Log.getLayer(),
-            testName + ": layer mismatch");
-        assertEquals(v1Log.getTimestamp(), v2Log.getTimestamp(),
-            testName + ": timestamp mismatch");
-        assertEquals(v1Log.getTags(), v2Log.getTags(),
-            testName + ": tags mismatch");
-
-        // Compare sampledTrace builder state
-        // v1 Groovy Binding throws MissingPropertyException if sampledTrace was never set
-        SampledTraceBuilder v1St = null;
-        try {
-            v1St = v1Ctx.sampledTraceBuilder();
-        } catch (Exception ignored) {
-            // Not set — rule has no sampledTrace block
-        }
-        final SampledTraceBuilder v2St = v2Ctx.sampledTraceBuilder();
-        if (v1St != null || v2St != null) {
-            if (v1St == null) {
-                fail(testName + ": v1 has no sampledTrace but v2 does");
-            }
-            if (v2St == null) {
-                fail(testName + ": v2 has no sampledTrace but v1 does");
-            }
-            // Fields set by prepareSampledTrace() from log context
-            assertEquals(v1St.getTraceId(), v2St.getTraceId(),
-                testName + ": sampledTrace.traceId mismatch");
-            assertEquals(v1St.getServiceName(), v2St.getServiceName(),
-                testName + ": sampledTrace.serviceName mismatch");
-            assertEquals(v1St.getServiceInstanceName(), v2St.getServiceInstanceName(),
-                testName + ": sampledTrace.serviceInstanceName mismatch");
-            assertEquals(v1St.getLayer(), v2St.getLayer(),
-                testName + ": sampledTrace.layer mismatch");
-            assertEquals(v1St.getTimestamp(), v2St.getTimestamp(),
-                testName + ": sampledTrace.timestamp mismatch");
-
-            // Verify traceId came from the log (not empty/fabricated)
-            assertEquals(testLog.getTraceContext().getTraceId(),
-                v2St.getTraceId(),
-                testName + ": sampledTrace.traceId should match log traceId");
-
-            // Fields set by DSL closure body
-            assertEquals(v1St.getLatency(), v2St.getLatency(),
-                testName + ": sampledTrace.latency mismatch");
-            assertEquals(v1St.getUri(), v2St.getUri(),
-                testName + ": sampledTrace.uri mismatch");
-            assertEquals(v1St.getReason(), v2St.getReason(),
-                testName + ": sampledTrace.reason mismatch");
-            assertEquals(v1St.getProcessId(), v2St.getProcessId(),
-                testName + ": sampledTrace.processId mismatch");
-            assertEquals(v1St.getDestProcessId(), v2St.getDestProcessId(),
-                testName + ": sampledTrace.destProcessId mismatch");
-            assertEquals(v1St.getDetectPoint(), v2St.getDetectPoint(),
-                testName + ": sampledTrace.detectPoint mismatch");
-            assertEquals(v1St.getComponentId(), v2St.getComponentId(),
-                testName + ": sampledTrace.componentId mismatch");
-
-            // Verify builder.toRecord() produces valid Record for RecordStreamProcessor
-            // (submitSampledTrace already called validate + toRecord + RecordStreamProcessor.in
-            // during execution; this explicitly confirms toRecord consistency)
-            final Record v1Record = v1St.toRecord();
-            final Record v2Record = v2St.toRecord();
-            assertNotNull(v1Record, testName + ": v1 toRecord() returned null");
-            assertNotNull(v2Record, testName + ": v2 toRecord() returned null");
-            assertEquals(v1Record.getClass(), v2Record.getClass(),
-                testName + ": toRecord() type mismatch");
-
-            // Verify v2 actually dispatched the trace via sourceReceiver.receive()
-            final SourceReceiver v2Receiver = v2Manager.find(CoreModule.NAME)
-                .provider().getService(SourceReceiver.class);
-            verify(v2Receiver, atLeastOnce()).receive(any());
+        if (v1Ctx != null) {
+            // Compare binding state
+            assertEquals(v1Ctx.shouldAbort(), v2Ctx.shouldAbort(),
+                testName + ": shouldAbort mismatch");
+            assertEquals(v1Ctx.shouldSave(), v2Ctx.shouldSave(),
+                testName + ": shouldSave mismatch");
+
+            final LogData.Builder v1Log = v1Ctx.log();
+            final LogData.Builder v2Log = v2Ctx.log();
+
+            assertEquals(v1Log.getService(), v2Log.getService(),
+                testName + ": service mismatch");
+            assertEquals(v1Log.getServiceInstance(), v2Log.getServiceInstance(),
+                testName + ": serviceInstance mismatch");
+            assertEquals(v1Log.getEndpoint(), v2Log.getEndpoint(),
+                testName + ": endpoint mismatch");
+            assertEquals(v1Log.getLayer(), v2Log.getLayer(),
+                testName + ": layer mismatch");
+            assertEquals(v1Log.getTimestamp(), v2Log.getTimestamp(),
+                testName + ": timestamp mismatch");
+            assertEquals(v1Log.getTags(), v2Log.getTags(),
+                testName + ": tags mismatch");
         }
 
         // ---- Validate expected section ----
@@ -306,6 +249,7 @@ class LalComparisonTest {
             final Map<String, Object> expect =
                 (Map<String, Object>) inputData.get("expect");
             if (expect != null) {
+                final LogData.Builder v2Log = v2Ctx.log();
                 validateExpected(testName, v2Ctx, v2Log, expect);
             }
         }
@@ -358,74 +302,19 @@ class LalComparisonTest {
                             .findFirst().orElse("");
                         assertEquals(expected, actual,
                             ruleName + ": expect." + key + " mismatch");
-                    } else if (key.startsWith("sampledTrace.")) {
-                        final String field = key.substring("sampledTrace.".length());
-                        final SampledTraceBuilder st = ctx.sampledTraceBuilder();
-                        assertNotNull(st, ruleName + ": expect sampledTrace but builder is null");
-                        validateSampledTraceField(ruleName, field, expected, st);
+                    } else if (key.startsWith("outputField.")) {
+                        final String fieldName = key.substring("outputField.".length());
+                        final Map<String, Object> outputFields = ctx.outputFields();
+                        final Object actual = outputFields != null
+                            ? outputFields.get(fieldName) : null;
+                        assertEquals(expected, String.valueOf(actual),
+                            ruleName + ": expect." + key + " mismatch");
                     }
                     break;
             }
         }
     }
 
-    private void validateSampledTraceField(final String ruleName,
-                                           final String field, final String expected,
-                                           final SampledTraceBuilder st) {
-        switch (field) {
-            case "traceId":
-                assertEquals(expected, st.getTraceId(),
-                    ruleName + ": expect.sampledTrace.traceId mismatch");
-                break;
-            case "serviceName":
-                assertEquals(expected, st.getServiceName(),
-                    ruleName + ": expect.sampledTrace.serviceName mismatch");
-                break;
-            case "serviceInstanceName":
-                assertEquals(expected, st.getServiceInstanceName(),
-                    ruleName + ": expect.sampledTrace.serviceInstanceName mismatch");
-                break;
-            case "timestamp":
-                assertEquals(Long.parseLong(expected), st.getTimestamp(),
-                    ruleName + ": expect.sampledTrace.timestamp mismatch");
-                break;
-            case "latency":
-                assertEquals(Integer.parseInt(expected), st.getLatency(),
-                    ruleName + ": expect.sampledTrace.latency mismatch");
-                break;
-            case "uri":
-                assertEquals(expected, st.getUri(),
-                    ruleName + ": expect.sampledTrace.uri mismatch");
-                break;
-            case "reason":
-                assertNotNull(st.getReason(),
-                    ruleName + ": expect.sampledTrace.reason is null");
-                assertEquals(expected, st.getReason().name(),
-                    ruleName + ": expect.sampledTrace.reason mismatch");
-                break;
-            case "processId":
-                assertEquals(expected, st.getProcessId(),
-                    ruleName + ": expect.sampledTrace.processId mismatch");
-                break;
-            case "destProcessId":
-                assertEquals(expected, st.getDestProcessId(),
-                    ruleName + ": expect.sampledTrace.destProcessId mismatch");
-                break;
-            case "detectPoint":
-                assertNotNull(st.getDetectPoint(),
-                    ruleName + ": expect.sampledTrace.detectPoint is null");
-                assertEquals(expected, st.getDetectPoint().name(),
-                    ruleName + ": expect.sampledTrace.detectPoint mismatch");
-                break;
-            case "componentId":
-                assertEquals(Integer.parseInt(expected), st.getComponentId(),
-                    ruleName + ": expect.sampledTrace.componentId mismatch");
-                break;
-            default:
-                break;
-        }
-    }
-
     private ModuleManager buildMockModuleManager(final boolean isV1) {
         final ModuleManager manager = mock(ModuleManager.class);
         setInternalField(manager, "isInPrepareStage", false);
@@ -671,7 +560,8 @@ class LalComparisonTest {
                 if (name == null || dslStr == null) {
                     continue;
                 }
-                final String extraLogType = rule.get("extraLogType");
+                final String inputType = rule.get("inputType");
+                final String outputType = rule.get("outputType");
                 final String layer = rule.get("layer");
                 final int count = nameCount.merge(name, 1, Integer::sum);
                 final int lineNo = findRuleLine(lines, name, count);
@@ -689,7 +579,8 @@ class LalComparisonTest {
                     inputs = Collections.emptyList();
                 }
                 lalRules.add(new LalRule(
-                    name, dslStr, extraLogType, layer, inputs, file, lineNo));
+                    name, dslStr, inputType, outputType, layer,
+                    inputs, file, lineNo));
             }
 
             if (!lalRules.isEmpty()) {
@@ -763,35 +654,64 @@ class LalComparisonTest {
 
     private Map<String, Class<?>> spiTypes;
 
-    private Map<String, Class<?>> spiExtraLogTypes() {
+    private Map<String, Class<?>> spiInputTypes() {
         if (spiTypes == null) {
             spiTypes = new HashMap<>();
             for (final LALSourceTypeProvider p :
                     ServiceLoader.load(LALSourceTypeProvider.class)) {
-                spiTypes.put(p.layer().name(), p.extraLogType());
+                spiTypes.put(p.layer().name(), p.inputType());
             }
         }
         return spiTypes;
     }
 
+    private Map<String, Class<?>> outputBuilderNames;
+
+    private Map<String, Class<?>> outputBuilderNameMap() {
+        if (outputBuilderNames == null) {
+            outputBuilderNames = new HashMap<>();
+            for (final LALOutputBuilder builder :
+                    ServiceLoader.load(LALOutputBuilder.class)) {
+                outputBuilderNames.put(builder.name(), builder.getClass());
+            }
+        }
+        return outputBuilderNames;
+    }
+
+    private Class<?> resolveOutputType(final String outputType) throws ClassNotFoundException {
+        if (outputType == null) {
+            return null;
+        }
+        if (!outputType.contains(".")) {
+            final Class<?> byName = outputBuilderNameMap().get(outputType);
+            if (byName != null) {
+                return byName;
+            }
+        }
+        return Class.forName(outputType);
+    }
+
     // ==================== Inner classes ====================
 
     private static class LalRule {
         final String name;
         final String dsl;
-        final String extraLogType;
+        final String inputType;
+        final String outputType;
         final String layer;
         final List<Map<String, Object>> inputs;
         final File sourceFile;
         final int lineNo;
 
         LalRule(final String name, final String dsl,
-                final String extraLogType, final String layer,
+                final String inputType, final String outputType,
+                final String layer,
                 final List<Map<String, Object>> inputs,
                 final File sourceFile, final int lineNo) {
             this.name = name;
             this.dsl = dsl;
-            this.extraLogType = extraLogType;
+            this.inputType = inputType;
+            this.outputType = outputType;
             this.layer = layer;
             this.inputs = inputs;
             this.sourceFile = sourceFile;
diff --git a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/TestMeshLALSourceTypeProvider.java b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/TestMeshLALSourceTypeProvider.java
index bcaf35f654..4b89babaad 100644
--- a/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/TestMeshLALSourceTypeProvider.java
+++ b/test/script-cases/script-runtime-with-groovy/mal-lal-v1-v2-checker/src/test/java/org/apache/skywalking/oap/server/checker/lal/TestMeshLALSourceTypeProvider.java
@@ -28,7 +28,7 @@ public class TestMeshLALSourceTypeProvider implements LALSourceTypeProvider {
     }
 
     @Override
-    public Class<?> extraLogType() {
+    public Class<?> inputType() {
         return HTTPAccessLogEntry.class;
     }
 }
diff --git a/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml b/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
index b165383513..16678e2092 100644
--- a/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.data.yaml
@@ -138,24 +138,3 @@ text-parser-regexp:
   body: "1609459200000 ERROR Something failed"
   expect:
     service: ERROR
-
-sampled-trace-basic:
-  body-type: json
-  body: '{"latency":150,"uri":"/test","reason":"slow","pid":"proc-a","dpid":"proc-b","dp":"client"}'
-  service: trace-svc
-  instance: trace-inst
-  trace-id: trace-basic-001
-  timestamp: 1609459200000
-  expect:
-    save: true
-    sampledTrace.traceId: trace-basic-001
-    sampledTrace.serviceName: trace-svc
-    sampledTrace.serviceInstanceName: trace-inst
-    sampledTrace.timestamp: 1609459200000
-    sampledTrace.latency: 150
-    sampledTrace.uri: /test
-    sampledTrace.reason: SLOW
-    sampledTrace.processId: proc-a
-    sampledTrace.destProcessId: proc-b
-    sampledTrace.detectPoint: CLIENT
-    sampledTrace.componentId: 49
diff --git a/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.yaml b/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.yaml
index 71e3bb7d40..c820724bec 100644
--- a/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.yaml
+++ b/test/script-cases/scripts/lal/test-lal/feature-cases/execution-basic.yaml
@@ -252,21 +252,3 @@ rules:
         }
         sink {}
       }
-
-  - name: sampled-trace-basic
-    layer: MESH_DP
-    dsl: |
-      filter {
-        json {}
-        extractor {
-          sampledTrace {
-            latency parsed.latency as Long
-            uri parsed.uri as String
-            reason parsed.reason as String
-            processId parsed.pid as String
-            destProcessId parsed.dpid as String
-            detectPoint parsed.dp as String
-            componentId 49
-          }
-        }
-      }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
index cf400f7661..1b644ec757 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.data.yaml
@@ -48,64 +48,16 @@ envoy-als:
       tag.response.flag: "upstream_connection_failure: true\n"
 
 network-profiling-slow-trace:
-  - body-type: json
-    body: '{"latency":200,"uri":"/mesh/api","reason":"slow","client_process":{"process_id":"client-proc-1","local":false,"address":"10.0.0.1:8080"},"server_process":{"process_id":"server-proc-2","local":true,"address":"10.0.0.2:9090"},"detect_point":"client","component":"http","ssl":false}'
-    service: mesh-envoy-svc
-    instance: mesh-envoy-instance
-    trace-id: trace-envoy-mesh-001
-    timestamp: 1609459200000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-envoy-mesh-001
-      sampledTrace.processId: client-proc-1
-      sampledTrace.destProcessId: server-proc-2
-      sampledTrace.componentId: 49
-
-  - body-type: json
-    body: '{"latency":500,"uri":"/mesh/api/ssl","reason":"slow","client_process":{"process_id":"","local":true,"address":"127.0.0.1:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.3:9090"},"detect_point":"server","component":"http","ssl":true}'
-    service: mesh-envoy-svc-ssl
-    instance: mesh-envoy-instance-ssl
-    trace-id: trace-envoy-mesh-002
-    timestamp: 1609459300000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-envoy-mesh-002
-      # processId/destProcessId omitted: virtual process IDs depend on ProcessRegistry
-      # implementation (mock vs production). Validated via v1-v2 comparison in checker test.
-      sampledTrace.componentId: 129
-
-  - body-type: json
-    body: '{"latency":100,"uri":"/mesh/api/tcp","reason":"slow","client_process":{"process_id":"","local":false,"address":"10.0.0.4:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.5:9090"},"detect_point":"client","component":"tcp","ssl":true}'
-    service: mesh-envoy-svc-tcp-ssl
-    instance: mesh-envoy-instance-tcp-ssl
-    trace-id: trace-envoy-mesh-003
-    timestamp: 1609459400000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      # processId/destProcessId omitted: virtual process IDs depend on ProcessRegistry
-      # implementation (mock vs production). Validated via v1-v2 comparison in checker test.
-      sampledTrace.componentId: 130
-
-  - body-type: json
-    body: '{"latency":50,"uri":"/mesh/api/other","reason":"slow","client_process":{"process_id":"","local":false,"address":"10.0.0.6:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.7:9090"},"detect_point":"client","component":"other","ssl":false}'
-    service: mesh-envoy-svc-other
-    instance: mesh-envoy-instance-other
-    trace-id: trace-envoy-mesh-004
-    timestamp: 1609459500000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      # processId/destProcessId omitted: virtual process IDs depend on ProcessRegistry
-      # implementation (mock vs production). Validated via v1-v2 comparison in checker test.
-      sampledTrace.componentId: 110
+  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}'
+  tags:
+    LOG_KIND: NET_PROFILING_SAMPLED_TRACE
+  expect:
+    save: true
+    abort: false
+    outputField.latency: 800
+    outputField.uri: /mesh/api
+    outputField.reason: SLOW
+    outputField.processId: proc-c1
+    outputField.detectPoint: SERVER
+    outputField.componentId: 49
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.yaml
index 4e2708e0d2..ae23160841 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/envoy-als.yaml
@@ -14,8 +14,11 @@
 # limitations under the License.
 
 rules:
-  # The extra log type (io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry) is
-  # registered via SPI by EnvoyHTTPLALSourceTypeProvider in envoy-metrics-receiver-plugin.
+  # The envoy-als rule has no json/yaml/text parser — it accesses protobuf fields
+  # directly via parsed.* (e.g. parsed?.response?.responseCode?.value).
+  # The extra log type (HTTPAccessLogEntry) is registered via SPI by
+  # EnvoyHTTPLALSourceTypeProvider in envoy-metrics-receiver-plugin,
+  # enabling the compiler to generate direct proto getter calls.
   - name: envoy-als
     layer: MESH
     dsl: |
@@ -50,45 +53,46 @@ rules:
       }
   - name: network-profiling-slow-trace
     layer: MESH
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
+        sink {
+        }
       }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.data.yaml
index 79c4d96dcd..76f8fb003a 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.data.yaml
@@ -14,110 +14,18 @@
 # limitations under the License.
 
 # Mock input data for k8s-service.yaml rules.
-# Covers all branches: process_id resolution (direct/virtual-local/virtual-remote),
-# component+ssl combinations (http+ssl/http/tcp+ssl/other), and LOG_KIND false path.
 
 network-profiling-slow-trace:
-  # [0] Direct process IDs (non-empty), HTTPS (http+ssl=true) → componentId 129
-  - body-type: json
-    body: '{"latency":350,"uri":"/k8s/endpoint","reason":"status_5xx","client_process":{"process_id":"k8s-client-proc","local":true,"address":"10.1.0.1:80"},"server_process":{"process_id":"k8s-server-proc","local":false,"address":"10.1.0.2:443"},"detect_point":"server","component":"http","ssl":true}'
-    service: k8s-test-svc
-    instance: k8s-test-instance
-    trace-id: trace-k8s-001
-    timestamp: 1609459300000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-k8s-001
-      sampledTrace.serviceName: k8s-test-svc
-      sampledTrace.serviceInstanceName: k8s-test-instance
-      sampledTrace.timestamp: 1609459300000
-      sampledTrace.latency: 350
-      sampledTrace.uri: /k8s/endpoint
-      sampledTrace.reason: STATUS_5XX
-      sampledTrace.processId: k8s-client-proc
-      sampledTrace.destProcessId: k8s-server-proc
-      sampledTrace.detectPoint: SERVER
-      sampledTrace.componentId: 129
-
-  # [1] Virtual local process: client process_id empty + local=true → generateVirtualLocalProcess()
-  #     Virtual remote process: server process_id empty + local=false → generateVirtualRemoteProcess()
-  #     HTTP without SSL → componentId 49
-  - body-type: json
-    body: '{"latency":200,"uri":"/k8s/api","reason":"slow","client_process":{"process_id":"","local":true,"address":"10.1.0.3:8080"},"server_process":{"process_id":"","local":false,"address":"10.1.0.4:9090"},"detect_point":"client","component":"http","ssl":false}'
-    service: k8s-svc-virtual
-    instance: k8s-inst-virtual
-    trace-id: trace-k8s-002
-    timestamp: 1609459400000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-k8s-002
-      sampledTrace.latency: 200
-      sampledTrace.uri: /k8s/api
-      sampledTrace.reason: SLOW
-      # processId/destProcessId omitted: virtual process IDs depend on ProcessRegistry
-      # implementation (mock vs production). Validated via v1-v2 comparison in checker test.
-      sampledTrace.detectPoint: CLIENT
-      sampledTrace.componentId: 49
-
-  # [2] Virtual remote process: client process_id empty + local=false → generateVirtualRemoteProcess()
-  #     Virtual local process: server process_id empty + local=true → generateVirtualLocalProcess()
-  #     TCP with SSL → componentId 130
-  - body-type: json
-    body: '{"latency":100,"uri":"/k8s/tcp","reason":"slow","client_process":{"process_id":"","local":false,"address":"10.1.0.5:8080"},"server_process":{"process_id":"","local":true,"address":"10.1.0.6:9090"},"detect_point":"client","component":"tcp","ssl":true}'
-    service: k8s-svc-tcp-ssl
-    instance: k8s-inst-tcp-ssl
-    trace-id: trace-k8s-003
-    timestamp: 1609459500000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-k8s-003
-      sampledTrace.latency: 100
-      sampledTrace.uri: /k8s/tcp
-      sampledTrace.reason: SLOW
-      # processId/destProcessId omitted: virtual process IDs depend on ProcessRegistry
-      # implementation (mock vs production). Validated via v1-v2 comparison in checker test.
-      sampledTrace.detectPoint: CLIENT
-      sampledTrace.componentId: 130
-
-  # [3] Default component: component=other, ssl=false → componentId 110
-  - body-type: json
-    body: '{"latency":50,"uri":"/k8s/other","reason":"slow","client_process":{"process_id":"other-client","local":false,"address":"10.1.0.7:8080"},"server_process":{"process_id":"other-server","local":false,"address":"10.1.0.8:9090"},"detect_point":"server","component":"other","ssl":false}'
-    service: k8s-svc-other
-    instance: k8s-inst-other
-    trace-id: trace-k8s-004
-    timestamp: 1609459600000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-k8s-004
-      sampledTrace.latency: 50
-      sampledTrace.uri: /k8s/other
-      sampledTrace.reason: SLOW
-      sampledTrace.processId: other-client
-      sampledTrace.destProcessId: other-server
-      sampledTrace.detectPoint: SERVER
-      sampledTrace.componentId: 110
-
-  # [4] LOG_KIND false path: tag is not NET_PROFILING_SAMPLED_TRACE → no sampledTrace
-  - body-type: json
-    body: '{"latency":300,"uri":"/k8s/normal","reason":"slow","client_process":{"process_id":"proc-a","local":false,"address":"10.1.0.9:80"},"server_process":{"process_id":"proc-b","local":false,"address":"10.1.0.10:443"},"detect_point":"client","component":"http","ssl":true}'
-    service: k8s-svc-normal
-    instance: k8s-inst-normal
-    trace-id: trace-k8s-005
-    timestamp: 1609459700000
-    tags:
-      LOG_KIND: NORMAL_LOG
-    expect:
-      save: true
-      abort: false
+  body-type: json
+  body: '{"latency":1200,"uri":"/api/orders","reason":"STATUS_5XX","service":"k8s-svc","serviceInstance":"k8s-inst-1","client_process":{"process_id":"","local":true,"address":""},"server_process":{"process_id":"proc-server-2","local":false,"address":"10.0.1.2:9090"},"detect_point":"SERVER","component":"http","ssl":true}'
+  tags:
+    LOG_KIND: NET_PROFILING_SAMPLED_TRACE
+  expect:
+    save: true
+    abort: false
+    outputField.latency: 1200
+    outputField.uri: /api/orders
+    outputField.reason: STATUS_5XX
+    outputField.detectPoint: SERVER
+    outputField.destProcessId: proc-server-2
+    outputField.componentId: 129
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.yaml
index 2992b39ed7..ff2d88a8ad 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/k8s-service.yaml
@@ -13,49 +13,49 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# The default LAL script to save all logs, behaving like the versions before 8.5.0.
 rules:
   - name: network-profiling-slow-trace
     layer: K8S_SERVICE
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
-      }
\ No newline at end of file
+        sink {
+        }
+      }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.data.yaml
index 65ac8bf42b..69a860cf59 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.data.yaml
@@ -14,76 +14,19 @@
 # limitations under the License.
 
 # Mock input data for mesh-dp.yaml rules.
-# Covers all conditional branches: process resolution (explicit, local, remote),
-# component types (http, tcp, other), and SSL combinations.
 
 network-profiling-slow-trace:
-  # Case 1: HTTP + no SSL, explicit process IDs (componentId 49)
-  - body-type: json
-    body: '{"latency":200,"uri":"/mesh/api","reason":"slow","client_process":{"process_id":"client-proc-1","local":false,"address":"10.0.0.1:8080"},"server_process":{"process_id":"server-proc-2","local":true,"address":"10.0.0.2:9090"},"detect_point":"client","component":"http","ssl":false}'
-    service: mesh-test-svc
-    instance: mesh-test-instance
-    trace-id: trace-mesh-001
-    timestamp: 1609459200000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-mesh-001
-      sampledTrace.serviceName: mesh-test-svc
-      sampledTrace.serviceInstanceName: mesh-test-instance
-      sampledTrace.timestamp: 1609459200000
-      sampledTrace.latency: 200
-      sampledTrace.uri: /mesh/api
-      sampledTrace.reason: SLOW
-      sampledTrace.processId: client-proc-1
-      sampledTrace.destProcessId: server-proc-2
-      sampledTrace.detectPoint: CLIENT
-      sampledTrace.componentId: 49
-
-  # Case 2: HTTP + SSL, virtual local/remote process IDs (componentId 129)
-  - body-type: json
-    body: '{"latency":500,"uri":"/mesh/api/ssl","reason":"slow","client_process":{"process_id":"","local":true,"address":"127.0.0.1:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.3:9090"},"detect_point":"server","component":"http","ssl":true}'
-    service: mesh-test-svc-ssl
-    instance: mesh-test-instance-ssl
-    trace-id: trace-mesh-002
-    timestamp: 1609459300000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.traceId: trace-mesh-002
-      # processId/destProcessId: virtual IDs from ProcessRegistry mock.
-      # Validated via v1-v2 comparison (not asserted here).
-      sampledTrace.componentId: 129
-      sampledTrace.detectPoint: SERVER
-
-  # Case 3: non-HTTP + SSL (componentId 130), both remote
-  - body-type: json
-    body: '{"latency":100,"uri":"/mesh/api/tcp","reason":"slow","client_process":{"process_id":"","local":false,"address":"10.0.0.4:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.5:9090"},"detect_point":"client","component":"tcp","ssl":true}'
-    service: mesh-test-svc-tcp-ssl
-    instance: mesh-test-instance-tcp-ssl
-    trace-id: trace-mesh-003
-    timestamp: 1609459400000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.componentId: 130
-
-  # Case 4: non-HTTP + no SSL (componentId 110), both remote
-  - body-type: json
-    body: '{"latency":50,"uri":"/mesh/api/other","reason":"slow","client_process":{"process_id":"","local":false,"address":"10.0.0.6:8080"},"server_process":{"process_id":"","local":false,"address":"10.0.0.7:9090"},"detect_point":"client","component":"other","ssl":false}'
-    service: mesh-test-svc-other
-    instance: mesh-test-instance-other
-    trace-id: trace-mesh-004
-    timestamp: 1609459500000
-    tags:
-      LOG_KIND: NET_PROFILING_SAMPLED_TRACE
-    expect:
-      save: true
-      abort: false
-      sampledTrace.componentId: 110
+  body-type: json
+  body: '{"latency":500,"uri":"/api/test","reason":"SLOW","service":"mesh-svc","serviceInstance":"mesh-inst-1","client_process":{"process_id":"proc-client-1","local":false,"address":"10.0.0.1:8080"},"server_process":{"process_id":"proc-server-1","local":false,"address":"10.0.0.2:8080"},"detect_point":"CLIENT","component":"http","ssl":false}'
+  tags:
+    LOG_KIND: NET_PROFILING_SAMPLED_TRACE
+  expect:
+    save: true
+    abort: false
+    outputField.latency: 500
+    outputField.uri: /api/test
+    outputField.reason: SLOW
+    outputField.processId: proc-client-1
+    outputField.destProcessId: proc-server-1
+    outputField.detectPoint: CLIENT
+    outputField.componentId: 49
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.yaml
index e8271ef8a7..fcaef7f362 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/mesh-dp.yaml
@@ -16,45 +16,46 @@
 rules:
   - name: network-profiling-slow-trace
     layer: MESH_DP
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri parsed.uri as String
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri parsed.uri as String
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-                processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean) {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+              processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean) {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.client_process.address as String) as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean) {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean) {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualRemoteProcess(parsed.service as String, parsed.serviceInstance as String, parsed.server_process.address as String) as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
-      }
\ No newline at end of file
+        sink {
+        }
+      }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml
index 49d3f2ec27..7eb9c3bd8f 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.data.yaml
@@ -17,10 +17,15 @@
 
 mysql-slowsql:
   body-type: json
-  body: '{"layer":"MYSQL","service":"db-svc","time":"1609459200000","id":"slow-1","statement":"SELECT 1","query_time":500}'
+  body: '{"layer":"MYSQL","service":"mysql::test-db","time":"1609459200000","id":"slow-001","statement":"SELECT * FROM users WHERE name LIKE \"%test%\"","query_time":3000}'
   tags:
     LOG_KIND: SLOW_SQL
   expect:
-    service: db-svc
+    save: true
+    abort: false
     layer: MYSQL
-    timestamp: 1609459200000
+    service: mysql::test-db
+    timestamp: "1609459200000"
+    outputField.id: slow-001
+    outputField.statement: "SELECT * FROM users WHERE name LIKE \"%test%\""
+    outputField.latency: 3000
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.yaml
index 774da2955d..de70243206 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/mysql-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: mysql-slowsql
     layer: MYSQL
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/network-profiling-e2e.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/network-profiling-e2e.yaml
index a9a78d8ec7..e421a86fe1 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/network-profiling-e2e.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/network-profiling-e2e.yaml
@@ -23,49 +23,50 @@
 rules:
   - name: network-profiling-slow-trace
     layer: K8S_SERVICE
+    outputType: SampledTrace
     dsl: |
       filter {
         json{
         }
         extractor{
           if (tag("LOG_KIND") == "NET_PROFILING_SAMPLED_TRACE") {
-            sampledTrace {
-              latency parsed.latency as Long
-              uri ((parsed.trace_provider as String) + "-" + (parsed.uri as String))
-              reason parsed.reason as String
+            latency parsed.latency as Long
+            uri ((parsed.trace_provider as String) + "-" + (parsed.uri as String))
+            reason parsed.reason as String
 
-              if (parsed.client_process.process_id as String != "") {
-               processId parsed.client_process.process_id as String
-              } else if (parsed.client_process.local as Boolean
-                  || (parsed.client_process.address as String).split(":")[0].endsWith('.1')
-                  || (parsed.client_process.address as String).split(":")[1] == "53") {
-                processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                processId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
-              }
+            if (parsed.client_process.process_id as String != "") {
+             processId parsed.client_process.process_id as String
+            } else if (parsed.client_process.local as Boolean
+                || (parsed.client_process.address as String).split(":")[0].endsWith('.1')
+                || (parsed.client_process.address as String).split(":")[1] == "53") {
+              processId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              processId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
+            }
 
-              if (parsed.server_process.process_id as String != "") {
-                destProcessId parsed.server_process.process_id as String
-              } else if (parsed.server_process.local as Boolean
-                  || (parsed.server_process.address as String).split(":")[0].endsWith('.1')
-                  || (parsed.server_process.address as String).split(":")[1] == "53") {
-                destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
-              } else {
-                destProcessId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
-              }
+            if (parsed.server_process.process_id as String != "") {
+              destProcessId parsed.server_process.process_id as String
+            } else if (parsed.server_process.local as Boolean
+                || (parsed.server_process.address as String).split(":")[0].endsWith('.1')
+                || (parsed.server_process.address as String).split(":")[1] == "53") {
+              destProcessId ProcessRegistry.generateVirtualLocalProcess(parsed.service as String, parsed.serviceInstance as String) as String
+            } else {
+              destProcessId ProcessRegistry.generateVirtualProcess(parsed.service as String, parsed.serviceInstance as String, 'UNKNOWN_REMOTE') as String
+            }
 
-              detectPoint parsed.detect_point as String
+            detectPoint parsed.detect_point as String
 
-              if (parsed.component as String == "http" && parsed.ssl as Boolean) {
-                componentId 129
-              } else if (parsed.component as String == "http") {
-                componentId 49
-              } else if (parsed.ssl as Boolean) {
-                componentId 130
-              } else {
-                componentId 110
-              }
+            if (parsed.component as String == "http" && parsed.ssl as Boolean) {
+              componentId 129
+            } else if (parsed.component as String == "http") {
+              componentId 49
+            } else if (parsed.ssl as Boolean) {
+              componentId 130
+            } else {
+              componentId 110
             }
           }
         }
+        sink {
+        }
       }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.data.yaml
index ffdae9b64b..ba1b934435 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.data.yaml
@@ -17,10 +17,15 @@
 
 pgsql-slowsql:
   body-type: json
-  body: '{"layer":"POSTGRESQL","service":"pg-svc","time":"1609459200000","id":"slow-pg-1","statement":"SELECT 1","query_time":300}'
+  body: '{"layer":"POSTGRESQL","service":"pgsql::test-db","time":"1609459200000","id":"slow-002","statement":"SELECT * FROM orders WHERE created_at > now() - interval 1 day","query_time":5000}'
   tags:
     LOG_KIND: SLOW_SQL
   expect:
-    service: pg-svc
+    save: true
+    abort: false
     layer: POSTGRESQL
-    timestamp: 1609459200000
+    service: pgsql::test-db
+    timestamp: "1609459200000"
+    outputField.id: slow-002
+    outputField.statement: "SELECT * FROM orders WHERE created_at > now() - interval 1 day"
+    outputField.latency: 5000
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.yaml
index be3aeb291d..ed84072251 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/pgsql-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: pgsql-slowsql
     layer: POSTGRESQL
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.data.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.data.yaml
index 95dc05b494..c6fb362aa9 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.data.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.data.yaml
@@ -17,10 +17,15 @@
 
 redis-slowsql:
   body-type: json
-  body: '{"layer":"REDIS","service":"redis-svc","time":"1609459200000","id":"slow-redis-1","statement":"GET key","query_time":200}'
+  body: '{"layer":"REDIS","service":"redis::test-cache","time":"1609459200000","id":"slow-003","statement":"KEYS *","query_time":2000}'
   tags:
     LOG_KIND: SLOW_SQL
   expect:
-    service: redis-svc
+    save: true
+    abort: false
     layer: REDIS
-    timestamp: 1609459200000
+    service: redis::test-cache
+    timestamp: "1609459200000"
+    outputField.id: slow-003
+    outputField.statement: "KEYS *"
+    outputField.latency: 2000
diff --git a/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.yaml b/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.yaml
index b4873ad88c..c669e515a9 100644
--- a/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.yaml
+++ b/test/script-cases/scripts/lal/test-lal/oap-cases/redis-slowsql.yaml
@@ -16,6 +16,7 @@
 rules:
   - name: redis-slowsql
     layer: REDIS
+    outputType: SlowSQL
     dsl: |
       filter {
         json{
@@ -25,11 +26,11 @@ rules:
           service parsed.service as String
           timestamp parsed.time as String
           if (tag("LOG_KIND") == "SLOW_SQL") {
-             slowSql {
-               id parsed.id as String
-               statement parsed.statement as String
-               latency parsed.query_time as Long
-             }
+            id parsed.id as String
+            statement parsed.statement as String
+            latency parsed.query_time as Long
           }
         }
+        sink {
+        }
       }