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 {
+ }
}