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/13 01:29:23 UTC
(skywalking) branch master updated: LAL: introduce LogMetadata to decouple metadata from LogData in pipeline entry (#13742)
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new 2709c94ab3 LAL: introduce LogMetadata to decouple metadata from LogData in pipeline entry (#13742)
2709c94ab3 is described below
commit 2709c94ab3cc8997fd45c2ff38f3059cb0120074
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Fri Mar 13 09:29:18 2026 +0800
LAL: introduce LogMetadata to decouple metadata from LogData in pipeline entry (#13742)
- Introduce LogMetadata POJO as uniform metadata carrier, replacing LogData for metadata fields (service, layer, timestamp, trace context)
- ExecutionContext uses metadata() + input() instead of log() + extraLog(); removes LogData type awareness from runtime
- Unify LogSinkListener.parse() to single 3-parameter signature (LogMetadata, Object, ExecutionContext)
- Envoy ALS receivers pass typed proto directly as input — no longer build fake LogData
- Separate test tool (LogTestQuery) from production sink via dryRun flag
- LogMetadata.TraceContext defaults to EMPTY (matching protobuf's never-null semantics), preventing NPE on untraced logs
- LogMetadataUtils.fromLogData(Builder) extracts fields directly without build()
- tag("KEY") codegen is now input-type-aware: only intercepts for LogData.Builder input with single string literal arg; other cases fall through to normal type-oriented resolution
---
.../listener/DatabaseSlowStatementBuilder.java | 18 +-
.../trace/parser/listener/SampledTraceBuilder.java | 22 +-
oap-server/analyzer/log-analyzer/CLAUDE.md | 27 +-
.../log/analyzer/v2/compiler/LALBlockCodegen.java | 1123 +++-----------------
.../analyzer/v2/compiler/LALClassGenerator.java | 17 +-
.../log/analyzer/v2/compiler/LALCodegenHelper.java | 36 +-
.../log/analyzer/v2/compiler/LALDefCodegen.java | 360 +++++++
.../log/analyzer/v2/compiler/LALValueCodegen.java | 903 ++++++++++++++++
.../analyzer/v2/compiler/rt/LalRuntimeHelper.java | 15 +-
.../skywalking/oap/log/analyzer/v2/dsl/DSL.java | 10 +-
.../oap/log/analyzer/v2/dsl/ExecutionContext.java | 66 +-
.../analyzer/v2/dsl/spec/filter/FilterSpec.java | 67 +-
.../v2/dsl/spec/parser/TextParserSpec.java | 2 +-
.../v2/provider/log/ILogAnalyzerService.java | 14 +-
.../log/analyzer/v2/provider/log/LogAnalyzer.java | 29 +-
.../v2/provider/log/LogAnalyzerServiceImpl.java | 7 +-
.../provider/log/listener/LogAnalysisListener.java | 12 +-
.../provider/log/listener/LogFilterListener.java | 10 +-
.../v2/provider/log/listener/LogSinkListener.java | 16 +-
.../provider/log/listener/RecordSinkListener.java | 20 +-
.../provider/log/listener/TrafficSinkListener.java | 25 +-
.../log/analyzer/v2/spi/LALSourceTypeProvider.java | 8 +-
.../compiler/LALClassGeneratorExtractorTest.java | 14 +-
.../v2/compiler/LALExpressionExecutionTest.java | 26 +-
.../oap/server/core/source/LALOutputBuilder.java | 11 +-
.../oap/server/core/source/LogBuilder.java | 101 +-
.../oap/server/core/source/LogMetadata.java | 52 +
.../oap/server/core/source/LogMetadataUtils.java | 71 ++
.../agent/kafka/provider/handler/LogHandler.java | 6 +-
.../oap/query/graphql/resolver/LogTestQuery.java | 18 +-
.../envoy/persistence/EnvoyAccessLogBuilder.java | 18 +-
.../envoy/persistence/LogsPersistence.java | 30 +-
.../envoy/persistence/TCPLogsPersistence.java | 30 +-
.../persistence/EnvoyAccessLogBuilderTest.java | 37 +-
.../envoy/persistence/EnvoyAlsLalTest.java | 17 +-
.../otel/otlp/OpenTelemetryLogHandler.java | 21 +-
.../handler/grpc/LogReportServiceGrpcHandler.java | 4 +-
.../handler/rest/LogReportServiceHTTPHandler.java | 7 +-
.../oap/server/checker/lal/LalBenchmark.java | 12 +-
.../oap/server/checker/lal/LalComparisonTest.java | 44 +-
40 files changed, 1956 insertions(+), 1370 deletions(-)
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 db33d33e2f..1cd2c92f96 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
@@ -18,11 +18,9 @@
package org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener;
-import java.util.Optional;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
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.IDManager;
@@ -31,6 +29,7 @@ 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.LogMetadata;
import org.apache.skywalking.oap.server.core.source.SourceReceiver;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
@@ -70,7 +69,7 @@ public class DatabaseSlowStatementBuilder implements LALOutputBuilder {
}
/**
- * Constructor for v1 (Groovy) path which doesn't use {@link #init}.
+ * Constructor for v1 (Groovy) path where NamingControl is resolved eagerly.
*/
public DatabaseSlowStatementBuilder(final NamingControl namingControl) {
NAMING_CONTROL = namingControl;
@@ -83,7 +82,7 @@ public class DatabaseSlowStatementBuilder implements LALOutputBuilder {
}
@Override
- public void init(final LogData logData, final Optional<Object> extraLog,
+ public void init(final LogMetadata metadata, final Object input,
final ModuleManager moduleManager) {
if (!INITIALIZED) {
NAMING_CONTROL = moduleManager.find(CoreModule.NAME)
@@ -93,17 +92,20 @@ public class DatabaseSlowStatementBuilder implements LALOutputBuilder {
}
// Only populate fields not already set by the LAL extractor.
if (this.serviceName == null) {
- this.serviceName = logData.getService();
+ this.serviceName = metadata.getService();
}
if (this.traceId == null) {
- this.traceId = logData.getTraceContext().getTraceId();
+ final LogMetadata.TraceContext tc = metadata.getTraceContext();
+ if (tc != null) {
+ this.traceId = tc.getTraceId();
+ }
}
if (this.timestamp == 0) {
- this.timestamp = logData.getTimestamp();
+ this.timestamp = metadata.getTimestamp();
}
if (this.timeBucket == 0) {
this.timeBucket = TimeBucket.getTimeBucket(
- this.timestamp > 0 ? this.timestamp : logData.getTimestamp(),
+ this.timestamp > 0 ? this.timestamp : metadata.getTimestamp(),
DownSampling.Second);
}
}
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 1605a7ca10..5c7068cb74 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
@@ -20,11 +20,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 java.util.Optional;
import lombok.Getter;
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;
@@ -40,6 +38,7 @@ 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.LogMetadata;
import org.apache.skywalking.oap.server.core.source.ProcessRelation;
import org.apache.skywalking.oap.server.core.source.SourceReceiver;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
@@ -94,7 +93,7 @@ public class SampledTraceBuilder implements LALOutputBuilder {
}
/**
- * Constructor for v1 (Groovy) path which doesn't use {@link #init}.
+ * Constructor for v1 (Groovy) path where NamingControl is resolved eagerly.
*/
public SampledTraceBuilder(final NamingControl namingControl) {
NAMING_CONTROL = namingControl;
@@ -107,7 +106,7 @@ public class SampledTraceBuilder implements LALOutputBuilder {
}
@Override
- public void init(final LogData logData, final Optional<Object> extraLog,
+ public void init(final LogMetadata metadata, final Object input,
final ModuleManager moduleManager) {
if (!INITIALIZED) {
NAMING_CONTROL = moduleManager.find(CoreModule.NAME)
@@ -117,19 +116,22 @@ public class SampledTraceBuilder implements LALOutputBuilder {
}
// Only populate fields not already set by the LAL extractor.
if (this.traceId == null) {
- this.traceId = logData.getTraceContext().getTraceId();
+ final LogMetadata.TraceContext tc = metadata.getTraceContext();
+ if (tc != null) {
+ this.traceId = tc.getTraceId();
+ }
}
if (this.serviceName == null) {
- this.serviceName = logData.getService();
+ this.serviceName = metadata.getService();
}
if (this.serviceInstanceName == null) {
- this.serviceInstanceName = logData.getServiceInstance();
+ this.serviceInstanceName = metadata.getServiceInstance();
}
- if (this.layer == null && !logData.getLayer().isEmpty()) {
- this.layer = logData.getLayer();
+ if (this.layer == null && metadata.getLayer() != null && !metadata.getLayer().isEmpty()) {
+ this.layer = metadata.getLayer();
}
if (this.timestamp == 0) {
- this.timestamp = logData.getTimestamp();
+ this.timestamp = metadata.getTimestamp();
}
}
diff --git a/oap-server/analyzer/log-analyzer/CLAUDE.md b/oap-server/analyzer/log-analyzer/CLAUDE.md
index 3a0f6b0d13..2dbc40b904 100644
--- a/oap-server/analyzer/log-analyzer/CLAUDE.md
+++ b/oap-server/analyzer/log-analyzer/CLAUDE.md
@@ -41,7 +41,7 @@ oap-server/analyzer/log-analyzer/
src/main/java/.../dsl/
LalExpression.java — Functional interface: execute(FilterSpec, ExecutionContext)
- ExecutionContext.java — Per-log execution state (log, parsed, flags)
+ ExecutionContext.java — Per-log execution state (metadata, input, parsed, flags)
DSL.java — Wraps compiled expression + FilterSpec
spec/filter/FilterSpec.java — Top-level filter spec (all methods take ctx explicitly)
spec/extractor/MetricExtractor.java — Handles LAL metrics {} blocks (prepare/submit samples to MAL)
@@ -182,10 +182,11 @@ 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 + 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()` |
+| NONE + inputType | `parsed.response.code` | `((InputType) h.ctx().input()).getResponse().getCode()` |
+| NONE + no inputType | `parsed.service` | `h.ctx().metadata().getService()` (LogMetadata fallback) |
+| log fields (metadata) | `log.service` | `h.ctx().metadata().getService()` |
+| log fields (LogData) | `log.body` | `((LogData.Builder) h.ctx().input()).getBody()` |
+| log trace | `log.traceContext.traceId` | `h.ctx().metadata().getTraceContext().getTraceId()` |
| tags | `tag("KEY")` | `h.tagValue("KEY")` |
### inputType and LALSourceTypeProvider SPI
@@ -195,7 +196,7 @@ For LAL rules with no DSL parser (`json{}`/`yaml{}`/`text{}`), the compiler need
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.
+4. **`LogMetadata` fallback** — if none of the above, `parsed.*` generates getter chains on `LogMetadata` with compile-time reflection validation. Fields not found on `LogMetadata` 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`.
@@ -238,7 +239,7 @@ If no setter is found, compilation fails with an `IllegalArgumentException` at b
**Runtime dispatch**: `RecordSinkListener.parse()` reads the output object from
`ExecutionContext.output()` (already populated by generated code), calls
-`init(logData, extraLog, moduleManager)` to populate standard fields and resolve
+`init(metadata, input, moduleManager)` to populate standard fields and resolve
services (e.g., `NamingControl`, `ConfigService`) from `ModuleManager`, then `build()`
dispatches via `complete(sourceReceiver)`. Each builder caches resolved services in
static fields so `ModuleManager` lookups only happen once.
@@ -276,7 +277,7 @@ Instance-based helper created at the start of `execute()`, holds the `ExecutionC
- `mapVal(key)`, `mapVal(k1, k2)`, `mapVal(k1, k2, k3)` — JSON/YAML map access
- `group(name)` — text regexp named group
- `tagValue(key)` — log tag lookup
-- `ctx()` — access to ExecutionContext (for `h.ctx().log()` proto getters)
+- `ctx()` — access to ExecutionContext (for `h.ctx().metadata()` and `h.ctx().log()` getters)
**Type conversion:** `toStr()`, `toLong()`, `toInt()`, `toBool()`
@@ -284,11 +285,11 @@ Instance-based helper created at the start of `execute()`, holds the `ExecutionC
**Safe navigation:** `toString()`, `trim()`
-## JSON/YAML LogData Field Population
+## JSON/YAML Metadata Field Population
-When `json{}` or `yaml{}` parses the log body, `FilterSpec` also adds LogData proto fields
+When `json{}` or `yaml{}` parses the log body, `FilterSpec` also adds `LogMetadata` fields
(`service`, `serviceInstance`, `endpoint`, `layer`, `timestamp`) to the parsed map via
-`putIfAbsent`. Body-parsed values take priority; proto fields serve as fallback. This matches
+`putIfAbsent`. Body-parsed values take priority; metadata fields serve as fallback. This matches
v1 Groovy `Binding.Parsed.getAt(key)` behavior where `parsed.service` falls back to
`LogData.getService()` when the JSON body doesn't contain a `service` key.
@@ -342,7 +343,7 @@ rule-name:
### Principles
-1. **`body-type` determines parsing**: `json` → `json{}` block, `text` → `text{}` block, `none` → proto extraLog or raw LogData access.
+1. **`body-type` determines parsing**: `json` → `json{}` block, `text` → `text{}` block, `none` → typed proto input or LogMetadata fallback.
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. **Tag assertions**: `tag.KEY` in expect asserts extracted tag values (e.g., `tag.status.code: "500"`).
@@ -380,7 +381,7 @@ Test utilities from `org.apache.skywalking.oap.server.testing.dsl`:
- `LalRuleLoader.loadAllRules(Path)` — loads all LAL rules with companion `.input.data` or `.data.yaml`
- `LalLogDataBuilder.buildLogData(Map)` — builds `LogData.Builder` from test input map
- `LalLogDataBuilder.buildSyntheticLogData(String)` — builds synthetic LogData from DSL string
-- `LalLogDataBuilder.buildExtraLog(Map)` — builds proto Message for extraLog from input map
+- `LalLogDataBuilder.buildExtraLog(Map)` — builds proto Message for typed input from input map
- `DslRuleLoader.findScriptsDir(String...)` — resolves scripts directory from candidates
- `DslRuleLoader.findRuleLine(String[], String, int)` — finds 1-based line number of rule in YAML
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 629edf7f4e..6862c15126 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
@@ -23,13 +23,46 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
/**
- * Static code-generation methods for LAL extractor, sink, condition, and
- * value-access blocks. Extracted from {@link LALClassGenerator} for
- * readability; all methods are stateless and take a
- * {@link LALClassGenerator.GenCtx} parameter for shared state.
+ * Code generation for LAL block-level structures: {@code extractor},
+ * {@code sink}, {@code sampler}, {@code metrics}, tag assignments, and
+ * output-field assignments.
+ *
+ * <p>Given a LAL script:
+ * <pre>{@code
+ * filter {
+ * json {}
+ * extractor { // → generateExtractorMethod
+ * service parsed.service as String // → generateFieldToOutput
+ * tag 'status.code': parsed.code // → generateTagAssignment
+ * latency parsed.latency as Long // → generateOutputFieldAssignment
+ * if (tag("LOG_KIND") == "SLOW") { // → generateIfBlockInExtractor
+ * ...
+ * }
+ * metrics { // → generateMetricsInline
+ * name "log_count"
+ * value 1
+ * }
+ * }
+ * sink { // → generateSinkMethod
+ * sampler { // → generateSamplerInline
+ * rateLimit("${log.service}") { // → generateRateLimitInline
+ * rpm 6000
+ * }
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Each block becomes a private method in the generated class (e.g.
+ * {@code _extractor()}, {@code _sink()}). This class emits the method
+ * scaffolding (signature, local variable declarations, LVT entries) and
+ * walks the AST statements within each block.
+ *
+ * <p>Expression-level codegen (value access, conditions, type casts) is
+ * delegated to {@link LALValueCodegen}. {@code def} variable codegen is
+ * delegated to {@link LALDefCodegen}.
*/
final class LALBlockCodegen {
@@ -41,8 +74,6 @@ final class LALBlockCodegen {
METRIC_EXTRACTOR + "$SampleBuilder";
private static final String H =
"org.apache.skywalking.oap.log.analyzer.v2.compiler.rt.LalRuntimeHelper";
- private static final String PROCESS_REGISTRY =
- "org.apache.skywalking.oap.meter.analyzer.v2.dsl.registry.ProcessRegistry";
// Built-in function registry for def variable type inference.
// Maps DSL function name → [runtime helper method, return type].
@@ -59,6 +90,24 @@ final class LALBlockCodegen {
// ==================== Extractor method generation ====================
+ /**
+ * Generates the {@code _extractor()} private method for the LAL
+ * {@code extractor { ... }} block.
+ *
+ * <p>LAL example:
+ * <pre>{@code
+ * extractor {
+ * service parsed.service as String
+ * tag 'status.code': parsed?.response?.responseCode?.value
+ * metrics { name "log_count"; value 1 }
+ * }
+ * }</pre>
+ *
+ * <p>The generated method signature is:
+ * {@code private void _extractor(MetricExtractor _e, LalRuntimeHelper h)}.
+ * The call site in {@code execute()} guards it with
+ * {@code if (!ctx.shouldAbort())}.
+ */
static void generateExtractorMethod(final StringBuilder sb,
final LALScriptModel.ExtractorBlock block,
final LALClassGenerator.GenCtx genCtx) {
@@ -87,7 +136,7 @@ final class LALBlockCodegen {
if (genCtx.inputType != null) {
final String elTypeName = genCtx.inputType.getName();
body.append(" ").append(elTypeName).append(" _p = (")
- .append(elTypeName).append(") h.ctx().extraLog();\n");
+ .append(elTypeName).append(") h.ctx().input();\n");
lvtVars.add(new String[]{"_p",
"L" + elTypeName.replace('.', '/') + ";"});
}
@@ -128,6 +177,11 @@ final class LALBlockCodegen {
sb.append(" }\n");
}
+ /**
+ * Walks extractor statements and dispatches each to its codegen method:
+ * field assignments, tag assignments, if-blocks, metrics blocks,
+ * output-field assignments, and def statements.
+ */
static void generateExtractorBody(
final StringBuilder sb,
final List<? extends LALScriptModel.FilterStatement> stmts,
@@ -147,7 +201,7 @@ final class LALBlockCodegen {
generateOutputFieldAssignment(
sb, (LALScriptModel.OutputFieldAssignment) stmt, genCtx);
} else if (stmt instanceof LALScriptModel.DefStatement) {
- generateDefStatement(
+ LALDefCodegen.generateDefStatement(
sb, (LALScriptModel.DefStatement) stmt, genCtx);
}
}
@@ -172,6 +226,15 @@ final class LALBlockCodegen {
{"setTimestamp"},
};
+ /**
+ * Generates a standard field assignment to the output builder.
+ *
+ * <p>LAL: {@code service parsed.service as String}
+ * <br>Generated: {@code _o.setServiceName(h.toStr(h.mapVal("service")))}
+ *
+ * <p>Standard fields: service, instance, endpoint, layer, traceId,
+ * segmentId, spanId, timestamp.
+ */
private static void generateFieldToOutput(
final StringBuilder sb,
final LALScriptModel.FieldAssignment field,
@@ -198,16 +261,16 @@ final class LALBlockCodegen {
if (field.getFormatPattern() != null) {
// Format pattern provided in LAL script (e.g., timestamp ... , "yyyy/MM/dd HH:mm:ss")
sb.append("h.parseTimestamp(");
- generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
+ LALValueCodegen.generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
sb.append(", \"")
.append(LALCodegenHelper.escapeJava(field.getFormatPattern()))
.append("\")");
} else if (paramType.isEnum()) {
sb.append(paramType.getName()).append(".valueOf(");
- generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
+ LALValueCodegen.generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
sb.append(")");
} else {
- generateCastedValueAccess(sb, field.getValue(), effectiveCast, genCtx);
+ LALValueCodegen.generateCastedValueAccess(sb, field.getValue(), effectiveCast, genCtx);
}
sb.append(");\n");
}
@@ -217,7 +280,7 @@ final class LALBlockCodegen {
final LALScriptModel.IfBlock ifBlock,
final LALClassGenerator.GenCtx genCtx) {
sb.append(" if (");
- generateCondition(sb, ifBlock.getCondition(), genCtx);
+ LALValueCodegen.generateCondition(sb, ifBlock.getCondition(), genCtx);
sb.append(") {\n");
generateExtractorBody(sb, ifBlock.getThenBranch(), genCtx);
sb.append(" }\n");
@@ -230,6 +293,23 @@ final class LALBlockCodegen {
// ==================== Metrics inline ====================
+ /**
+ * Generates an inlined {@code metrics { ... }} block inside the extractor.
+ *
+ * <p>LAL example:
+ * <pre>{@code
+ * metrics {
+ * name "log_count"
+ * timestamp log.timestamp as Long
+ * labels level: parsed.level, service: parsed.service
+ * value 1
+ * }
+ * }</pre>
+ *
+ * <p>Generated code prepares a {@code SampleBuilder} via
+ * {@code _e.prepareMetrics()}, sets name/timestamp/labels/value, then
+ * calls {@code _e.submitMetrics()}.
+ */
static void generateMetricsInline(
final StringBuilder sb,
final LALScriptModel.MetricsBlock block,
@@ -242,7 +322,7 @@ final class LALBlockCodegen {
}
if (block.getTimestampValue() != null) {
sb.append(" _metrics.timestamp(");
- generateCastedValueAccess(sb, block.getTimestampValue(),
+ LALValueCodegen.generateCastedValueAccess(sb, block.getTimestampValue(),
block.getTimestampCast(), genCtx);
sb.append(");\n");
}
@@ -252,7 +332,7 @@ final class LALBlockCodegen {
: block.getLabels().entrySet()) {
sb.append(" _labels.put(\"")
.append(LALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
- generateCastedValueAccess(sb, entry.getValue().getValue(),
+ LALValueCodegen.generateCastedValueAccess(sb, entry.getValue().getValue(),
entry.getValue().getCastType(), genCtx);
sb.append(");\n");
}
@@ -262,18 +342,18 @@ final class LALBlockCodegen {
sb.append(" _metrics.value(");
if ("Long".equals(block.getValueCast())) {
sb.append("(double) h.toLong(");
- generateValueAccess(sb, block.getValue(), genCtx);
+ LALValueCodegen.generateValueAccess(sb, block.getValue(), genCtx);
sb.append(")");
} else if ("Integer".equals(block.getValueCast())) {
sb.append("(double) h.toInt(");
- generateValueAccess(sb, block.getValue(), genCtx);
+ LALValueCodegen.generateValueAccess(sb, block.getValue(), genCtx);
sb.append(")");
} else {
if (block.getValue().isNumberLiteral()) {
sb.append("(double) ").append(block.getValue().getSegments().get(0));
} else {
sb.append("((Number) ");
- generateValueAccess(sb, block.getValue(), genCtx);
+ LALValueCodegen.generateValueAccess(sb, block.getValue(), genCtx);
sb.append(").doubleValue()");
}
}
@@ -285,6 +365,12 @@ final class LALBlockCodegen {
// ==================== Tag assignment ====================
+ /**
+ * Generates tag assignment statements in the extractor.
+ *
+ * <p>LAL: {@code tag 'status.code': parsed?.response?.responseCode?.value}
+ * <br>Generated: {@code _o.addTag("status.code", h.toStr(...))}
+ */
static void generateTagAssignment(final StringBuilder sb,
final LALScriptModel.TagAssignment tag,
final LALClassGenerator.GenCtx genCtx) {
@@ -292,7 +378,7 @@ final class LALBlockCodegen {
: tag.getTags().entrySet()) {
sb.append(" _o.addTag(\"")
.append(LALCodegenHelper.escapeJava(entry.getKey())).append("\", ");
- generateStringValueAccess(sb, entry.getValue().getValue(),
+ LALValueCodegen.generateStringValueAccess(sb, entry.getValue().getValue(),
entry.getValue().getCastType(), genCtx);
sb.append(");\n");
}
@@ -348,6 +434,17 @@ final class LALBlockCodegen {
return false;
}
+ /**
+ * Generates an output-field assignment for custom output types.
+ *
+ * <p>LAL: {@code latency parsed.latency as Long}
+ * (where {@code latency} is not a standard field but a field on the
+ * {@code outputType} class, e.g. {@code SampledTrace.setLatency(long)})
+ * <br>Generated: {@code _o.setLatency(h.toLong(h.mapVal("latency")))}
+ *
+ * <p>The setter is validated at compile time via reflection on the
+ * output type. If no matching setter exists, compilation fails.
+ */
static void generateOutputFieldAssignment(
final StringBuilder sb,
final LALScriptModel.OutputFieldAssignment field,
@@ -380,10 +477,10 @@ final class LALBlockCodegen {
// Auto-convert String to enum: EnumType.valueOf(stringValue.toUpperCase())
// toUpperCase() handles case-insensitive matching (e.g., "slow" → "SLOW")
sb.append(paramType.getName()).append(".valueOf(((String) ");
- generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
+ LALValueCodegen.generateCastedValueAccess(sb, field.getValue(), "String", genCtx);
sb.append(").toUpperCase())");
} else {
- generateCastedValueAccess(sb, field.getValue(), effectiveCast, genCtx);
+ LALValueCodegen.generateCastedValueAccess(sb, field.getValue(), effectiveCast, genCtx);
}
sb.append(");\n");
}
@@ -432,6 +529,26 @@ final class LALBlockCodegen {
// ==================== Sink method generation ====================
+ /**
+ * Generates the {@code _sink()} private method for the LAL
+ * {@code sink { ... }} block.
+ *
+ * <p>LAL example:
+ * <pre>{@code
+ * sink {
+ * sampler {
+ * rateLimit("${log.service}:${parsed?.response?.responseCode}") {
+ * rpm 6000
+ * }
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>The generated method signature is:
+ * {@code private void _sink(FilterSpec _f, LalRuntimeHelper h)}.
+ * Sink statements include {@code enforcer()}, {@code dropper()},
+ * {@code sampler { ... }}, and if-blocks.
+ */
static void generateSinkMethod(final StringBuilder sb,
final LALScriptModel.SinkBlock sink,
final LALClassGenerator.GenCtx genCtx) {
@@ -460,7 +577,7 @@ final class LALBlockCodegen {
if (genCtx.inputType != null) {
final String elTypeName = genCtx.inputType.getName();
body.append(" ").append(elTypeName).append(" _p = (")
- .append(elTypeName).append(") h.ctx().extraLog();\n");
+ .append(elTypeName).append(") h.ctx().input();\n");
lvtVars.add(new String[]{"_p",
"L" + elTypeName.replace('.', '/') + ";"});
}
@@ -503,7 +620,7 @@ final class LALBlockCodegen {
final LALScriptModel.IfBlock ifBlock,
final LALClassGenerator.GenCtx genCtx) {
sb.append(" if (");
- generateCondition(sb, ifBlock.getCondition(), genCtx);
+ LALValueCodegen.generateCondition(sb, ifBlock.getCondition(), genCtx);
sb.append(") {\n");
generateSinkBody(sb, ifBlock.getThenBranch(), genCtx);
sb.append(" }\n");
@@ -516,6 +633,9 @@ final class LALBlockCodegen {
// ==================== Sampler/RateLimit inline ====================
+ /**
+ * Generates a {@code sampler { ... }} block inside the sink.
+ */
static void generateSamplerInline(
final StringBuilder sb,
final LALScriptModel.SamplerBlock block,
@@ -542,7 +662,7 @@ final class LALBlockCodegen {
final LALScriptModel.IfBlock ifBlock,
final LALClassGenerator.GenCtx genCtx) {
sb.append(" if (");
- generateCondition(sb, ifBlock.getCondition(), genCtx);
+ LALValueCodegen.generateCondition(sb, ifBlock.getCondition(), genCtx);
sb.append(") {\n");
generateSamplerContentsFromFilterStmts(sb, ifBlock.getThenBranch(), genCtx);
sb.append(" }\n");
@@ -567,6 +687,18 @@ final class LALBlockCodegen {
}
}
+ /**
+ * Generates a {@code rateLimit("id") { rpm N }} block.
+ *
+ * <p>The ID can be a plain string or an interpolated string:
+ * <pre>{@code
+ * rateLimit("${log.service}:${parsed?.response?.responseCode}") {
+ * rpm 6000
+ * }
+ * }</pre>
+ *
+ * <p>Generated: {@code _f.sampler().rateLimit(h.ctx(), "" + ... , 6000)}
+ */
static void generateRateLimitInline(
final StringBuilder sb,
final LALScriptModel.RateLimitBlock block,
@@ -581,7 +713,7 @@ final class LALBlockCodegen {
.append("\"");
} else {
sb.append("String.valueOf(");
- generateValueAccess(sb, part.getExpression(), genCtx);
+ LALValueCodegen.generateValueAccess(sb, part.getExpression(), genCtx);
sb.append(")");
}
}
@@ -590,945 +722,4 @@ final class LALBlockCodegen {
}
sb.append(", ").append(block.getRpm()).append(");\n");
}
-
- // ==================== Conditions ====================
-
- static void generateCondition(final StringBuilder sb,
- final LALScriptModel.Condition cond,
- final LALClassGenerator.GenCtx genCtx) {
- if (cond instanceof LALScriptModel.ComparisonCondition) {
- final LALScriptModel.ComparisonCondition cc =
- (LALScriptModel.ComparisonCondition) cond;
- switch (cc.getOp()) {
- case EQ:
- sb.append("java.util.Objects.equals(");
- generateValueAccessObj(sb, cc.getLeft(), cc.getLeftCast(), genCtx);
- sb.append(", ");
- generateConditionValue(sb, cc.getRight(), genCtx);
- sb.append(")");
- break;
- case NEQ:
- sb.append("!java.util.Objects.equals(");
- generateValueAccessObj(sb, cc.getLeft(), cc.getLeftCast(), genCtx);
- sb.append(", ");
- generateConditionValue(sb, cc.getRight(), genCtx);
- sb.append(")");
- break;
- case GT:
- generateNumericComparison(sb, cc, " > ", genCtx);
- break;
- case LT:
- generateNumericComparison(sb, cc, " < ", genCtx);
- break;
- case GTE:
- generateNumericComparison(sb, cc, " >= ", genCtx);
- break;
- case LTE:
- generateNumericComparison(sb, cc, " <= ", genCtx);
- break;
- default:
- break;
- }
- } else if (cond instanceof LALScriptModel.LogicalCondition) {
- final LALScriptModel.LogicalCondition lc =
- (LALScriptModel.LogicalCondition) cond;
- sb.append("(");
- generateCondition(sb, lc.getLeft(), genCtx);
- sb.append(lc.getOp() == LALScriptModel.LogicalOp.AND
- ? " && " : " || ");
- generateCondition(sb, lc.getRight(), genCtx);
- sb.append(")");
- } else if (cond instanceof LALScriptModel.NotCondition) {
- sb.append("!(");
- generateCondition(sb,
- ((LALScriptModel.NotCondition) cond).getInner(), genCtx);
- sb.append(")");
- } else if (cond instanceof LALScriptModel.ExprCondition) {
- final String ct = ((LALScriptModel.ExprCondition) cond).getCastType();
- final String method = "Boolean".equals(ct) || "boolean".equals(ct)
- ? ".isTrue(" : ".isNotEmpty(";
- sb.append("h").append(method);
- generateValueAccessObj(sb,
- ((LALScriptModel.ExprCondition) cond).getExpr(),
- ct, genCtx);
- sb.append(")");
- }
- }
-
- static void generateNumericComparison(
- final StringBuilder sb,
- final LALScriptModel.ComparisonCondition cc,
- final String op,
- final LALClassGenerator.GenCtx genCtx) {
- // Generate left side into buffer to inspect resolved type
- final StringBuilder leftBuf = new StringBuilder();
- generateValueAccessObj(leftBuf, cc.getLeft(), null, genCtx);
-
- final boolean primitiveNumeric = genCtx.lastResolvedType != null
- && (genCtx.lastResolvedType == int.class
- || genCtx.lastResolvedType == long.class);
-
- if (primitiveNumeric && genCtx.lastRawChain != null) {
- // Direct primitive comparison — no boxing, no h.toLong()
- if (genCtx.lastNullChecks != null) {
- sb.append("(").append(genCtx.lastNullChecks).append(" ? false : ")
- .append(genCtx.lastRawChain).append(op);
- generateConditionValueNumeric(sb, cc.getRight(), genCtx);
- sb.append(")");
- } else {
- sb.append(genCtx.lastRawChain).append(op);
- generateConditionValueNumeric(sb, cc.getRight(), genCtx);
- }
- } else {
- // Fallback: h.toLong() conversion
- sb.append("h.toLong(").append(leftBuf).append(")").append(op);
- generateConditionValueNumeric(sb, cc.getRight(), genCtx);
- }
- }
-
- static void generateConditionValue(final StringBuilder sb,
- final LALScriptModel.ConditionValue cv,
- final LALClassGenerator.GenCtx genCtx) {
- if (cv instanceof LALScriptModel.StringConditionValue) {
- sb.append('"')
- .append(LALCodegenHelper.escapeJava(
- ((LALScriptModel.StringConditionValue) cv).getValue()))
- .append('"');
- } else if (cv instanceof LALScriptModel.NumberConditionValue) {
- final double val =
- ((LALScriptModel.NumberConditionValue) cv).getValue();
- sb.append("Long.valueOf(").append((long) val).append("L)");
- } else if (cv instanceof LALScriptModel.BoolConditionValue) {
- sb.append("Boolean.valueOf(")
- .append(((LALScriptModel.BoolConditionValue) cv).isValue())
- .append(")");
- } else if (cv instanceof LALScriptModel.NullConditionValue) {
- sb.append("null");
- } else if (cv instanceof LALScriptModel.ValueAccessConditionValue) {
- generateValueAccessObj(sb,
- ((LALScriptModel.ValueAccessConditionValue) cv).getValue(),
- null, genCtx);
- }
- }
-
- static void generateConditionValueNumeric(
- final StringBuilder sb,
- final LALScriptModel.ConditionValue cv,
- final LALClassGenerator.GenCtx genCtx) {
- if (cv instanceof LALScriptModel.NumberConditionValue) {
- sb.append((long) ((LALScriptModel.NumberConditionValue) cv)
- .getValue()).append("L");
- } else if (cv instanceof LALScriptModel.ValueAccessConditionValue) {
- sb.append("h.toLong(");
- generateValueAccessObj(sb,
- ((LALScriptModel.ValueAccessConditionValue) cv).getValue(),
- null, genCtx);
- sb.append(")");
- } else {
- sb.append("0L");
- }
- }
-
- // ==================== Value access ====================
-
- static void generateCastedValueAccess(final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final String castType,
- final LALClassGenerator.GenCtx genCtx) {
- if ("String".equals(castType)) {
- sb.append("h.toStr(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else if ("Long".equals(castType)) {
- sb.append("h.toLong(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else if ("Integer".equals(castType)) {
- sb.append("h.toInt(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else if ("Boolean".equals(castType)) {
- sb.append("h.toBool(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else {
- generateValueAccess(sb, value, genCtx);
- }
- }
-
- static void generateStringValueAccess(final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final String castType,
- final LALClassGenerator.GenCtx genCtx) {
- if (castType == null || "String".equals(castType)) {
- sb.append("h.toStr(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else if ("Long".equals(castType)) {
- sb.append("String.valueOf(h.toLong(");
- generateValueAccess(sb, value, genCtx);
- sb.append("))");
- } else if ("Integer".equals(castType)) {
- sb.append("String.valueOf(h.toInt(");
- generateValueAccess(sb, value, genCtx);
- sb.append("))");
- } else if ("Boolean".equals(castType)) {
- sb.append("String.valueOf(h.toBool(");
- generateValueAccess(sb, value, genCtx);
- sb.append("))");
- } else {
- sb.append("h.toStr(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- }
- }
-
- static void generateValueAccessObj(final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final String castType,
- final LALClassGenerator.GenCtx genCtx) {
- if ("String".equals(castType)) {
- sb.append("h.toStr(");
- generateValueAccess(sb, value, genCtx);
- sb.append(")");
- } else {
- generateValueAccess(sb, value, genCtx);
- }
- }
-
- static void generateValueAccess(final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final LALClassGenerator.GenCtx genCtx) {
- genCtx.clearExtraLogResult();
-
- // Handle string concatenation (term1 + term2 + ...)
- if (!value.getConcatParts().isEmpty()) {
- sb.append("(\"\" + ");
- for (int i = 0; i < value.getConcatParts().size(); i++) {
- if (i > 0) {
- sb.append(" + ");
- }
- generateValueAccess(sb, value.getConcatParts().get(i), genCtx);
- }
- sb.append(")");
- return;
- }
-
- // Handle parenthesized expression: (innerExpr as Type).chain...
- if (value.getParenInner() != null) {
- generateParenAccess(sb, value, genCtx);
- return;
- }
-
- // Handle function call primaries (e.g., tag("LOG_KIND"))
- if (value.getFunctionCallName() != null) {
- if ("tag".equals(value.getFunctionCallName())
- && !value.getFunctionCallArgs().isEmpty()) {
- sb.append("h.tagValue(\"");
- final String key = value.getFunctionCallArgs().get(0)
- .getValue().getSegments().get(0);
- sb.append(LALCodegenHelper.escapeJava(key)).append("\")");
- } else {
- sb.append("null");
- }
- return;
- }
-
- // Handle string/number literals
- if (value.isStringLiteral() && value.getChain().isEmpty()) {
- sb.append("\"").append(LALCodegenHelper.escapeJava(value.getSegments().get(0)))
- .append("\"");
- return;
- }
- if (value.isNumberLiteral() && value.getChain().isEmpty()) {
- final String num = value.getSegments().get(0);
- if (num.contains(".")) {
- sb.append("Double.valueOf(").append(num).append(")");
- } else {
- sb.append("Integer.valueOf(").append(num).append(")");
- }
- return;
- }
-
- // Handle ProcessRegistry static calls
- if (value.isProcessRegistryRef()) {
- generateProcessRegistryCall(sb, value, genCtx);
- return;
- }
-
- final List<LALScriptModel.ValueAccessSegment> chain = value.getChain();
-
- // Handle log.X.Y direct proto getter chains
- if (value.isLogRef()) {
- generateLogAccess(sb, chain);
- return;
- }
-
- // Handle parsed.X.Y with compile-time type analysis
- if (value.isParsedRef()) {
- generateParsedAccess(sb, chain, genCtx);
- return;
- }
-
- // Check for def variable reference
- if (!value.getSegments().isEmpty()) {
- final String primaryName = value.getSegments().get(0);
- final LALClassGenerator.LocalVarInfo localVar =
- genCtx.localVars.get(primaryName);
- if (localVar != null) {
- generateDefVarChain(sb, localVar, chain, genCtx);
- return;
- }
- }
-
- // Fallback for unknown primary
- if (chain.isEmpty()) {
- sb.append("null");
- return;
- }
- // Treat as parsed ref
- generateParsedAccess(sb, chain, genCtx);
- }
-
- // ==================== Parenthesized expression ====================
-
- static void generateParenAccess(final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final LALClassGenerator.GenCtx genCtx) {
- // Generate the inner expression with cast
- final String castType = value.getParenCast();
- final StringBuilder inner = new StringBuilder();
- if (castType != null) {
- generateCastedValueAccess(inner, value.getParenInner(), castType, genCtx);
- } else {
- generateValueAccess(inner, value.getParenInner(), genCtx);
- }
-
- // Apply chain segments (methods, fields, index access)
- String current = inner.toString();
- for (final LALScriptModel.ValueAccessSegment seg : value.getChain()) {
- if (seg instanceof LALScriptModel.MethodSegment) {
- current = appendMethodSegment(current,
- (LALScriptModel.MethodSegment) seg, genCtx);
- } else if (seg instanceof LALScriptModel.IndexSegment) {
- current = current + "["
- + ((LALScriptModel.IndexSegment) seg).getIndex() + "]";
- } else if (seg instanceof LALScriptModel.FieldSegment) {
- final LALScriptModel.FieldSegment fs =
- (LALScriptModel.FieldSegment) seg;
- if (fs.isSafeNav()) {
- current = "(" + current + " == null ? null : "
- + current + "." + fs.getName() + ")";
- } else {
- current = current + "." + fs.getName();
- }
- }
- }
- sb.append(current);
- }
-
- // ==================== Log access (direct proto getters) ====================
-
- static void generateLogAccess(final StringBuilder sb,
- final List<LALScriptModel.ValueAccessSegment> chain) {
- if (chain.isEmpty()) {
- sb.append("h.ctx().log()");
- return;
- }
-
- String current = "h.ctx().log()";
- boolean needsBoxing = false;
- String boxType = null;
-
- for (int i = 0; i < chain.size(); i++) {
- final LALScriptModel.ValueAccessSegment seg = chain.get(i);
- if (seg instanceof LALScriptModel.FieldSegment) {
- final String name = ((LALScriptModel.FieldSegment) seg).getName();
- if (i == 0 && LALCodegenHelper.LOG_GETTERS.containsKey(name)) {
- if ("traceContext".equals(name)) {
- current = current + ".getTraceContext()";
- } else {
- current = current + "."
- + LALCodegenHelper.LOG_GETTERS.get(name) + "()";
- if (LALCodegenHelper.LONG_FIELDS.contains(name)) {
- needsBoxing = true;
- boxType = "Long";
- }
- }
- } else if (i == 1 && current.endsWith(".getTraceContext()")
- && LALCodegenHelper.TRACE_CONTEXT_GETTERS.containsKey(name)) {
- current = current + "."
- + LALCodegenHelper.TRACE_CONTEXT_GETTERS.get(name) + "()";
- if (LALCodegenHelper.INT_FIELDS.contains(name)) {
- needsBoxing = true;
- boxType = "Integer";
- }
- } else {
- throw new IllegalArgumentException(
- "Unknown log field: log." + name
- + ". Supported fields: "
- + LALCodegenHelper.LOG_GETTERS.keySet()
- + ", traceContext."
- + LALCodegenHelper.TRACE_CONTEXT_GETTERS.keySet());
- }
- } else if (seg instanceof LALScriptModel.MethodSegment) {
- current = appendMethodSegment(current,
- (LALScriptModel.MethodSegment) seg, null);
- }
- }
-
- if (needsBoxing) {
- sb.append(boxType).append(".valueOf(").append(current).append(")");
- } else {
- sb.append(current);
- }
- }
-
- // ==================== Parsed access (compile-time typed) ====================
-
- static void generateParsedAccess(
- final StringBuilder sb,
- final List<LALScriptModel.ValueAccessSegment> chain,
- final LALClassGenerator.GenCtx genCtx) {
- if (chain.isEmpty()) {
- sb.append("h.ctx().parsed()");
- return;
- }
-
- // Collect leading field segments (stop at method/index)
- final List<LALScriptModel.FieldSegment> fieldSegments = new ArrayList<>();
- int methodStart = -1;
- for (int i = 0; i < chain.size(); i++) {
- final LALScriptModel.ValueAccessSegment seg = chain.get(i);
- if (seg instanceof LALScriptModel.FieldSegment) {
- fieldSegments.add((LALScriptModel.FieldSegment) seg);
- } else {
- methodStart = i;
- break;
- }
- }
-
- final List<String> fieldKeys = new ArrayList<>();
- for (final LALScriptModel.FieldSegment fs : fieldSegments) {
- fieldKeys.add(fs.getName());
- }
-
- String current;
- switch (genCtx.parserType) {
- case JSON:
- case YAML:
- current = LALCodegenHelper.generateMapValCall(fieldKeys);
- break;
- case TEXT:
- if (!fieldKeys.isEmpty()) {
- current = "h.group(\""
- + LALCodegenHelper.escapeJava(fieldKeys.get(0)) + "\")";
- } else {
- current = "h.ctx().parsed()";
- }
- break;
- case NONE:
- if (genCtx.inputType != null) {
- current = generateExtraLogAccess(fieldSegments, genCtx.inputType,
- "_p", true, genCtx);
- } else {
- // No parser and no inputType — fall back to LogData proto
- current = generateExtraLogAccess(fieldSegments, LogData.Builder.class,
- "h.ctx().log()", false, genCtx);
- }
- break;
- default:
- current = "null";
- break;
- }
-
- // Apply remaining method/index segments
- if (methodStart >= 0) {
- for (int i = methodStart; i < chain.size(); i++) {
- final LALScriptModel.ValueAccessSegment seg = chain.get(i);
- if (seg instanceof LALScriptModel.MethodSegment) {
- current = appendMethodSegment(current,
- (LALScriptModel.MethodSegment) seg, genCtx);
- } else if (seg instanceof LALScriptModel.IndexSegment) {
- current = current + "["
- + ((LALScriptModel.IndexSegment) seg).getIndex() + "]";
- } else if (seg instanceof LALScriptModel.FieldSegment) {
- current = current + "."
- + ((LALScriptModel.FieldSegment) seg).getName();
- }
- }
- }
-
- sb.append(current);
- }
-
- static String generateExtraLogAccess(
- final List<LALScriptModel.FieldSegment> fieldSegments,
- final Class<?> rootType,
- final String rootExpr,
- final boolean rootCanBeNull,
- final LALClassGenerator.GenCtx genCtx) {
- genCtx.usedProtoAccess = true;
-
- if (fieldSegments.isEmpty()) {
- return rootExpr;
- }
-
- final String typeName = rootType.getName();
- final StringBuilder chainKey = new StringBuilder();
- String prevVar = rootExpr;
- Class<?> currentType = rootType;
- boolean prevCanBeNull = rootCanBeNull;
-
- for (int i = 0; i < fieldSegments.size(); i++) {
- final LALScriptModel.FieldSegment seg = fieldSegments.get(i);
- final String field = seg.getName();
- final String getterName = "get" + Character.toUpperCase(field.charAt(0))
- + field.substring(1);
-
- final java.lang.reflect.Method getter;
- try {
- getter = currentType.getMethod(getterName);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(
- "Cannot resolve getter " + currentType.getSimpleName()
- + "." + getterName + "() for type "
- + typeName + ". Check the field path in the LAL rule.");
- }
- final Class<?> returnType = getter.getReturnType();
-
- if (chainKey.length() > 0) {
- chainKey.append(".");
- }
- chainKey.append(field);
- final String key = chainKey.toString();
- final boolean isLast = i == fieldSegments.size() - 1;
-
- // Primitive final segment: return inline expression, no variable
- if (isLast && returnType.isPrimitive()) {
- final String rawAccess = prevVar + "." + getterName + "()";
- genCtx.lastResolvedType = returnType;
- genCtx.lastRawChain = rawAccess;
- final String boxName = LALCodegenHelper.boxTypeName(returnType);
- if (seg.isSafeNav() && prevCanBeNull) {
- genCtx.lastNullChecks = prevVar + " == null";
- return "(" + prevVar + " == null ? null : "
- + boxName + ".valueOf(" + rawAccess + "))";
- } else {
- genCtx.lastNullChecks = null;
- return boxName + ".valueOf(" + rawAccess + ")";
- }
- }
-
- // Reuse existing variable (dedup)
- final String existingVar = genCtx.protoVars.get(key);
- if (existingVar != null) {
- prevVar = existingVar;
- currentType = returnType;
- prevCanBeNull = true;
- continue;
- }
-
- // Create new local variable declaration
- final String newVar = "_t" + genCtx.protoVarCounter++;
- final String returnTypeName = returnType.getName();
- if (seg.isSafeNav() && prevCanBeNull) {
- genCtx.protoVarDecls.append(" ").append(returnTypeName)
- .append(" ").append(newVar).append(" = ")
- .append(prevVar).append(" == null ? null : ")
- .append(prevVar).append(".").append(getterName).append("();\n");
- prevCanBeNull = true;
- } else {
- genCtx.protoVarDecls.append(" ").append(returnTypeName)
- .append(" ").append(newVar).append(" = ")
- .append(prevVar).append(".").append(getterName).append("();\n");
- prevCanBeNull = !returnType.isPrimitive();
- }
- genCtx.protoVars.put(key, newVar);
- genCtx.protoLvtVars.add(new String[]{
- newVar, "L" + returnTypeName.replace('.', '/') + ";"
- });
-
- prevVar = newVar;
- currentType = returnType;
- }
-
- // Non-primitive final result — null checks are in declarations
- genCtx.lastResolvedType = currentType;
- genCtx.lastRawChain = prevVar;
- genCtx.lastNullChecks = null;
- return prevVar;
- }
-
- // ==================== Def statement codegen ====================
-
- static void generateDefStatement(final StringBuilder sb,
- final LALScriptModel.DefStatement def,
- final LALClassGenerator.GenCtx genCtx) {
- final LALScriptModel.ValueAccess init = def.getInitializer();
- final String varName = def.getVarName();
- final String javaVar = "_def_" + varName;
- final boolean alreadyDeclared = genCtx.localVars.containsKey(varName);
-
- // Determine type and generate initializer expression
- Class<?> resolvedType;
- final StringBuilder initExpr = new StringBuilder();
-
- if (init.getFunctionCallName() != null
- && BUILTIN_FUNCTIONS.containsKey(init.getFunctionCallName())) {
- // Built-in function: toJson(...), toJsonArray(...)
- final String funcName = init.getFunctionCallName();
- final int argCount = init.getFunctionCallArgs().size();
- if (argCount != 1) {
- throw new IllegalArgumentException(
- funcName + "() requires exactly 1 argument, got " + argCount);
- }
- final Object[] info = BUILTIN_FUNCTIONS.get(funcName);
- final String helperMethod = (String) info[0];
- resolvedType = (Class<?>) info[1];
-
- initExpr.append(helperMethod).append("(");
- generateValueAccess(initExpr,
- init.getFunctionCallArgs().get(0).getValue(), genCtx);
- initExpr.append(")");
- } else {
- // General value access — type inferred from lastResolvedType
- generateValueAccess(initExpr, init, genCtx);
- resolvedType = genCtx.lastResolvedType != null
- ? genCtx.lastResolvedType : Object.class;
- // Box primitive types for local variable declarations
- if (resolvedType.isPrimitive()) {
- final String boxName = LALCodegenHelper.boxTypeName(resolvedType);
- if (boxName != null) {
- try {
- resolvedType = Class.forName("java.lang." + boxName);
- } catch (ClassNotFoundException ignored) {
- // keep primitive
- }
- }
- }
- }
-
- // Apply explicit type cast if specified (e.g., "as com.example.MyType")
- final String castType = def.getCastType();
- if (castType != null && !castType.isEmpty()) {
- // Resolve the cast type — primitive wrapper names are handled,
- // anything else is treated as a FQCN
- final Class<?> castClass = resolveDefCastType(castType);
- if (castClass != null) {
- resolvedType = castClass;
- }
- }
-
- // Register in local vars for later reference
- genCtx.localVars.put(varName,
- new LALClassGenerator.LocalVarInfo(javaVar, resolvedType));
-
- // Emit declaration (placed at method top via localVarDecls) — skip if already declared
- if (!alreadyDeclared) {
- genCtx.localVarDecls.append(" ").append(resolvedType.getName())
- .append(" ").append(javaVar).append(";\n");
- genCtx.localVarLvtVars.add(new String[]{
- javaVar, "L" + resolvedType.getName().replace('.', '/') + ";"
- });
- }
-
- // Emit assignment in body (at the point where def appears)
- sb.append(" ").append(javaVar).append(" = ");
- if (castType != null && !castType.isEmpty()) {
- sb.append("(").append(resolvedType.getName()).append(") ");
- }
- sb.append(initExpr).append(";\n");
- }
-
- /**
- * Resolves a cast type string to a {@link Class}.
- * Handles the four built-in type names ({@code String}, {@code Long},
- * {@code Integer}, {@code Boolean}) and fully qualified class names.
- */
- private static Class<?> resolveDefCastType(final String castType) {
- switch (castType) {
- case "String":
- return String.class;
- case "Long":
- return Long.class;
- case "Integer":
- return Integer.class;
- case "Boolean":
- return Boolean.class;
- default:
- try {
- return Class.forName(castType);
- } catch (ClassNotFoundException e) {
- throw new IllegalArgumentException(
- "def cast type not found on classpath: " + castType, e);
- }
- }
- }
-
- // ==================== Def variable chain codegen ====================
-
- /**
- * Generates typed method-chain access on a def variable.
- * Uses reflection to resolve each method/field call and track types.
- *
- * @param sb output buffer
- * @param localVar the def variable info (java var name + resolved type)
- * @param chain the chain segments after the variable name
- * @param genCtx codegen context
- */
- static void generateDefVarChain(
- final StringBuilder sb,
- final LALClassGenerator.LocalVarInfo localVar,
- final List<LALScriptModel.ValueAccessSegment> chain,
- final LALClassGenerator.GenCtx genCtx) {
- if (chain.isEmpty()) {
- sb.append(localVar.javaVarName);
- genCtx.lastResolvedType = localVar.resolvedType;
- return;
- }
-
- String prevExpr = localVar.javaVarName;
- Class<?> currentType = localVar.resolvedType;
- boolean canBeNull = true;
-
- for (int i = 0; i < chain.size(); i++) {
- final LALScriptModel.ValueAccessSegment seg = chain.get(i);
- final boolean isLast = i == chain.size() - 1;
-
- if (seg instanceof LALScriptModel.MethodSegment) {
- final LALScriptModel.MethodSegment ms =
- (LALScriptModel.MethodSegment) seg;
- final String methodName = ms.getName();
-
- // Resolve method on currentType via reflection
- final java.lang.reflect.Method method =
- resolveMethod(currentType, methodName, ms.getArguments());
- if (method == null) {
- throw new IllegalArgumentException(
- "Cannot resolve method " + currentType.getSimpleName()
- + "." + methodName + "() in def variable chain");
- }
- final Class<?> returnType = method.getReturnType();
- final String args = generateMethodArgs(ms.getArguments(), genCtx);
-
- if (ms.isSafeNav() && canBeNull) {
- if (isLast && returnType.isPrimitive()) {
- // Primitive return with null guard
- final String boxName =
- LALCodegenHelper.boxTypeName(returnType);
- prevExpr = "(" + prevExpr + " == null ? null : "
- + boxName + ".valueOf(" + prevExpr + "."
- + methodName + "(" + args + ")))";
- currentType = returnType;
- } else {
- prevExpr = "(" + prevExpr + " == null ? null : "
- + prevExpr + "." + methodName + "(" + args + "))";
- currentType = returnType;
- canBeNull = true;
- }
- } else {
- prevExpr = prevExpr + "." + methodName + "(" + args + ")";
- currentType = returnType;
- canBeNull = !returnType.isPrimitive();
- }
- } else if (seg instanceof LALScriptModel.FieldSegment) {
- final LALScriptModel.FieldSegment fs =
- (LALScriptModel.FieldSegment) seg;
- final String fieldName = fs.getName();
- // Try getter first
- final String getterName = "get"
- + Character.toUpperCase(fieldName.charAt(0))
- + fieldName.substring(1);
- java.lang.reflect.Method getter = null;
- try {
- getter = currentType.getMethod(getterName);
- } catch (NoSuchMethodException e) {
- // Try direct field access name
- try {
- getter = currentType.getMethod(fieldName);
- } catch (NoSuchMethodException e2) {
- throw new IllegalArgumentException(
- "Cannot resolve field/getter "
- + currentType.getSimpleName()
- + "." + fieldName + " in def variable chain");
- }
- }
- final Class<?> returnType = getter.getReturnType();
-
- if (fs.isSafeNav() && canBeNull) {
- if (isLast && returnType.isPrimitive()) {
- final String boxName =
- LALCodegenHelper.boxTypeName(returnType);
- prevExpr = "(" + prevExpr + " == null ? null : "
- + boxName + ".valueOf(" + prevExpr + "."
- + getter.getName() + "()))";
- currentType = returnType;
- } else {
- prevExpr = "(" + prevExpr + " == null ? null : "
- + prevExpr + "." + getter.getName() + "())";
- currentType = returnType;
- canBeNull = true;
- }
- } else {
- prevExpr = prevExpr + "." + getter.getName() + "()";
- currentType = returnType;
- canBeNull = !returnType.isPrimitive();
- }
- } else if (seg instanceof LALScriptModel.IndexSegment) {
- final int index = ((LALScriptModel.IndexSegment) seg).getIndex();
- // Try get(int) method (e.g., JsonArray.get(int))
- java.lang.reflect.Method getMethod = null;
- try {
- getMethod = currentType.getMethod("get", int.class);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(
- "Cannot resolve index access on "
- + currentType.getSimpleName()
- + " in def variable chain");
- }
- final Class<?> returnType = getMethod.getReturnType();
- if (canBeNull) {
- prevExpr = "(" + prevExpr + " == null ? null : "
- + prevExpr + ".get(" + index + "))";
- } else {
- prevExpr = prevExpr + ".get(" + index + ")";
- }
- currentType = returnType;
- canBeNull = true;
- }
- }
-
- genCtx.lastResolvedType = currentType;
- sb.append(prevExpr);
- }
-
- /**
- * Resolves a method on the given type by name, matching argument count.
- * For methods with String arguments (like JsonObject.get(String)),
- * prioritizes exact match by parameter types.
- */
- private static java.lang.reflect.Method resolveMethod(
- final Class<?> type, final String name,
- final List<LALScriptModel.FunctionArg> args) {
- final int argCount = args != null ? args.size() : 0;
- // Try exact match with common parameter types
- if (argCount == 1) {
- try {
- return type.getMethod(name, String.class);
- } catch (NoSuchMethodException ignored) {
- // fall through
- }
- try {
- return type.getMethod(name, int.class);
- } catch (NoSuchMethodException ignored) {
- // fall through
- }
- }
- if (argCount == 0) {
- try {
- return type.getMethod(name);
- } catch (NoSuchMethodException ignored) {
- // fall through
- }
- }
- // Fallback: find by name and arg count
- for (final java.lang.reflect.Method m : type.getMethods()) {
- if (m.getName().equals(name)
- && m.getParameterCount() == argCount) {
- return m;
- }
- }
- return null;
- }
-
- // ==================== ProcessRegistry ====================
-
- static void generateProcessRegistryCall(
- final StringBuilder sb,
- final LALScriptModel.ValueAccess value,
- final LALClassGenerator.GenCtx genCtx) {
- final List<LALScriptModel.ValueAccessSegment> chain = value.getChain();
- if (chain.isEmpty()) {
- sb.append("null");
- return;
- }
- final LALScriptModel.ValueAccessSegment seg = chain.get(0);
- if (seg instanceof LALScriptModel.MethodSegment) {
- final LALScriptModel.MethodSegment ms =
- (LALScriptModel.MethodSegment) seg;
- sb.append(PROCESS_REGISTRY).append(".")
- .append(ms.getName()).append("(");
- final List<LALScriptModel.FunctionArg> args = ms.getArguments();
- for (int i = 0; i < args.size(); i++) {
- if (i > 0) {
- sb.append(", ");
- }
- generateCastedValueAccess(sb,
- args.get(i).getValue(), args.get(i).getCastType(), genCtx);
- }
- sb.append(")");
- } else {
- sb.append("null");
- }
- }
-
- // ==================== Utility methods ====================
-
- static String appendMethodSegment(final String current,
- final LALScriptModel.MethodSegment ms,
- final LALClassGenerator.GenCtx genCtx) {
- final String mn = ms.getName();
- final String args = ms.getArguments().isEmpty()
- ? "" : generateMethodArgs(ms.getArguments(), genCtx);
- if (ms.isSafeNav()) {
- // Special-cased helpers for common safe-nav methods on Object
- if ("toString".equals(mn)) {
- return "h.toString(" + current + ")";
- } else if ("trim".equals(mn)) {
- return "h.trim(" + current + ")";
- }
- // General safe-nav: null guard with ternary
- return "(" + current + " == null ? null : "
- + current + "." + mn + "(" + args + "))";
- } else {
- return current + "." + mn + "(" + args + ")";
- }
- }
-
- static String generateMethodArgs(
- final List<LALScriptModel.FunctionArg> args,
- final LALClassGenerator.GenCtx genCtx) {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < args.size(); i++) {
- if (i > 0) {
- sb.append(", ");
- }
- final LALScriptModel.FunctionArg arg = args.get(i);
- final LALScriptModel.ValueAccess va = arg.getValue();
- if (va.isStringLiteral()) {
- sb.append("\"").append(LALCodegenHelper.escapeJava(
- va.getSegments().get(0))).append("\"");
- } else if (va.isNumberLiteral()) {
- sb.append(va.getSegments().get(0));
- } else if (!va.getSegments().isEmpty()) {
- final String text = va.getSegments().get(0);
- if ("true".equals(text) || "false".equals(text)
- || "null".equals(text)) {
- // Boolean or null literal
- sb.append(text);
- } else if (genCtx != null
- && genCtx.localVars.containsKey(text)) {
- // Local def variable reference
- sb.append(genCtx.localVars.get(text).javaVarName);
- } else {
- throw new IllegalArgumentException(
- "Unknown identifier used as method argument: '" + text + "'");
- }
- } else {
- sb.append("null");
- }
- }
- return sb.toString();
- }
}
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 3a1cab3655..59854cc961 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
@@ -440,7 +440,13 @@ public final class LALClassGenerator {
final Class<?> resolvedOutput = this.outputType != null
? this.outputType
: org.apache.skywalking.oap.server.core.source.LogBuilder.class;
- final GenCtx genCtx = new GenCtx(parserType, this.inputType, resolvedOutput);
+ // inputType is only meaningful for parser-less rules (NONE) where parsed.*
+ // generates direct proto getter calls. When a parser is present (json/yaml/text),
+ // parsed.* reads from the parsed map and tag() reads from LogData.Builder tags,
+ // so inputType must be null to avoid mis-guarding codegen branches.
+ final Class<?> effectiveInputType =
+ parserType == ParserType.NONE ? this.inputType : null;
+ final GenCtx genCtx = new GenCtx(parserType, effectiveInputType, resolvedOutput);
if (parserType == ParserType.NONE && this.inputType != null) {
log.info("LAL rule has no parser — using inputType {} for "
@@ -543,7 +549,7 @@ public final class LALClassGenerator {
if (genCtx.inputType != null) {
final String elTypeName = genCtx.inputType.getName();
sb.append(" ").append(elTypeName).append(" _p = (")
- .append(elTypeName).append(") h.ctx().extraLog();\n");
+ .append(elTypeName).append(") h.ctx().input();\n");
}
sb.append(genCtx.protoVarDecls);
}
@@ -589,7 +595,7 @@ public final class LALClassGenerator {
} else if (stmt instanceof LALScriptModel.IfBlock) {
generateTopLevelIfBlock(sb, (LALScriptModel.IfBlock) stmt, genCtx);
} else if (stmt instanceof LALScriptModel.DefStatement) {
- LALBlockCodegen.generateDefStatement(
+ LALDefCodegen.generateDefStatement(
sb, (LALScriptModel.DefStatement) stmt, genCtx);
}
}
@@ -598,7 +604,7 @@ public final class LALClassGenerator {
final LALScriptModel.IfBlock ifBlock,
final GenCtx genCtx) {
sb.append(" if (");
- LALBlockCodegen.generateCondition(sb, ifBlock.getCondition(), genCtx);
+ LALValueCodegen.generateCondition(sb, ifBlock.getCondition(), genCtx);
sb.append(") {\n");
for (final LALScriptModel.FilterStatement s : ifBlock.getThenBranch()) {
generateFilterStatement(sb, s, genCtx);
@@ -624,8 +630,9 @@ public final class LALClassGenerator {
final Class<?> resolvedOutput = this.outputType != null
? this.outputType
: org.apache.skywalking.oap.server.core.source.LogBuilder.class;
+ final ParserType pt = detectParserType(model.getStatements());
final GenCtx genCtx = new GenCtx(
- detectParserType(model.getStatements()), this.inputType, resolvedOutput);
+ pt, pt == ParserType.NONE ? this.inputType : null, resolvedOutput);
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/LALCodegenHelper.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALCodegenHelper.java
index bb890a65fe..a463d13b4e 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALCodegenHelper.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALCodegenHelper.java
@@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.skywalking.apm.network.logging.v3.LogData;
/**
* Static utility constants and methods extracted from {@link LALClassGenerator}
@@ -29,27 +30,40 @@ import java.util.Set;
*/
final class LALCodegenHelper {
+ /** Metadata fields — available on {@code LogMetadata} (always present). */
+ static final Map<String, String> METADATA_GETTERS = new HashMap<>();
+ /** TraceContext sub-fields on {@code LogMetadata.TraceContext}. */
+ static final Map<String, String> METADATA_TRACE_GETTERS = new HashMap<>();
+ /** LogData-only fields — require the input to be {@code LogData}. */
static final Map<String, String> LOG_GETTERS = new HashMap<>();
- static final Map<String, String> TRACE_CONTEXT_GETTERS = new HashMap<>();
+ /**
+ * Getter-name overrides for reflection-based access.
+ * Applied by {@link LALBlockCodegen#generateExtraLogAccess} when a getter
+ * is not found on the current type — tries the alias before failing.
+ */
+ static final Map<String, String> METADATA_GETTER_ALIASES = Map.of();
static final Set<String> LONG_FIELDS = new HashSet<>();
static final Set<String> INT_FIELDS = new HashSet<>();
+ static final Class<?> LOGDATA_BUILDER_CLASS = LogData.Builder.class;
static {
- LOG_GETTERS.put("service", "getService");
- LOG_GETTERS.put("serviceInstance", "getServiceInstance");
- LOG_GETTERS.put("endpoint", "getEndpoint");
- LOG_GETTERS.put("timestamp", "getTimestamp");
+ METADATA_GETTERS.put("service", "getService");
+ METADATA_GETTERS.put("serviceInstance", "getServiceInstance");
+ METADATA_GETTERS.put("endpoint", "getEndpoint");
+ METADATA_GETTERS.put("layer", "getLayer");
+ METADATA_GETTERS.put("timestamp", "getTimestamp");
+ METADATA_GETTERS.put("traceContext", "getTraceContext");
+
+ METADATA_TRACE_GETTERS.put("traceId", "getTraceId");
+ METADATA_TRACE_GETTERS.put("traceSegmentId", "getTraceSegmentId");
+ METADATA_TRACE_GETTERS.put("spanId", "getSpanId");
+
LOG_GETTERS.put("body", "getBody");
- LOG_GETTERS.put("traceContext", "getTraceContext");
LOG_GETTERS.put("tags", "getTags");
- LOG_GETTERS.put("layer", "getLayer");
-
- TRACE_CONTEXT_GETTERS.put("traceId", "getTraceId");
- TRACE_CONTEXT_GETTERS.put("traceSegmentId", "getTraceSegmentId");
- TRACE_CONTEXT_GETTERS.put("spanId", "getSpanId");
LONG_FIELDS.add("timestamp");
INT_FIELDS.add("spanId");
+
}
private LALCodegenHelper() {
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALDefCodegen.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALDefCodegen.java
new file mode 100644
index 0000000000..53afc2c584
--- /dev/null
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALDefCodegen.java
@@ -0,0 +1,360 @@
+/*
+ * 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.compiler;
+
+import java.util.List;
+
+/**
+ * Code generation for LAL {@code def} local variable declarations and
+ * typed method-chain resolution on def variables.
+ *
+ * <p>The {@code def} keyword declares a local variable in the extractor
+ * block. The type is inferred from the initializer expression, and
+ * subsequent method chains are resolved via compile-time reflection.
+ *
+ * <p>LAL example:
+ * <pre>{@code
+ * extractor {
+ * // def with built-in function — type inferred as JsonObject
+ * def jwt = toJson(parsed?.commonProperties?.metadata?
+ * .filterMetadataMap?.get("envoy.filters.http.jwt_authn"))
+ *
+ * // def chaining on another def variable — type inferred as JsonObject
+ * def payload = jwt?.getAsJsonObject("payload")
+ *
+ * // Using def variable in tag assignment
+ * tag 'email': payload?.get("email")?.getAsString()
+ *
+ * // def with explicit type cast
+ * def code = parsed?.response?.responseCode?.value as Integer
+ * }
+ * }</pre>
+ *
+ * <p>Generated code declares Java locals with {@code _def_} prefix
+ * (e.g. {@code _def_jwt}, {@code _def_payload}) and emits assignments
+ * at the point where {@code def} appears in the DSL.
+ */
+final class LALDefCodegen {
+
+ private LALDefCodegen() {
+ // utility class
+ }
+
+ // ==================== Def statement codegen ====================
+
+ /**
+ * Generates a {@code def} variable declaration and assignment.
+ *
+ * <p>LAL: {@code def jwt = toJson(parsed?.metadata?.filterMetadataMap?.get("key"))}
+ *
+ * <p>Generated (declaration at method top):
+ * <pre>{@code
+ * com.google.gson.JsonObject _def_jwt;
+ * }</pre>
+ * <p>Generated (assignment at def site):
+ * <pre>{@code
+ * _def_jwt = h.toJsonObject(h.mapVal("metadata", "filterMetadataMap", "key"));
+ * }</pre>
+ *
+ * <p>Type inference: built-in functions ({@code toJson} →
+ * {@code JsonObject}, {@code toJsonArray} → {@code JsonArray}) have
+ * known return types. General expressions infer the type from
+ * {@code genCtx.lastResolvedType} after codegen. An explicit
+ * {@code as Type} cast overrides the inferred type.
+ */
+ static void generateDefStatement(final StringBuilder sb,
+ final LALScriptModel.DefStatement def,
+ final LALClassGenerator.GenCtx genCtx) {
+ final LALScriptModel.ValueAccess init = def.getInitializer();
+ final String varName = def.getVarName();
+ final String javaVar = "_def_" + varName;
+ final boolean alreadyDeclared = genCtx.localVars.containsKey(varName);
+
+ // Determine type and generate initializer expression
+ Class<?> resolvedType;
+ final StringBuilder initExpr = new StringBuilder();
+
+ if (init.getFunctionCallName() != null
+ && LALBlockCodegen.BUILTIN_FUNCTIONS.containsKey(init.getFunctionCallName())) {
+ // Built-in function: toJson(...), toJsonArray(...)
+ final String funcName = init.getFunctionCallName();
+ final int argCount = init.getFunctionCallArgs().size();
+ if (argCount != 1) {
+ throw new IllegalArgumentException(
+ funcName + "() requires exactly 1 argument, got " + argCount);
+ }
+ final Object[] info = LALBlockCodegen.BUILTIN_FUNCTIONS.get(funcName);
+ final String helperMethod = (String) info[0];
+ resolvedType = (Class<?>) info[1];
+
+ initExpr.append(helperMethod).append("(");
+ LALValueCodegen.generateValueAccess(initExpr,
+ init.getFunctionCallArgs().get(0).getValue(), genCtx);
+ initExpr.append(")");
+ } else {
+ // General value access — type inferred from lastResolvedType
+ LALValueCodegen.generateValueAccess(initExpr, init, genCtx);
+ resolvedType = genCtx.lastResolvedType != null
+ ? genCtx.lastResolvedType : Object.class;
+ // Box primitive types for local variable declarations
+ if (resolvedType.isPrimitive()) {
+ final String boxName = LALCodegenHelper.boxTypeName(resolvedType);
+ if (boxName != null) {
+ try {
+ resolvedType = Class.forName("java.lang." + boxName);
+ } catch (ClassNotFoundException ignored) {
+ // keep primitive
+ }
+ }
+ }
+ }
+
+ // Apply explicit type cast if specified (e.g., "as com.example.MyType")
+ final String castType = def.getCastType();
+ if (castType != null && !castType.isEmpty()) {
+ // Resolve the cast type — primitive wrapper names are handled,
+ // anything else is treated as a FQCN
+ final Class<?> castClass = resolveDefCastType(castType);
+ if (castClass != null) {
+ resolvedType = castClass;
+ }
+ }
+
+ // Register in local vars for later reference
+ genCtx.localVars.put(varName,
+ new LALClassGenerator.LocalVarInfo(javaVar, resolvedType));
+
+ // Emit declaration (placed at method top via localVarDecls) — skip if already declared
+ if (!alreadyDeclared) {
+ genCtx.localVarDecls.append(" ").append(resolvedType.getName())
+ .append(" ").append(javaVar).append(";\n");
+ genCtx.localVarLvtVars.add(new String[]{
+ javaVar, "L" + resolvedType.getName().replace('.', '/') + ";"
+ });
+ }
+
+ // Emit assignment in body (at the point where def appears)
+ sb.append(" ").append(javaVar).append(" = ");
+ if (castType != null && !castType.isEmpty()) {
+ sb.append("(").append(resolvedType.getName()).append(") ");
+ }
+ sb.append(initExpr).append(";\n");
+ }
+
+ /**
+ * Resolves a cast type string to a {@link Class}.
+ * Handles the four built-in type names ({@code String}, {@code Long},
+ * {@code Integer}, {@code Boolean}) and fully qualified class names.
+ */
+ private static Class<?> resolveDefCastType(final String castType) {
+ switch (castType) {
+ case "String":
+ return String.class;
+ case "Long":
+ return Long.class;
+ case "Integer":
+ return Integer.class;
+ case "Boolean":
+ return Boolean.class;
+ default:
+ try {
+ return Class.forName(castType);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException(
+ "def cast type not found on classpath: " + castType, e);
+ }
+ }
+ }
+
+ // ==================== Def variable chain codegen ====================
+
+ /**
+ * Generates typed method-chain access on a def variable.
+ * Uses reflection to resolve each method/field call and track types.
+ *
+ * @param sb output buffer
+ * @param localVar the def variable info (java var name + resolved type)
+ * @param chain the chain segments after the variable name
+ * @param genCtx codegen context
+ */
+ static void generateDefVarChain(
+ final StringBuilder sb,
+ final LALClassGenerator.LocalVarInfo localVar,
+ final List<LALScriptModel.ValueAccessSegment> chain,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (chain.isEmpty()) {
+ sb.append(localVar.javaVarName);
+ genCtx.lastResolvedType = localVar.resolvedType;
+ return;
+ }
+
+ String prevExpr = localVar.javaVarName;
+ Class<?> currentType = localVar.resolvedType;
+ boolean canBeNull = true;
+
+ for (int i = 0; i < chain.size(); i++) {
+ final LALScriptModel.ValueAccessSegment seg = chain.get(i);
+ final boolean isLast = i == chain.size() - 1;
+
+ if (seg instanceof LALScriptModel.MethodSegment) {
+ final LALScriptModel.MethodSegment ms =
+ (LALScriptModel.MethodSegment) seg;
+ final String methodName = ms.getName();
+
+ // Resolve method on currentType via reflection
+ final java.lang.reflect.Method method =
+ resolveMethod(currentType, methodName, ms.getArguments());
+ if (method == null) {
+ throw new IllegalArgumentException(
+ "Cannot resolve method " + currentType.getSimpleName()
+ + "." + methodName + "() in def variable chain");
+ }
+ final Class<?> returnType = method.getReturnType();
+ final String args =
+ LALValueCodegen.generateMethodArgs(ms.getArguments(), genCtx);
+
+ if (ms.isSafeNav() && canBeNull) {
+ if (isLast && returnType.isPrimitive()) {
+ // Primitive return with null guard
+ final String boxName =
+ LALCodegenHelper.boxTypeName(returnType);
+ prevExpr = "(" + prevExpr + " == null ? null : "
+ + boxName + ".valueOf(" + prevExpr + "."
+ + methodName + "(" + args + ")))";
+ currentType = returnType;
+ } else {
+ prevExpr = "(" + prevExpr + " == null ? null : "
+ + prevExpr + "." + methodName + "(" + args + "))";
+ currentType = returnType;
+ canBeNull = true;
+ }
+ } else {
+ prevExpr = prevExpr + "." + methodName + "(" + args + ")";
+ currentType = returnType;
+ canBeNull = !returnType.isPrimitive();
+ }
+ } else if (seg instanceof LALScriptModel.FieldSegment) {
+ final LALScriptModel.FieldSegment fs =
+ (LALScriptModel.FieldSegment) seg;
+ final String fieldName = fs.getName();
+ // Try getter first
+ final String getterName = "get"
+ + Character.toUpperCase(fieldName.charAt(0))
+ + fieldName.substring(1);
+ java.lang.reflect.Method getter = null;
+ try {
+ getter = currentType.getMethod(getterName);
+ } catch (NoSuchMethodException e) {
+ // Try direct field access name
+ try {
+ getter = currentType.getMethod(fieldName);
+ } catch (NoSuchMethodException e2) {
+ throw new IllegalArgumentException(
+ "Cannot resolve field/getter "
+ + currentType.getSimpleName()
+ + "." + fieldName + " in def variable chain");
+ }
+ }
+ final Class<?> returnType = getter.getReturnType();
+
+ if (fs.isSafeNav() && canBeNull) {
+ if (isLast && returnType.isPrimitive()) {
+ final String boxName =
+ LALCodegenHelper.boxTypeName(returnType);
+ prevExpr = "(" + prevExpr + " == null ? null : "
+ + boxName + ".valueOf(" + prevExpr + "."
+ + getter.getName() + "()))";
+ currentType = returnType;
+ } else {
+ prevExpr = "(" + prevExpr + " == null ? null : "
+ + prevExpr + "." + getter.getName() + "())";
+ currentType = returnType;
+ canBeNull = true;
+ }
+ } else {
+ prevExpr = prevExpr + "." + getter.getName() + "()";
+ currentType = returnType;
+ canBeNull = !returnType.isPrimitive();
+ }
+ } else if (seg instanceof LALScriptModel.IndexSegment) {
+ final int index = ((LALScriptModel.IndexSegment) seg).getIndex();
+ // Try get(int) method (e.g., JsonArray.get(int))
+ java.lang.reflect.Method getMethod = null;
+ try {
+ getMethod = currentType.getMethod("get", int.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "Cannot resolve index access on "
+ + currentType.getSimpleName()
+ + " in def variable chain");
+ }
+ final Class<?> returnType = getMethod.getReturnType();
+ if (canBeNull) {
+ prevExpr = "(" + prevExpr + " == null ? null : "
+ + prevExpr + ".get(" + index + "))";
+ } else {
+ prevExpr = prevExpr + ".get(" + index + ")";
+ }
+ currentType = returnType;
+ canBeNull = true;
+ }
+ }
+
+ genCtx.lastResolvedType = currentType;
+ sb.append(prevExpr);
+ }
+
+ /**
+ * Resolves a method on the given type by name, matching argument count.
+ * For methods with String arguments (like JsonObject.get(String)),
+ * prioritizes exact match by parameter types.
+ */
+ private static java.lang.reflect.Method resolveMethod(
+ final Class<?> type, final String name,
+ final List<LALScriptModel.FunctionArg> args) {
+ final int argCount = args != null ? args.size() : 0;
+ // Try exact match with common parameter types
+ if (argCount == 1) {
+ try {
+ return type.getMethod(name, String.class);
+ } catch (NoSuchMethodException ignored) {
+ // fall through
+ }
+ try {
+ return type.getMethod(name, int.class);
+ } catch (NoSuchMethodException ignored) {
+ // fall through
+ }
+ }
+ if (argCount == 0) {
+ try {
+ return type.getMethod(name);
+ } catch (NoSuchMethodException ignored) {
+ // fall through
+ }
+ }
+ // Fallback: find by name and arg count
+ for (final java.lang.reflect.Method m : type.getMethods()) {
+ if (m.getName().equals(name)
+ && m.getParameterCount() == argCount) {
+ return m;
+ }
+ }
+ return null;
+ }
+}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java
new file mode 100644
index 0000000000..63588f34b1
--- /dev/null
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java
@@ -0,0 +1,903 @@
+/*
+ * 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.compiler;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
+
+/**
+ * Code generation for LAL expression evaluation: value access, conditions,
+ * type casts, and all data-source-specific access patterns.
+ *
+ * <p>This class handles the expression-level codegen that
+ * {@link LALBlockCodegen} delegates to when it encounters value expressions
+ * inside block statements. The central dispatch method is
+ * {@link #generateValueAccess}, which routes each expression to the
+ * appropriate codegen path:
+ *
+ * <pre>{@code
+ * Expression type LAL example Generated code
+ * ─────────────────────── ─────────────────────────────────── ────────────────────────────
+ * tag() function tag("LOG_KIND") h.tagValue("LOG_KIND")
+ * String literal "SLOW_SQL" "SLOW_SQL"
+ * Number literal 500 Integer.valueOf(500)
+ * ProcessRegistry ProcessRegistry.generateVirtual.. ProcessRegistry.generate...
+ * log.* field log.traceContext.traceId h.ctx().metadata().get...
+ * parsed.* (json/yaml) parsed.service h.mapVal("service")
+ * parsed.* (text) parsed.level h.group("level")
+ * parsed.* (typed proto) parsed?.response?.responseCode ((Type)_p).getResponse()...
+ * def variable myVar?.getAsString() _def_myVar?.getAsString()
+ * Parenthesized (expr as String).trim() h.toStr(...).trim()
+ * String concat "${log.service}:${parsed.code}" "" + ... + ":" + ...
+ * }</pre>
+ *
+ * <p>Condition codegen ({@link #generateCondition}) handles {@code if}
+ * conditions: comparisons ({@code ==}, {@code !=}, {@code <}, {@code >}),
+ * logical operators ({@code &&}, {@code ||}), negation ({@code !}), and
+ * expression-as-boolean ({@code if (parsed?.flags?.toString())}).
+ */
+final class LALValueCodegen {
+
+ static final String LOGDATA_BUILDER_CAST =
+ "((org.apache.skywalking.apm.network.logging.v3.LogData.Builder) h.ctx().input())";
+ static final String PROCESS_REGISTRY =
+ "org.apache.skywalking.oap.meter.analyzer.v2.dsl.registry.ProcessRegistry";
+
+ private LALValueCodegen() {
+ // utility class
+ }
+
+ // ==================== Conditions ====================
+
+ /**
+ * Generates code for an {@code if} condition expression.
+ *
+ * <p>LAL examples and their generated code:
+ * <pre>{@code
+ * // Comparison (==, !=):
+ * if (tag("LOG_KIND") == "SLOW_SQL")
+ * → java.util.Objects.equals(h.tagValue("LOG_KIND"), "SLOW_SQL")
+ *
+ * // Numeric comparison (<, >, <=, >=):
+ * if (parsed?.response?.responseCode?.value as Integer < 400)
+ * → ((Type)_p).getResponse().getResponseCode().getValue() < 400L
+ *
+ * // Logical operators:
+ * if (condA && condB) → (condA && condB)
+ * if (!cond) → !(cond)
+ *
+ * // Expression-as-boolean (truthy check):
+ * if (parsed?.commonProperties?.responseFlags?.toString())
+ * → h.isNotEmpty(h.toString(...))
+ * }</pre>
+ */
+ static void generateCondition(final StringBuilder sb,
+ final LALScriptModel.Condition cond,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (cond instanceof LALScriptModel.ComparisonCondition) {
+ final LALScriptModel.ComparisonCondition cc =
+ (LALScriptModel.ComparisonCondition) cond;
+ switch (cc.getOp()) {
+ case EQ:
+ sb.append("java.util.Objects.equals(");
+ generateValueAccessObj(sb, cc.getLeft(), cc.getLeftCast(), genCtx);
+ sb.append(", ");
+ generateConditionValue(sb, cc.getRight(), genCtx);
+ sb.append(")");
+ break;
+ case NEQ:
+ sb.append("!java.util.Objects.equals(");
+ generateValueAccessObj(sb, cc.getLeft(), cc.getLeftCast(), genCtx);
+ sb.append(", ");
+ generateConditionValue(sb, cc.getRight(), genCtx);
+ sb.append(")");
+ break;
+ case GT:
+ generateNumericComparison(sb, cc, " > ", genCtx);
+ break;
+ case LT:
+ generateNumericComparison(sb, cc, " < ", genCtx);
+ break;
+ case GTE:
+ generateNumericComparison(sb, cc, " >= ", genCtx);
+ break;
+ case LTE:
+ generateNumericComparison(sb, cc, " <= ", genCtx);
+ break;
+ default:
+ break;
+ }
+ } else if (cond instanceof LALScriptModel.LogicalCondition) {
+ final LALScriptModel.LogicalCondition lc =
+ (LALScriptModel.LogicalCondition) cond;
+ sb.append("(");
+ generateCondition(sb, lc.getLeft(), genCtx);
+ sb.append(lc.getOp() == LALScriptModel.LogicalOp.AND
+ ? " && " : " || ");
+ generateCondition(sb, lc.getRight(), genCtx);
+ sb.append(")");
+ } else if (cond instanceof LALScriptModel.NotCondition) {
+ sb.append("!(");
+ generateCondition(sb,
+ ((LALScriptModel.NotCondition) cond).getInner(), genCtx);
+ sb.append(")");
+ } else if (cond instanceof LALScriptModel.ExprCondition) {
+ final String ct = ((LALScriptModel.ExprCondition) cond).getCastType();
+ final String method = "Boolean".equals(ct) || "boolean".equals(ct)
+ ? ".isTrue(" : ".isNotEmpty(";
+ sb.append("h").append(method);
+ generateValueAccessObj(sb,
+ ((LALScriptModel.ExprCondition) cond).getExpr(),
+ ct, genCtx);
+ sb.append(")");
+ }
+ }
+
+ static void generateNumericComparison(
+ final StringBuilder sb,
+ final LALScriptModel.ComparisonCondition cc,
+ final String op,
+ final LALClassGenerator.GenCtx genCtx) {
+ // Generate left side into buffer to inspect resolved type
+ final StringBuilder leftBuf = new StringBuilder();
+ generateValueAccessObj(leftBuf, cc.getLeft(), null, genCtx);
+
+ final boolean primitiveNumeric = genCtx.lastResolvedType != null
+ && (genCtx.lastResolvedType == int.class
+ || genCtx.lastResolvedType == long.class);
+
+ if (primitiveNumeric && genCtx.lastRawChain != null) {
+ // Direct primitive comparison — no boxing, no h.toLong()
+ if (genCtx.lastNullChecks != null) {
+ sb.append("(").append(genCtx.lastNullChecks).append(" ? false : ")
+ .append(genCtx.lastRawChain).append(op);
+ generateConditionValueNumeric(sb, cc.getRight(), genCtx);
+ sb.append(")");
+ } else {
+ sb.append(genCtx.lastRawChain).append(op);
+ generateConditionValueNumeric(sb, cc.getRight(), genCtx);
+ }
+ } else {
+ // Fallback: h.toLong() conversion
+ sb.append("h.toLong(").append(leftBuf).append(")").append(op);
+ generateConditionValueNumeric(sb, cc.getRight(), genCtx);
+ }
+ }
+
+ static void generateConditionValue(final StringBuilder sb,
+ final LALScriptModel.ConditionValue cv,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (cv instanceof LALScriptModel.StringConditionValue) {
+ sb.append('"')
+ .append(LALCodegenHelper.escapeJava(
+ ((LALScriptModel.StringConditionValue) cv).getValue()))
+ .append('"');
+ } else if (cv instanceof LALScriptModel.NumberConditionValue) {
+ final double val =
+ ((LALScriptModel.NumberConditionValue) cv).getValue();
+ sb.append("Long.valueOf(").append((long) val).append("L)");
+ } else if (cv instanceof LALScriptModel.BoolConditionValue) {
+ sb.append("Boolean.valueOf(")
+ .append(((LALScriptModel.BoolConditionValue) cv).isValue())
+ .append(")");
+ } else if (cv instanceof LALScriptModel.NullConditionValue) {
+ sb.append("null");
+ } else if (cv instanceof LALScriptModel.ValueAccessConditionValue) {
+ generateValueAccessObj(sb,
+ ((LALScriptModel.ValueAccessConditionValue) cv).getValue(),
+ null, genCtx);
+ }
+ }
+
+ static void generateConditionValueNumeric(
+ final StringBuilder sb,
+ final LALScriptModel.ConditionValue cv,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (cv instanceof LALScriptModel.NumberConditionValue) {
+ sb.append((long) ((LALScriptModel.NumberConditionValue) cv)
+ .getValue()).append("L");
+ } else if (cv instanceof LALScriptModel.ValueAccessConditionValue) {
+ sb.append("h.toLong(");
+ generateValueAccessObj(sb,
+ ((LALScriptModel.ValueAccessConditionValue) cv).getValue(),
+ null, genCtx);
+ sb.append(")");
+ } else {
+ sb.append("0L");
+ }
+ }
+
+ // ==================== Value access ====================
+
+ /**
+ * Generates a value access with an explicit type cast.
+ *
+ * <p>LAL: {@code parsed.latency as Long}
+ * <br>Generated: {@code h.toLong(h.mapVal("latency"))}
+ *
+ * <p>Supported cast types: String, Long, Integer, Boolean.
+ */
+ static void generateCastedValueAccess(final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final String castType,
+ final LALClassGenerator.GenCtx genCtx) {
+ if ("String".equals(castType)) {
+ sb.append("h.toStr(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else if ("Long".equals(castType)) {
+ sb.append("h.toLong(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else if ("Integer".equals(castType)) {
+ sb.append("h.toInt(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else if ("Boolean".equals(castType)) {
+ sb.append("h.toBool(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else {
+ generateValueAccess(sb, value, genCtx);
+ }
+ }
+
+ /**
+ * Generates a value access that must produce a String result (used by
+ * tag assignments). Wraps the inner expression with {@code h.toStr()}.
+ */
+ static void generateStringValueAccess(final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final String castType,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (castType == null || "String".equals(castType)) {
+ sb.append("h.toStr(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else if ("Long".equals(castType)) {
+ sb.append("String.valueOf(h.toLong(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append("))");
+ } else if ("Integer".equals(castType)) {
+ sb.append("String.valueOf(h.toInt(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append("))");
+ } else if ("Boolean".equals(castType)) {
+ sb.append("String.valueOf(h.toBool(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append("))");
+ } else {
+ sb.append("h.toStr(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ }
+ }
+
+ /**
+ * Generates a value access that produces an Object (boxed) result.
+ * Only applies String cast; other casts pass through as-is.
+ */
+ static void generateValueAccessObj(final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final String castType,
+ final LALClassGenerator.GenCtx genCtx) {
+ if ("String".equals(castType)) {
+ sb.append("h.toStr(");
+ generateValueAccess(sb, value, genCtx);
+ sb.append(")");
+ } else {
+ generateValueAccess(sb, value, genCtx);
+ }
+ }
+
+ /**
+ * Central dispatch for all LAL value-access expressions. Routes each
+ * expression to the appropriate codegen path based on its AST type.
+ *
+ * <p>Dispatch order:
+ * <ol>
+ * <li>String concatenation ({@code "..." + expr + "..."})</li>
+ * <li>Parenthesized expression ({@code (expr as Type).chain})</li>
+ * <li>Standalone {@code tag("KEY")} function call</li>
+ * <li>String/number literals</li>
+ * <li>{@code ProcessRegistry.method(...)} static calls</li>
+ * <li>{@code log.X.Y} direct proto getter chains</li>
+ * <li>{@code parsed.X.Y} with compile-time type analysis</li>
+ * <li>{@code def} variable reference with method chain</li>
+ * <li>Fallback: compile-time error</li>
+ * </ol>
+ *
+ * <p>Note: standalone {@code tag("KEY")} and {@code parsed.tag("KEY")}
+ * are different AST structures. {@code tag("KEY")} is a top-level
+ * function call handled here; {@code parsed.tag("KEY")} is a
+ * {@code parsed}-prefixed chain where {@code tag("KEY")} appears as a
+ * MethodSegment, handled by {@link #generateParsedAccess}.
+ */
+ static void generateValueAccess(final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final LALClassGenerator.GenCtx genCtx) {
+ genCtx.clearExtraLogResult();
+
+ // Handle string concatenation (term1 + term2 + ...)
+ if (!value.getConcatParts().isEmpty()) {
+ sb.append("(\"\" + ");
+ for (int i = 0; i < value.getConcatParts().size(); i++) {
+ if (i > 0) {
+ sb.append(" + ");
+ }
+ generateValueAccess(sb, value.getConcatParts().get(i), genCtx);
+ }
+ sb.append(")");
+ return;
+ }
+
+ // Handle parenthesized expression: (innerExpr as Type).chain...
+ if (value.getParenInner() != null) {
+ generateParenAccess(sb, value, genCtx);
+ return;
+ }
+
+ // tag("KEY") — reads LogData tags via LalRuntimeHelper.tagValue().
+ // Only valid when input is LogData.Builder (i.e. no inputType, or inputType
+ // is LogData.Builder). For typed inputs, tag() is not supported — use
+ // parsed.* to access fields on the typed input.
+ if ("tag".equals(value.getFunctionCallName())) {
+ if (value.getFunctionCallArgs().size() != 1
+ || !value.getFunctionCallArgs().get(0).getValue().isStringLiteral()) {
+ throw new IllegalArgumentException(
+ "tag() requires exactly one string literal argument, "
+ + "e.g. tag(\"KEY\")");
+ }
+ if (genCtx.inputType != null
+ && !LALCodegenHelper.LOGDATA_BUILDER_CLASS
+ .isAssignableFrom(genCtx.inputType)) {
+ throw new IllegalArgumentException(
+ "tag() reads LogData tags but the input type is "
+ + genCtx.inputType.getName()
+ + ". Use a json{}/yaml{}/text{} parser, or access "
+ + "typed fields via parsed.* instead.");
+ }
+ sb.append("h.tagValue(\"");
+ final String key = value.getFunctionCallArgs().get(0)
+ .getValue().getSegments().get(0);
+ sb.append(LALCodegenHelper.escapeJava(key)).append("\")");
+ return;
+ }
+
+ // Handle string/number literals
+ if (value.isStringLiteral() && value.getChain().isEmpty()) {
+ sb.append("\"").append(LALCodegenHelper.escapeJava(value.getSegments().get(0)))
+ .append("\"");
+ return;
+ }
+ if (value.isNumberLiteral() && value.getChain().isEmpty()) {
+ final String num = value.getSegments().get(0);
+ if (num.contains(".")) {
+ sb.append("Double.valueOf(").append(num).append(")");
+ } else {
+ sb.append("Integer.valueOf(").append(num).append(")");
+ }
+ return;
+ }
+
+ // Handle ProcessRegistry static calls
+ if (value.isProcessRegistryRef()) {
+ generateProcessRegistryCall(sb, value, genCtx);
+ return;
+ }
+
+ final List<LALScriptModel.ValueAccessSegment> chain = value.getChain();
+
+ // Handle log.X.Y direct proto getter chains
+ if (value.isLogRef()) {
+ generateLogAccess(sb, chain);
+ return;
+ }
+
+ // Handle parsed.X.Y with compile-time type analysis
+ if (value.isParsedRef()) {
+ generateParsedAccess(sb, chain, genCtx);
+ return;
+ }
+
+ // Check for def variable reference
+ if (!value.getSegments().isEmpty()) {
+ final String primaryName = value.getSegments().get(0);
+ final LALClassGenerator.LocalVarInfo localVar =
+ genCtx.localVars.get(primaryName);
+ if (localVar != null) {
+ LALDefCodegen.generateDefVarChain(sb, localVar, chain, genCtx);
+ return;
+ }
+ }
+
+ // No matching codegen path — fail at compile time instead of silently
+ // generating null.
+ if (chain.isEmpty()) {
+ final String desc = value.getFunctionCallName() != null
+ ? "function call '" + value.getFunctionCallName() + "(...)'"
+ : "expression '" + value.getSegments() + "'";
+ throw new IllegalArgumentException(
+ "Cannot resolve " + desc + " — no matching codegen path. "
+ + "Parser type: " + genCtx.parserType
+ + (genCtx.inputType != null
+ ? ", inputType: " + genCtx.inputType.getName() : ""));
+ }
+ // Treat as parsed ref
+ generateParsedAccess(sb, chain, genCtx);
+ }
+
+ // ==================== Parenthesized expression ====================
+
+ /**
+ * Generates code for a parenthesized expression with optional type cast
+ * and method chain.
+ *
+ * <p>LAL: {@code (parsed.component as String).trim()}
+ * <br>Generated: {@code h.toStr(h.mapVal("component")).trim()}
+ */
+ static void generateParenAccess(final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final LALClassGenerator.GenCtx genCtx) {
+ // Generate the inner expression with cast
+ final String castType = value.getParenCast();
+ final StringBuilder inner = new StringBuilder();
+ if (castType != null) {
+ generateCastedValueAccess(inner, value.getParenInner(), castType, genCtx);
+ } else {
+ generateValueAccess(inner, value.getParenInner(), genCtx);
+ }
+
+ // Apply chain segments (methods, fields, index access)
+ String current = inner.toString();
+ for (final LALScriptModel.ValueAccessSegment seg : value.getChain()) {
+ if (seg instanceof LALScriptModel.MethodSegment) {
+ current = appendMethodSegment(current,
+ (LALScriptModel.MethodSegment) seg, genCtx);
+ } else if (seg instanceof LALScriptModel.IndexSegment) {
+ current = current + "["
+ + ((LALScriptModel.IndexSegment) seg).getIndex() + "]";
+ } else if (seg instanceof LALScriptModel.FieldSegment) {
+ final LALScriptModel.FieldSegment fs =
+ (LALScriptModel.FieldSegment) seg;
+ if (fs.isSafeNav()) {
+ current = "(" + current + " == null ? null : "
+ + current + "." + fs.getName() + ")";
+ } else {
+ current = current + "." + fs.getName();
+ }
+ }
+ }
+ sb.append(current);
+ }
+
+ // ==================== Log access (direct proto getters) ====================
+
+ /**
+ * Generate code for {@code log.xxx} field access in LAL scripts.
+ * <ul>
+ * <li>Metadata fields (service, endpoint, layer, timestamp, traceContext)
+ * → {@code h.ctx().metadata().getXxx()}</li>
+ * <li>LogData-only fields (body, tags)
+ * → {@code ((LogData.Builder) h.ctx().input()).getXxx()}</li>
+ * </ul>
+ */
+ static void generateLogAccess(final StringBuilder sb,
+ final List<LALScriptModel.ValueAccessSegment> chain) {
+ if (chain.isEmpty()) {
+ sb.append("h.ctx().input()");
+ return;
+ }
+
+ String current;
+ boolean needsBoxing = false;
+ String boxType = null;
+
+ // Determine root based on first field
+ final LALScriptModel.ValueAccessSegment first = chain.get(0);
+ if (!(first instanceof LALScriptModel.FieldSegment)) {
+ current = LOGDATA_BUILDER_CAST;
+ } else {
+ final String firstName = ((LALScriptModel.FieldSegment) first).getName();
+ if (LALCodegenHelper.METADATA_GETTERS.containsKey(firstName)) {
+ current = "h.ctx().metadata()";
+ } else if (LALCodegenHelper.LOG_GETTERS.containsKey(firstName)) {
+ current = LOGDATA_BUILDER_CAST;
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown log field: log." + firstName
+ + ". Supported metadata fields: "
+ + LALCodegenHelper.METADATA_GETTERS.keySet()
+ + ", LogData fields: "
+ + LALCodegenHelper.LOG_GETTERS.keySet());
+ }
+ }
+
+ for (int i = 0; i < chain.size(); i++) {
+ final LALScriptModel.ValueAccessSegment seg = chain.get(i);
+ if (seg instanceof LALScriptModel.FieldSegment) {
+ final String name = ((LALScriptModel.FieldSegment) seg).getName();
+ if (i == 0 && LALCodegenHelper.METADATA_GETTERS.containsKey(name)) {
+ if ("traceContext".equals(name)) {
+ current = current + ".getTraceContext()";
+ } else {
+ current = current + "."
+ + LALCodegenHelper.METADATA_GETTERS.get(name) + "()";
+ if (LALCodegenHelper.LONG_FIELDS.contains(name)) {
+ needsBoxing = true;
+ boxType = "Long";
+ }
+ }
+ } else if (i == 0 && LALCodegenHelper.LOG_GETTERS.containsKey(name)) {
+ current = current + "."
+ + LALCodegenHelper.LOG_GETTERS.get(name) + "()";
+ } else if (i == 1 && current.endsWith(".getTraceContext()")
+ && LALCodegenHelper.METADATA_TRACE_GETTERS.containsKey(name)) {
+ current = current + "."
+ + LALCodegenHelper.METADATA_TRACE_GETTERS.get(name) + "()";
+ if (LALCodegenHelper.INT_FIELDS.contains(name)) {
+ needsBoxing = true;
+ boxType = "Integer";
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown log field: log." + name
+ + ". Supported metadata fields: "
+ + LALCodegenHelper.METADATA_GETTERS.keySet()
+ + ", traceContext."
+ + LALCodegenHelper.METADATA_TRACE_GETTERS.keySet()
+ + ", LogData fields: "
+ + LALCodegenHelper.LOG_GETTERS.keySet());
+ }
+ } else if (seg instanceof LALScriptModel.MethodSegment) {
+ current = appendMethodSegment(current,
+ (LALScriptModel.MethodSegment) seg, null);
+ }
+ }
+
+ if (needsBoxing) {
+ sb.append(boxType).append(".valueOf(").append(current).append(")");
+ } else {
+ sb.append(current);
+ }
+ }
+
+ // ==================== Parsed access (compile-time typed) ====================
+
+ /**
+ * Generates code for {@code parsed.X.Y} field access, with different
+ * strategies depending on the parser type:
+ *
+ * <pre>{@code
+ * Parser LAL Generated
+ * ────────── ──────────────────────────────── ──────────────────────────
+ * json/yaml parsed.service h.mapVal("service")
+ * json/yaml parsed.client_process.address h.mapVal("client_process", "address")
+ * text parsed.level h.group("level")
+ * none+type parsed?.response?.responseCode ((Type)_p).getResponse()...
+ * none parsed.service h.ctx().metadata().getService()
+ * }</pre>
+ *
+ * <p>For typed proto access ({@code NONE} parser with {@code inputType}),
+ * uses compile-time reflection to generate direct getter chains with
+ * null-safe navigation and local variable caching.
+ */
+ static void generateParsedAccess(
+ final StringBuilder sb,
+ final List<LALScriptModel.ValueAccessSegment> chain,
+ final LALClassGenerator.GenCtx genCtx) {
+ if (chain.isEmpty()) {
+ sb.append("h.ctx().parsed()");
+ return;
+ }
+
+ // Collect leading field segments (stop at method/index)
+ final List<LALScriptModel.FieldSegment> fieldSegments = new ArrayList<>();
+ int methodStart = -1;
+ for (int i = 0; i < chain.size(); i++) {
+ final LALScriptModel.ValueAccessSegment seg = chain.get(i);
+ if (seg instanceof LALScriptModel.FieldSegment) {
+ fieldSegments.add((LALScriptModel.FieldSegment) seg);
+ } else {
+ methodStart = i;
+ break;
+ }
+ }
+
+ final List<String> fieldKeys = new ArrayList<>();
+ for (final LALScriptModel.FieldSegment fs : fieldSegments) {
+ fieldKeys.add(fs.getName());
+ }
+
+ String current;
+ switch (genCtx.parserType) {
+ case JSON:
+ case YAML:
+ current = LALCodegenHelper.generateMapValCall(fieldKeys);
+ break;
+ case TEXT:
+ if (!fieldKeys.isEmpty()) {
+ current = "h.group(\""
+ + LALCodegenHelper.escapeJava(fieldKeys.get(0)) + "\")";
+ } else {
+ current = "h.ctx().parsed()";
+ }
+ break;
+ case NONE:
+ if (genCtx.inputType != null) {
+ current = generateExtraLogAccess(fieldSegments, genCtx.inputType,
+ "_p", true, genCtx);
+ } else {
+ // No parser and no inputType — fall back to LogMetadata
+ current = generateExtraLogAccess(fieldSegments, LogMetadata.class,
+ "h.ctx().metadata()", false, genCtx);
+ }
+ break;
+ default:
+ current = "null";
+ break;
+ }
+
+ // Apply remaining method/index segments
+ if (methodStart >= 0) {
+ for (int i = methodStart; i < chain.size(); i++) {
+ final LALScriptModel.ValueAccessSegment seg = chain.get(i);
+ if (seg instanceof LALScriptModel.MethodSegment) {
+ current = appendMethodSegment(current,
+ (LALScriptModel.MethodSegment) seg, genCtx);
+ } else if (seg instanceof LALScriptModel.IndexSegment) {
+ current = current + "["
+ + ((LALScriptModel.IndexSegment) seg).getIndex() + "]";
+ } else if (seg instanceof LALScriptModel.FieldSegment) {
+ current = current + "."
+ + ((LALScriptModel.FieldSegment) seg).getName();
+ }
+ }
+ }
+
+ sb.append(current);
+ }
+
+ /**
+ * Generates a reflection-based getter chain on a typed root object.
+ * Used for both typed proto access ({@code parsed?.response?.responseCode})
+ * and LogMetadata fallback ({@code parsed.service} without a parser).
+ *
+ * <p>Each getter call is validated at compile time via reflection. Safe
+ * navigation ({@code ?.}) generates null checks. Intermediate results
+ * are cached in local variables ({@code _t0}, {@code _t1}, ...) to avoid
+ * redundant getter calls when the same chain prefix appears multiple times.
+ */
+ static String generateExtraLogAccess(
+ final List<LALScriptModel.FieldSegment> fieldSegments,
+ final Class<?> rootType,
+ final String rootExpr,
+ final boolean rootCanBeNull,
+ final LALClassGenerator.GenCtx genCtx) {
+ genCtx.usedProtoAccess = true;
+
+ if (fieldSegments.isEmpty()) {
+ return rootExpr;
+ }
+
+ final String typeName = rootType.getName();
+ final StringBuilder chainKey = new StringBuilder();
+ String prevVar = rootExpr;
+ Class<?> currentType = rootType;
+ boolean prevCanBeNull = rootCanBeNull;
+
+ for (int i = 0; i < fieldSegments.size(); i++) {
+ final LALScriptModel.FieldSegment seg = fieldSegments.get(i);
+ final String field = seg.getName();
+ String getterName = "get" + Character.toUpperCase(field.charAt(0))
+ + field.substring(1);
+
+ // Apply getter aliases (e.g., traceSegmentId → segmentId on LogMetadata)
+ final String alias = LALCodegenHelper.METADATA_GETTER_ALIASES.get(getterName);
+ if (alias != null) {
+ try {
+ currentType.getMethod(getterName);
+ } catch (NoSuchMethodException ignored) {
+ getterName = alias;
+ }
+ }
+
+ final java.lang.reflect.Method getter;
+ try {
+ getter = currentType.getMethod(getterName);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ "Cannot resolve getter " + currentType.getSimpleName()
+ + "." + getterName + "() for type "
+ + typeName + ". Check the field path in the LAL rule.");
+ }
+ final Class<?> returnType = getter.getReturnType();
+
+ if (chainKey.length() > 0) {
+ chainKey.append(".");
+ }
+ chainKey.append(field);
+ final String key = chainKey.toString();
+ final boolean isLast = i == fieldSegments.size() - 1;
+
+ // Primitive final segment: return inline expression, no variable
+ if (isLast && returnType.isPrimitive()) {
+ final String rawAccess = prevVar + "." + getterName + "()";
+ genCtx.lastResolvedType = returnType;
+ genCtx.lastRawChain = rawAccess;
+ final String boxName = LALCodegenHelper.boxTypeName(returnType);
+ if (seg.isSafeNav() && prevCanBeNull) {
+ genCtx.lastNullChecks = prevVar + " == null";
+ return "(" + prevVar + " == null ? null : "
+ + boxName + ".valueOf(" + rawAccess + "))";
+ } else {
+ genCtx.lastNullChecks = null;
+ return boxName + ".valueOf(" + rawAccess + ")";
+ }
+ }
+
+ // Reuse existing variable (dedup)
+ final String existingVar = genCtx.protoVars.get(key);
+ if (existingVar != null) {
+ prevVar = existingVar;
+ currentType = returnType;
+ prevCanBeNull = true;
+ continue;
+ }
+
+ // Create new local variable declaration
+ final String newVar = "_t" + genCtx.protoVarCounter++;
+ final String returnTypeName = returnType.getName();
+ if (seg.isSafeNav() && prevCanBeNull) {
+ genCtx.protoVarDecls.append(" ").append(returnTypeName)
+ .append(" ").append(newVar).append(" = ")
+ .append(prevVar).append(" == null ? null : ")
+ .append(prevVar).append(".").append(getterName).append("();\n");
+ prevCanBeNull = true;
+ } else {
+ genCtx.protoVarDecls.append(" ").append(returnTypeName)
+ .append(" ").append(newVar).append(" = ")
+ .append(prevVar).append(".").append(getterName).append("();\n");
+ prevCanBeNull = !returnType.isPrimitive();
+ }
+ genCtx.protoVars.put(key, newVar);
+ genCtx.protoLvtVars.add(new String[]{
+ newVar, "L" + returnTypeName.replace('.', '/') + ";"
+ });
+
+ prevVar = newVar;
+ currentType = returnType;
+ }
+
+ // Non-primitive final result — null checks are in declarations
+ genCtx.lastResolvedType = currentType;
+ genCtx.lastRawChain = prevVar;
+ genCtx.lastNullChecks = null;
+ return prevVar;
+ }
+
+ // ==================== ProcessRegistry ====================
+
+ /**
+ * Generates a {@code ProcessRegistry} static method call.
+ *
+ * <p>LAL: {@code ProcessRegistry.generateVirtualRemoteProcess(
+ * parsed.service as String, parsed.serviceInstance as String,
+ * parsed.client_process.address as String)}
+ * <br>Generated: {@code ProcessRegistry.generateVirtualRemoteProcess(
+ * h.toStr(...), h.toStr(...), h.toStr(...))}
+ */
+ static void generateProcessRegistryCall(
+ final StringBuilder sb,
+ final LALScriptModel.ValueAccess value,
+ final LALClassGenerator.GenCtx genCtx) {
+ final List<LALScriptModel.ValueAccessSegment> chain = value.getChain();
+ if (chain.isEmpty()) {
+ sb.append("null");
+ return;
+ }
+ final LALScriptModel.ValueAccessSegment seg = chain.get(0);
+ if (seg instanceof LALScriptModel.MethodSegment) {
+ final LALScriptModel.MethodSegment ms =
+ (LALScriptModel.MethodSegment) seg;
+ sb.append(PROCESS_REGISTRY).append(".")
+ .append(ms.getName()).append("(");
+ final List<LALScriptModel.FunctionArg> args = ms.getArguments();
+ for (int i = 0; i < args.size(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ generateCastedValueAccess(sb,
+ args.get(i).getValue(), args.get(i).getCastType(), genCtx);
+ }
+ sb.append(")");
+ } else {
+ sb.append("null");
+ }
+ }
+
+ // ==================== Utility methods ====================
+
+ /**
+ * Appends a method call segment to the current expression chain.
+ * Handles safe-navigation ({@code ?.method()}) by wrapping in a null
+ * guard. Special-cases {@code ?.toString()} and {@code ?.trim()} to
+ * use helper methods for null-safe behavior.
+ */
+ static String appendMethodSegment(final String current,
+ final LALScriptModel.MethodSegment ms,
+ final LALClassGenerator.GenCtx genCtx) {
+ final String mn = ms.getName();
+ final String args = ms.getArguments().isEmpty()
+ ? "" : generateMethodArgs(ms.getArguments(), genCtx);
+ if (ms.isSafeNav()) {
+ // Special-cased helpers for common safe-nav methods on Object
+ if ("toString".equals(mn)) {
+ return "h.toString(" + current + ")";
+ } else if ("trim".equals(mn)) {
+ return "h.trim(" + current + ")";
+ }
+ // General safe-nav: null guard with ternary
+ return "(" + current + " == null ? null : "
+ + current + "." + mn + "(" + args + "))";
+ } else {
+ return current + "." + mn + "(" + args + ")";
+ }
+ }
+
+ /**
+ * Generates the argument list for a method call. Handles string literals,
+ * number literals, boolean/null literals, and def variable references.
+ */
+ static String generateMethodArgs(
+ final List<LALScriptModel.FunctionArg> args,
+ final LALClassGenerator.GenCtx genCtx) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < args.size(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ final LALScriptModel.FunctionArg arg = args.get(i);
+ final LALScriptModel.ValueAccess va = arg.getValue();
+ if (va.isStringLiteral()) {
+ sb.append("\"").append(LALCodegenHelper.escapeJava(
+ va.getSegments().get(0))).append("\"");
+ } else if (va.isNumberLiteral()) {
+ sb.append(va.getSegments().get(0));
+ } else if (!va.getSegments().isEmpty()) {
+ final String text = va.getSegments().get(0);
+ if ("true".equals(text) || "false".equals(text)
+ || "null".equals(text)) {
+ // Boolean or null literal
+ sb.append(text);
+ } else if (genCtx != null
+ && genCtx.localVars.containsKey(text)) {
+ // Local def variable reference
+ sb.append(genCtx.localVars.get(text).javaVarName);
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown identifier used as method argument: '" + text + "'");
+ }
+ } else {
+ sb.append("null");
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/rt/LalRuntimeHelper.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/rt/LalRuntimeHelper.java
index 56ad4b706a..4ffd24ae1d 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/rt/LalRuntimeHelper.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/rt/LalRuntimeHelper.java
@@ -31,6 +31,7 @@ import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.apm.network.common.v3.KeyStringValuePair;
+import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.server.library.util.StringUtil;
@@ -67,13 +68,17 @@ import org.apache.skywalking.oap.server.library.util.StringUtil;
* // Generated: h.tagValue("LOG_KIND")
* </pre>
*
- * <p><b>4. Log proto data</b> — direct access to {@code LogData.Builder} fields.
+ * <p><b>4. Metadata</b> — direct access to {@code LogMetadata} fields.
* Not accessed through this helper; the compiler generates direct getter chains
- * like {@code h.ctx().log().getService()}.
+ * like {@code h.ctx().metadata().getService()}.
*
- * <p><b>5. ExtraLog proto data</b> — direct access to typed protobuf extraLog.
+ * <p><b>5. LogData body/tags</b> — direct access to {@code LogData.Builder} fields.
* Not accessed through this helper; the compiler generates typed cast + getter
- * chains like {@code ((HTTPAccessLogEntry) h.ctx().extraLog()).getResponse()}.
+ * chains like {@code ((LogData.Builder) h.ctx().input()).getBody()}.
+ *
+ * <p><b>6. Typed input proto data</b> — direct access to typed protobuf input.
+ * Not accessed through this helper; the compiler generates typed cast + getter
+ * chains like {@code ((HTTPAccessLogEntry) h.ctx().input()).getResponse()}.
*
* <h2>Type Conversion Methods</h2>
*
@@ -178,7 +183,7 @@ public final class LalRuntimeHelper {
* </pre>
*/
public String tagValue(final String key) {
- final List dl = ctx.log().getTags().getDataList();
+ final List dl = ((LogData.Builder) ctx.input()).getTags().getDataList();
for (int i = 0; i < dl.size(); i++) {
final KeyStringValuePair kv = (KeyStringValuePair) dl.get(i);
if (key.equals(kv.getKey())) {
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 265b674458..b279744a4c 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
@@ -20,10 +20,10 @@ package org.apache.skywalking.oap.log.analyzer.v2.dsl;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-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.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
@@ -82,11 +82,11 @@ public class DSL {
public void evaluate(final ExecutionContext ctx) {
if (log.isDebugEnabled()) {
- final LogData.Builder logData = ctx.log();
- log.debug("[LAL] rule={}, class={}, service={}, instance={}, endpoint={}, bodyType={}",
+ final LogMetadata metadata = ctx.metadata();
+ log.debug("[LAL] rule={}, class={}, service={}, instance={}, endpoint={}",
ruleName, expression.getClass().getName(),
- logData.getService(), logData.getServiceInstance(),
- logData.getEndpoint(), logData.getBody().getContentCase());
+ metadata.getService(), metadata.getServiceInstance(),
+ metadata.getEndpoint());
}
expression.execute(filterSpec, ctx);
}
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 992d797720..04154077a2 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
@@ -23,10 +23,9 @@ import java.util.Map;
import java.util.Optional;
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.core.source.LALOutputBuilder;
-import org.apache.skywalking.oap.server.core.source.Log;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
/**
* Mutable property storage for a single LAL script execution cycle.
@@ -34,21 +33,22 @@ import org.apache.skywalking.oap.server.core.source.Log;
* <p>A new ExecutionContext is created for each incoming log. It carries all
* per-log state through the compiled LAL pipeline:
* <ul>
- * <li>{@code log} — the incoming {@code LogData.Builder}</li>
+ * <li>{@code metadata} — the {@link LogMetadata} (service, layer, timestamp, trace context)</li>
+ * <li>{@code input} — the raw input object (LogData.Builder for standard logs, typed proto for ALS)</li>
* <li>{@code parsed} — structured data extracted by json/text/yaml parsers</li>
* <li>{@code save}/{@code abort} — control flags set by extractor/sink logic</li>
* <li>{@code metrics_container} — optional list for LAL-extracted metrics (log-MAL)</li>
- * <li>{@code log_container} — optional container for the built {@code Log} source object</li>
+ * <li>{@code dry_run} — when true, sink skips persistence (used by the LAL test tool)</li>
* </ul>
*/
public class ExecutionContext {
- public static final String KEY_LOG = "log";
+ public static final String KEY_INPUT = "input";
+ public static final String KEY_METADATA = "metadata";
public static final String KEY_PARSED = "parsed";
public static final String KEY_SAVE = "save";
public static final String KEY_ABORT = "abort";
public static final String KEY_METRICS_CONTAINER = "metrics_container";
- public static final String KEY_CAPTURE_LOG = "capture_log";
- public static final String KEY_LOG_CONTAINER = "log_container";
+ public static final String KEY_DRY_RUN = "dry_run";
public static final String KEY_OUTPUT = "output";
private final Map<String, Object> properties = new HashMap<>();
@@ -65,32 +65,30 @@ public class ExecutionContext {
return properties.get(name);
}
- public ExecutionContext log(final LogData.Builder log) {
- setProperty(KEY_LOG, log);
+ /**
+ * Initialize from metadata + input.
+ */
+ public ExecutionContext init(final LogMetadata metadata, final Object input) {
+ setProperty(KEY_METADATA, metadata);
+ setProperty(KEY_INPUT, input);
setProperty(KEY_SAVE, true);
setProperty(KEY_ABORT, false);
setProperty(KEY_METRICS_CONTAINER, null);
- setProperty(KEY_CAPTURE_LOG, false);
- setProperty(KEY_LOG_CONTAINER, null);
+ setProperty(KEY_DRY_RUN, false);
setProperty(KEY_OUTPUT, null);
return this;
}
- public ExecutionContext log(final LogData log) {
- return log(log.toBuilder());
+ public LogMetadata metadata() {
+ return (LogMetadata) getProperty(KEY_METADATA);
}
- public LogData.Builder log() {
- return (LogData.Builder) getProperty(KEY_LOG);
- }
-
- public ExecutionContext extraLog(final Object extraLog) {
- parsed().extraLog = extraLog;
- return this;
- }
-
- public Object extraLog() {
- return parsed().getExtraLog();
+ /**
+ * Returns the raw input object. For standard logs this is a {@code LogData.Builder};
+ * for ALS it is the typed proto (e.g., {@code HTTPAccessLogEntry}).
+ */
+ public Object input() {
+ return getProperty(KEY_INPUT);
}
public ExecutionContext parsed(final Matcher parsed) {
@@ -140,22 +138,13 @@ public class ExecutionContext {
return Optional.ofNullable((List<SampleFamily>) getProperty(KEY_METRICS_CONTAINER));
}
- public ExecutionContext captureLog(final boolean capture) {
- setProperty(KEY_CAPTURE_LOG, capture);
- return this;
- }
-
- public boolean shouldCaptureLog() {
- return (boolean) getProperty(KEY_CAPTURE_LOG);
- }
-
- public ExecutionContext logContainer(final Log container) {
- setProperty(KEY_LOG_CONTAINER, container);
+ public ExecutionContext dryRun(final boolean dryRun) {
+ setProperty(KEY_DRY_RUN, dryRun);
return this;
}
- public Optional<Log> logContainer() {
- return Optional.ofNullable((Log) getProperty(KEY_LOG_CONTAINER));
+ public boolean isDryRun() {
+ return (boolean) getProperty(KEY_DRY_RUN);
}
public void setOutput(final Object output) {
@@ -176,8 +165,5 @@ public class ExecutionContext {
@Getter
private Map<String, Object> map;
-
- @Getter
- private Object extraLog;
}
}
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 476a9802f4..dcd72fa052 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
@@ -19,11 +19,9 @@
package org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.filter;
import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.protobuf.TextFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.spec.AbstractSpec;
@@ -37,7 +35,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.LogBuilder;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
import org.slf4j.Logger;
@@ -113,21 +111,21 @@ public class FilterSpec extends AbstractSpec {
/**
* LAL {@code json {}} — parses {@code LogData.body.json.json} into a
* {@code Map<String, Object>} and stores it in {@code ctx.parsed()}.
- * LogData proto fields (service, serviceInstance, endpoint, layer, timestamp)
+ * Metadata fields (service, serviceInstance, endpoint, layer, timestamp)
* are also added to the map via {@code putIfAbsent}, so body values take
- * priority while proto fields serve as fallback — matching v1 Groovy
+ * priority while metadata fields serve as fallback — matching v1 Groovy
* {@code Binding.Parsed.getAt(key)} behavior.
*/
public void json(final ExecutionContext ctx) {
if (ctx.shouldAbort()) {
return;
}
- final LogData.Builder logData = ctx.log();
try {
+ final LogData.Builder logData = (LogData.Builder) ctx.input();
final Map<String, Object> parsed = jsonParser.create().readValue(
logData.getBody().getJson().getJson(), parsedType
);
- addLogDataFields(parsed, logData);
+ addMetadataFields(parsed, ctx.metadata());
ctx.parsed(parsed);
} catch (final Exception e) {
if (jsonParser.abortOnFailure()) {
@@ -139,18 +137,18 @@ public class FilterSpec extends AbstractSpec {
/**
* LAL {@code yaml {}} — parses {@code LogData.body.yaml.yaml} into a
* {@code Map<String, Object>} and stores it in {@code ctx.parsed()}.
- * LogData proto fields are added the same way as {@link #json(ExecutionContext)}.
+ * Metadata fields are added the same way as {@link #json(ExecutionContext)}.
*/
public void yaml(final ExecutionContext ctx) {
if (ctx.shouldAbort()) {
return;
}
- final LogData.Builder logData = ctx.log();
try {
+ final LogData.Builder logData = (LogData.Builder) ctx.input();
final Map<String, Object> parsed = yamlParser.create().load(
logData.getBody().getYaml().getYaml()
);
- addLogDataFields(parsed, logData);
+ addMetadataFields(parsed, ctx.metadata());
ctx.parsed(parsed);
} catch (final Exception e) {
if (yamlParser.abortOnFailure()) {
@@ -171,32 +169,24 @@ public class FilterSpec extends AbstractSpec {
}
private void doSink(final ExecutionContext ctx) {
- final LogData.Builder logData = ctx.log();
- final Optional<Object> extraLog = Optional.ofNullable(ctx.extraLog());
+ if (ctx.isDryRun()) {
+ return;
+ }
+
+ final LogMetadata metadata = ctx.metadata();
+ final Object input = ctx.input();
if (!ctx.shouldSave()) {
if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Log is dropped: {}", TextFormat.shortDebugString(logData));
+ LOGGER.debug("Log is dropped: service={}, layer={}",
+ metadata.getService(), metadata.getLayer());
}
return;
}
- if (ctx.shouldCaptureLog()) {
- sinkListenerFactories.stream()
- .map(LogSinkListenerFactory::create)
- .filter(it -> it instanceof RecordSinkListener)
- .map(it -> it.parse(logData, extraLog, ctx))
- .map(it -> (RecordSinkListener) it)
- .map(RecordSinkListener::getBuilder)
- .filter(it -> it instanceof LogBuilder)
- .map(it -> ((LogBuilder) it).toLog())
- .findFirst()
- .ifPresent(log -> ctx.logContainer(log));
- } else {
- sinkListenerFactories.stream()
- .map(LogSinkListenerFactory::create)
- .forEach(it -> it.parse(logData, extraLog, ctx).build());
- }
+ sinkListenerFactories.stream()
+ .map(LogSinkListenerFactory::create)
+ .forEach(it -> it.parse(metadata, input, ctx).build());
}
// ==================== Direct-access APIs for flattened generated code ====================
@@ -229,18 +219,21 @@ public class FilterSpec extends AbstractSpec {
}
/**
- * Add LogData proto fields to the parsed map so that {@code parsed.service},
+ * Add metadata fields to the parsed map so that {@code parsed.service},
* {@code parsed.serviceInstance}, etc. resolve correctly — matching v1 Groovy
* {@code Binding.Parsed.getAt(key)} fallback behavior.
* Uses {@code putIfAbsent} so body-parsed values take priority.
*/
- private static void addLogDataFields(final Map<String, Object> parsed,
- final LogData.Builder logData) {
- putIfNotEmpty(parsed, "service", logData.getService());
- putIfNotEmpty(parsed, "serviceInstance", logData.getServiceInstance());
- putIfNotEmpty(parsed, "endpoint", logData.getEndpoint());
- putIfNotEmpty(parsed, "layer", logData.getLayer());
- final long ts = logData.getTimestamp();
+ private static void addMetadataFields(final Map<String, Object> parsed,
+ final LogMetadata metadata) {
+ if (metadata == null) {
+ return;
+ }
+ putIfNotEmpty(parsed, "service", metadata.getService());
+ putIfNotEmpty(parsed, "serviceInstance", metadata.getServiceInstance());
+ putIfNotEmpty(parsed, "endpoint", metadata.getEndpoint());
+ putIfNotEmpty(parsed, "layer", metadata.getLayer());
+ final long ts = metadata.getTimestamp();
if (ts > 0) {
parsed.putIfAbsent("timestamp", ts);
}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
index 8d57a0c2ad..a0f07926c2 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/spec/parser/TextParserSpec.java
@@ -39,7 +39,7 @@ public class TextParserSpec extends AbstractParserSpec {
if (ctx.shouldAbort()) {
return;
}
- final LogData.Builder log = ctx.log();
+ final LogData.Builder log = (LogData.Builder) ctx.input();
final Matcher matcher = pattern.matcher(log.getBody().getText().getText());
final boolean matched = matcher.find();
if (matched) {
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/ILogAnalyzerService.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/ILogAnalyzerService.java
index ec951fba50..6aae340848 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/ILogAnalyzerService.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/ILogAnalyzerService.java
@@ -17,8 +17,7 @@
package org.apache.skywalking.oap.log.analyzer.v2.provider.log;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.Service;
/**
@@ -26,10 +25,11 @@ import org.apache.skywalking.oap.server.library.module.Service;
*/
public interface ILogAnalyzerService extends Service {
- void doAnalysis(LogData.Builder log, Optional<Object> extraLog);
-
- default void doAnalysis(LogData logData, Optional<Object> extraLog) {
- doAnalysis(logData.toBuilder(), extraLog);
- }
+ /**
+ * @param metadata uniform metadata (service, layer, timestamp, trace context)
+ * @param input source-specific input object (LogData for standard logs,
+ * HTTPAccessLogEntry for envoy ALS, etc.)
+ */
+ void doAnalysis(LogMetadata metadata, Object input);
}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
index 08f84a9156..19d99541bd 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzer.java
@@ -20,12 +20,11 @@ package org.apache.skywalking.oap.log.analyzer.v2.provider.log;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogAnalysisListener;
import org.apache.skywalking.oap.server.core.UnexpectedException;
import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.util.StringUtil;
/**
@@ -40,9 +39,9 @@ import org.apache.skywalking.oap.server.library.util.StringUtil;
* {@link org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogFilterListener.Factory},
* which returns a listener wrapping all compiled {@link org.apache.skywalking.oap.log.analyzer.v2.dsl.DSL}
* instances for that layer.</li>
- * <li>{@code notifyAnalysisListener(builder, extraLog)} — calls
+ * <li>{@code notifyAnalysisListener(metadata, input)} — calls
* {@link org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogAnalysisListener#parse}
- * on each listener, which binds the log data to the compiled LAL scripts.</li>
+ * on each listener, which binds the metadata and input to the compiled LAL scripts.</li>
* <li>{@code notifyAnalysisListenerToBuild()} — calls
* {@link org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogAnalysisListener#build}
* on each listener, which evaluates the compiled LAL scripts (extractors, sinks).</li>
@@ -58,36 +57,34 @@ public class LogAnalyzer {
private final List<LogAnalysisListener> listeners = new ArrayList<>();
- public void doAnalysis(LogData.Builder builder, Optional<Object> extraLog) {
- if (StringUtil.isEmpty(builder.getService())) {
- // If the service name is empty, the log will be ignored.
+ public void doAnalysis(LogMetadata metadata, final Object input) {
+ if (StringUtil.isEmpty(metadata.getService())) {
log.debug("The log is ignored because the Service name is empty");
return;
}
Layer layer;
- if ("".equals(builder.getLayer())) {
+ if (metadata.getLayer() == null || metadata.getLayer().isEmpty()) {
layer = Layer.GENERAL;
} else {
try {
- layer = Layer.nameOf(builder.getLayer());
+ layer = Layer.nameOf(metadata.getLayer());
} catch (UnexpectedException e) {
- log.warn("The Layer {} is not found, abandon the log.", builder.getLayer());
+ log.warn("The Layer {} is not found, abandon the log.", metadata.getLayer());
return;
}
}
createAnalysisListeners(layer);
- if (builder.getTimestamp() == 0) {
- // If no timestamp, OAP server would use the received timestamp as log's timestamp
- builder.setTimestamp(System.currentTimeMillis());
+ if (metadata.getTimestamp() == 0) {
+ metadata.setTimestamp(System.currentTimeMillis());
}
- notifyAnalysisListener(builder, extraLog);
+ notifyAnalysisListener(metadata, input);
notifyAnalysisListenerToBuild();
}
- private void notifyAnalysisListener(LogData.Builder builder, final Optional<Object> extraLog) {
- listeners.forEach(listener -> listener.parse(builder, extraLog));
+ private void notifyAnalysisListener(final LogMetadata metadata, final Object input) {
+ listeners.forEach(listener -> listener.parse(metadata, input));
}
private void notifyAnalysisListenerToBuild() {
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzerServiceImpl.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzerServiceImpl.java
index 8e1c746bfc..581a2a337d 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzerServiceImpl.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/LogAnalyzerServiceImpl.java
@@ -19,11 +19,10 @@ package org.apache.skywalking.oap.log.analyzer.v2.provider.log;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener.LogAnalysisListenerFactory;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
@RequiredArgsConstructor
@@ -33,9 +32,9 @@ public class LogAnalyzerServiceImpl implements ILogAnalyzerService, ILogAnalysis
private final List<LogAnalysisListenerFactory> analysisListenerFactories = new ArrayList<>();
@Override
- public void doAnalysis(final LogData.Builder log, Optional<Object> extraLog) {
+ public void doAnalysis(final LogMetadata metadata, final Object input) {
LogAnalyzer analyzer = new LogAnalyzer(this);
- analyzer.doAnalysis(log, extraLog);
+ analyzer.doAnalysis(metadata, input);
}
@Override
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogAnalysisListener.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogAnalysisListener.java
index fe299835e9..0154ee76c6 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogAnalysisListener.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/LogAnalysisListener.java
@@ -17,8 +17,7 @@
package org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
/**
* LogAnalysisListener represents the callback when OAP does the log data analysis.
@@ -31,11 +30,10 @@ public interface LogAnalysisListener {
/**
* Parse the raw data from the probe.
- * @param logData log metadata (service, layer, timestamp, etc.)
- * @param extraLog the actual input object whose type matches
- * {@code LALSourceTypeProvider#inputType()} — may be {@code null}
- * for standard logs where LogData is the sole input
+ * @param metadata uniform metadata (service, layer, timestamp, trace context)
+ * @param input source-specific input object (LogData for standard logs,
+ * HTTPAccessLogEntry for envoy ALS, etc.)
* @return {@code this} for chaining.
*/
- LogAnalysisListener parse(LogData.Builder logData, Optional<Object> extraLog);
+ LogAnalysisListener parse(LogMetadata metadata, Object input);
}
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 0d1873ac91..ab16b1f540 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
@@ -22,13 +22,11 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.DSL;
import org.apache.skywalking.oap.log.analyzer.v2.provider.LALConfig;
@@ -38,6 +36,7 @@ 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.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
@@ -76,12 +75,11 @@ public class LogFilterListener implements LogAnalysisListener {
}
@Override
- public LogAnalysisListener parse(final LogData.Builder logData,
- final Optional<Object> extraLog) {
- final LogData logDataSnapshot = logData.build();
+ public LogAnalysisListener parse(final LogMetadata metadata,
+ final Object input) {
contexts = new ArrayList<>(dsls.size());
for (int i = 0; i < dsls.size(); i++) {
- contexts.add(new ExecutionContext().log(logDataSnapshot).extraLog(extraLog.orElse(null)));
+ contexts.add(new ExecutionContext().init(metadata, input));
}
return this;
}
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 afda6f0671..d282156505 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
@@ -17,9 +17,8 @@
package org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
public interface LogSinkListener {
/**
@@ -32,16 +31,5 @@ public interface LogSinkListener {
* Parse the raw data from the probe.
* @return {@code this} for chaining.
*/
- LogSinkListener parse(LogData.Builder logData, Optional<Object> 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 Optional<Object> extraLog,
- final ExecutionContext ctx) {
- return parse(logData, extraLog);
- }
+ LogSinkListener parse(LogMetadata metadata, Object input, ExecutionContext ctx);
}
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 11e741db7d..4d6e465e2b 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
@@ -17,14 +17,13 @@
package org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener;
-import java.util.Optional;
import lombok.SneakyThrows;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.source.LALOutputBuilder;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.core.source.SourceReceiver;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.slf4j.Logger;
@@ -34,7 +33,7 @@ import org.slf4j.LoggerFactory;
* RecordSinkListener forwards LAL output to the persistence layer.
*
* <p>All LAL rules produce an {@link LALOutputBuilder} in the {@link ExecutionContext}.
- * This listener calls {@code init()} to populate standard fields from LogData,
+ * This listener calls {@code init()} to populate standard fields from metadata,
* then {@code complete()} to dispatch the final source(s).
*/
public class RecordSinkListener implements LogSinkListener {
@@ -64,23 +63,14 @@ public class RecordSinkListener implements LogSinkListener {
@Override
@SneakyThrows
- public LogSinkListener parse(final LogData.Builder logData,
- final Optional<Object> extraLog) {
- return this;
- }
-
- @Override
- @SneakyThrows
- public LogSinkListener parse(final LogData.Builder logData,
- final Optional<Object> extraLog,
+ public LogSinkListener parse(final LogMetadata metadata,
+ final Object input,
final ExecutionContext ctx) {
if (ctx == null || !(ctx.output() instanceof LALOutputBuilder)) {
return this;
}
builder = ctx.outputAsBuilder();
- // Pass the input data matching the declared inputType:
- // extraLog (e.g., HTTPAccessLogEntry) when present, otherwise LogData.
- builder.init(logData.build(), extraLog, moduleManager);
+ builder.init(metadata, input, moduleManager);
return this;
}
diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/TrafficSinkListener.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/TrafficSinkListener.java
index 8a9ab2b6f2..9679a3354f 100644
--- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/TrafficSinkListener.java
+++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/log/listener/TrafficSinkListener.java
@@ -17,10 +17,10 @@
package org.apache.skywalking.oap.log.analyzer.v2.provider.log.listener;
-import java.util.Optional;
import lombok.RequiredArgsConstructor;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.util.StringUtil;
import org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig;
import org.apache.skywalking.oap.server.core.CoreModule;
@@ -62,36 +62,37 @@ public class TrafficSinkListener implements LogSinkListener {
}
@Override
- public LogSinkListener parse(final LogData.Builder logData,
- final Optional<Object> extraLog) {
+ public LogSinkListener parse(final LogMetadata metadata,
+ final Object input,
+ final ExecutionContext ctx) {
Layer layer;
- if (StringUtil.isNotEmpty(logData.getLayer())) {
- layer = Layer.valueOf(logData.getLayer());
+ if (StringUtil.isNotEmpty(metadata.getLayer())) {
+ layer = Layer.valueOf(metadata.getLayer());
} else {
layer = Layer.GENERAL;
}
final long timeBucket = TimeBucket.getTimeBucket(System.currentTimeMillis(), DownSampling.Minute);
// to service traffic
- String serviceName = namingControl.formatServiceName(logData.getService());
+ String serviceName = namingControl.formatServiceName(metadata.getService());
String serviceId = IDManager.ServiceID.buildId(serviceName, layer.isNormal());
serviceMeta = new ServiceMeta();
- serviceMeta.setName(namingControl.formatServiceName(logData.getService()));
+ serviceMeta.setName(namingControl.formatServiceName(metadata.getService()));
serviceMeta.setLayer(layer);
serviceMeta.setTimeBucket(timeBucket);
// to service instance traffic
- if (StringUtil.isNotEmpty(logData.getServiceInstance())) {
+ if (StringUtil.isNotEmpty(metadata.getServiceInstance())) {
instanceMeta = new ServiceInstanceUpdate();
instanceMeta.setServiceId(serviceId);
- instanceMeta.setName(namingControl.formatInstanceName(logData.getServiceInstance()));
+ instanceMeta.setName(namingControl.formatInstanceName(metadata.getServiceInstance()));
instanceMeta.setTimeBucket(timeBucket);
}
// to endpoint traffic
- if (StringUtil.isNotEmpty(logData.getEndpoint())) {
+ if (StringUtil.isNotEmpty(metadata.getEndpoint())) {
endpointMeta = new EndpointMeta();
endpointMeta.setServiceName(serviceName);
endpointMeta.setServiceNormal(true);
- endpointMeta.setEndpoint(namingControl.formatEndpointName(serviceName, logData.getEndpoint()));
+ endpointMeta.setEndpoint(namingControl.formatEndpointName(serviceName, metadata.getEndpoint()));
endpointMeta.setTimeBucket(timeBucket);
}
return this;
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 d843b9159c..f97f56c044 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
@@ -23,8 +23,8 @@ import org.apache.skywalking.oap.server.core.analysis.Layer;
* SPI for receiver plugins to declare the input and default output types for
* LAL rules on a specific {@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)}.
+ * <p><b>Input type</b> ({@link #inputType()}): The Java type of the {@code input}
+ * passed to LAL via {@code ILogAnalyzerService.doAnalysis(LogMetadata, Object)}.
* 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.
@@ -42,7 +42,7 @@ import org.apache.skywalking.oap.server.core.analysis.Layer;
* <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>
+ * <li>{@code LogMetadata} fallback if none of the above</li>
* </ol>
*
* <p>Output type resolution order:
@@ -62,7 +62,7 @@ public interface LALSourceTypeProvider {
Layer layer();
/**
- * The Java type passed as {@code extraLog} by the receiver plugin for
+ * The Java type passed as {@code input} by the receiver plugin for
* this layer. The compiler resolves getter chains on this type at
* compile time.
*/
diff --git a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
index 8fa30a6adc..8fa8640ed8 100644
--- a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
+++ b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALClassGeneratorExtractorTest.java
@@ -117,7 +117,7 @@ class LALClassGeneratorExtractorTest extends LALClassGeneratorTestBase {
}
@Test
- void compileNoParserFallsBackToLogDataProto() throws Exception {
+ void compileNoParserFallsBackToLogMetadata() throws Exception {
final String dsl =
"filter {\n"
+ " extractor {\n"
@@ -127,12 +127,12 @@ class LALClassGeneratorExtractorTest extends LALClassGeneratorTestBase {
+ " sink {}\n"
+ "}";
final String source = generator.generateSource(dsl);
- assertTrue(source.contains("h.ctx().log().getService()"),
- "Expected h.ctx().log().getService() but got: " + source);
- assertTrue(source.contains("h.ctx().log().getServiceInstance()"),
- "Expected h.ctx().log().getServiceInstance() but got: " + source);
+ assertTrue(source.contains("h.ctx().metadata().getService()"),
+ "Expected h.ctx().metadata().getService() but got: " + source);
+ assertTrue(source.contains("h.ctx().metadata().getServiceInstance()"),
+ "Expected h.ctx().metadata().getServiceInstance() but got: " + source);
assertFalse(source.contains("_p"),
- "Should NOT have _p variable for LogData fallback but got: " + source);
+ "Should NOT have _p variable for LogMetadata fallback but got: " + source);
compileAndAssert(dsl);
}
@@ -158,7 +158,7 @@ class LALClassGeneratorExtractorTest extends LALClassGeneratorTestBase {
final String fqcn =
"io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry";
assertTrue(source.contains(
- fqcn + " _p = (" + fqcn + ") h.ctx().extraLog()"),
+ fqcn + " _p = (" + fqcn + ") h.ctx().input()"),
"Expected _p local variable for inputType cast but got: " + source);
assertTrue(source.contains("_p.getResponse()"),
"Expected _p.getResponse() via cached variable but got: " + source);
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 38e51069d5..07004a4d6f 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
@@ -39,6 +39,8 @@ 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;
import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
import org.apache.skywalking.oap.server.core.config.NamingControl;
import org.apache.skywalking.oap.server.core.config.group.EndpointNameGrouping;
import org.apache.skywalking.oap.server.core.config.ConfigService;
@@ -173,13 +175,13 @@ class LALExpressionExecutionTest {
if (ruleLayer != null) {
logData.setLayer(ruleLayer);
}
- final ExecutionContext ctx = new ExecutionContext();
- ctx.log(logData);
+ final LogMetadata metadata = LogMetadataUtils.fromLogData(logData);
final Message extraLog = LalLogDataBuilder.buildExtraLog(input);
- if (extraLog != null) {
- ctx.extraLog(extraLog);
- }
+ final Object lalInput = extraLog != null ? extraLog : logData;
+
+ final ExecutionContext ctx = new ExecutionContext();
+ ctx.init(metadata, lalInput);
expr.execute(filterSpec, ctx);
@@ -208,28 +210,28 @@ class LALExpressionExecutionTest {
ruleName + ": shouldAbort mismatch");
break;
case "service":
- assertOutputField(ruleName, outputObj, "service", expected, ctx.log().getService());
+ assertOutputField(ruleName, outputObj, "service", expected, ctx.metadata().getService());
break;
case "instance":
- assertOutputField(ruleName, outputObj, "serviceInstance", expected, ctx.log().getServiceInstance());
+ assertOutputField(ruleName, outputObj, "serviceInstance", expected, ctx.metadata().getServiceInstance());
break;
case "endpoint":
- assertOutputField(ruleName, outputObj, "endpoint", expected, ctx.log().getEndpoint());
+ assertOutputField(ruleName, outputObj, "endpoint", expected, ctx.metadata().getEndpoint());
break;
case "layer":
- assertOutputField(ruleName, outputObj, "layer", expected, ctx.log().getLayer());
+ assertOutputField(ruleName, outputObj, "layer", expected, ctx.metadata().getLayer());
break;
case "timestamp":
- assertOutputField(ruleName, outputObj, "timestamp", expected, String.valueOf(ctx.log().getTimestamp()));
+ assertOutputField(ruleName, outputObj, "timestamp", expected, String.valueOf(ctx.metadata().getTimestamp()));
break;
default:
if (key.startsWith("tag.")) {
final String tagKey = key.substring(4);
if (outputObj instanceof org.apache.skywalking.oap.server.core.source.LogBuilder) {
assertLalTag(ruleName, outputObj, tagKey, expected);
- } else {
+ } else if (ctx.input() instanceof LogData.Builder) {
final List<KeyStringValuePair> tags =
- ctx.log().getTags().getDataList();
+ ((LogData.Builder) ctx.input()).getTags().getDataList();
assertTrue(tags.stream().anyMatch(
t -> tagKey.equals(t.getKey())
&& expected.equals(t.getValue())),
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
index fad33dd9a8..e88ab85368 100644
--- 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
@@ -18,8 +18,6 @@
package org.apache.skywalking.oap.server.core.source;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
/**
@@ -53,13 +51,12 @@ public interface LALOutputBuilder {
* Pre-populate standard fields before custom output fields are applied.
* Called once per log entry.
*
- * @param logData log metadata (service, layer, timestamp, trace context, etc.)
- * @param extraLog optional extra input whose type matches
- * {@code LALSourceTypeProvider#inputType()} for the layer
- * (e.g., {@code HTTPAccessLogEntry} for envoy access logs)
+ * @param metadata uniform metadata (service, layer, timestamp, trace context, etc.)
+ * @param input source-specific input object ({@code LogData} for standard logs,
+ * {@code HTTPAccessLogEntry} for envoy access logs, etc.)
* @param moduleManager module manager for resolving services (e.g., NamingControl)
*/
- void init(LogData logData, Optional<Object> extraLog, ModuleManager moduleManager);
+ void init(LogMetadata metadata, Object input, ModuleManager moduleManager);
/**
* Validate the builder state and dispatch the final output source(s).
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogBuilder.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogBuilder.java
index 96f7f7a294..a49ffc83cb 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogBuilder.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogBuilder.java
@@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
-import java.util.Optional;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
@@ -31,7 +30,6 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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.server.core.Const;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
@@ -95,8 +93,21 @@ public class LogBuilder implements LALOutputBuilder {
}
@Override
- public void init(final LogData logData, final Optional<Object> extraLog,
+ public void init(final LogMetadata metadata, final Object input,
final ModuleManager moduleManager) {
+ ensureInitialized(moduleManager);
+ if (input instanceof LogData) {
+ this.logData = (LogData) input;
+ } else if (input instanceof LogData.Builder) {
+ this.logData = ((LogData.Builder) input).build();
+ }
+ initFromMetadata(metadata);
+ }
+
+ /**
+ * Initialize static services from ModuleManager (once per JVM).
+ */
+ protected void ensureInitialized(final ModuleManager moduleManager) {
if (!INITIALIZED) {
NAMING_CONTROL = moduleManager.find(CoreModule.NAME)
.provider()
@@ -108,33 +119,39 @@ public class LogBuilder implements LALOutputBuilder {
configService.getSearchableLogsTags().split(Const.COMMA));
INITIALIZED = true;
}
- this.logData = logData;
- // Only populate fields that were NOT already set by the LAL extractor.
- // The extractor runs before init(), so extractor values take priority.
+ }
+
+ /**
+ * Populate fields from metadata. Only sets fields not already set by
+ * the LAL extractor (extractor runs before init, so its values take priority).
+ */
+ protected void initFromMetadata(final LogMetadata metadata) {
if (this.service == null) {
- this.service = logData.getService();
+ this.service = metadata.getService();
}
if (this.serviceInstance == null) {
- this.serviceInstance = logData.getServiceInstance();
+ this.serviceInstance = metadata.getServiceInstance();
}
if (this.endpoint == null) {
- this.endpoint = logData.getEndpoint();
+ this.endpoint = metadata.getEndpoint();
}
if (this.layer == null) {
- this.layer = logData.getLayer();
- }
- final TraceContext tc = logData.getTraceContext();
- if (this.traceId == null) {
- this.traceId = tc.getTraceId();
- }
- if (this.segmentId == null) {
- this.segmentId = tc.getTraceSegmentId();
+ this.layer = metadata.getLayer();
}
- if (this.spanId < 0) {
- this.spanId = tc.getSpanId();
+ final LogMetadata.TraceContext tc = metadata.getTraceContext();
+ if (tc != null) {
+ if (this.traceId == null) {
+ this.traceId = tc.getTraceId();
+ }
+ if (this.segmentId == null) {
+ this.segmentId = tc.getTraceSegmentId();
+ }
+ if (this.spanId < 0) {
+ this.spanId = tc.getSpanId();
+ }
}
if (this.timestamp == 0) {
- this.timestamp = logData.getTimestamp();
+ this.timestamp = metadata.getTimestamp();
}
}
@@ -179,21 +196,23 @@ public class LogBuilder implements LALOutputBuilder {
log.setSpanId(spanId);
}
}
- // content
- final LogDataBody body = logData.getBody();
- if (body.hasText()) {
- log.setContentType(ContentType.TEXT);
- log.setContent(body.getText().getText());
- } else if (body.hasYaml()) {
- log.setContentType(ContentType.YAML);
- log.setContent(body.getYaml().getYaml());
- } else if (body.hasJson()) {
- log.setContentType(ContentType.JSON);
- log.setContent(body.getJson().getJson());
- }
- // raw tags from original LogData
- if (logData.getTags().getDataCount() > 0) {
- log.setTagsRawData(logData.getTags().toByteArray());
+ // content (only when input is LogData)
+ if (logData != null) {
+ final LogDataBody body = logData.getBody();
+ if (body.hasText()) {
+ log.setContentType(ContentType.TEXT);
+ log.setContent(body.getText().getText());
+ } else if (body.hasYaml()) {
+ log.setContentType(ContentType.YAML);
+ log.setContent(body.getYaml().getYaml());
+ } else if (body.hasJson()) {
+ log.setContentType(ContentType.JSON);
+ log.setContent(body.getJson().getJson());
+ }
+ // raw tags from original LogData
+ if (logData.getTags().getDataCount() > 0) {
+ log.setTagsRawData(logData.getTags().toByteArray());
+ }
}
// searchable tags from LogData + LAL-added tags
log.getTags().addAll(collectSearchableTags());
@@ -205,11 +224,13 @@ public class LogBuilder implements LALOutputBuilder {
final HashSet<Tag> result = new HashSet<>();
if (SEARCHABLE_TAG_KEYS != null) {
// Tags from original LogData
- logData.getTags().getDataList().forEach(kv -> {
- if (SEARCHABLE_TAG_KEYS.contains(kv.getKey())) {
- addSearchableTag(result, kv.getKey(), kv.getValue());
- }
- });
+ if (logData != null) {
+ logData.getTags().getDataList().forEach(kv -> {
+ if (SEARCHABLE_TAG_KEYS.contains(kv.getKey())) {
+ addSearchableTag(result, kv.getKey(), kv.getValue());
+ }
+ });
+ }
// Tags added by LAL extractor
for (final String[] kv : lalTags) {
if (SEARCHABLE_TAG_KEYS.contains(kv[0])) {
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadata.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadata.java
new file mode 100644
index 0000000000..5c029fb74a
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadata.java
@@ -0,0 +1,52 @@
+/*
+ * 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 lombok.Builder;
+import lombok.Data;
+
+/**
+ * Uniform metadata carrier for log analysis. Receivers build this from their
+ * native source (LogData proto for standard logs, ALS context for envoy, etc.).
+ *
+ * <p>For building from {@code LogData} protobuf, see {@link LogMetadataUtils}.
+ */
+@Data
+@Builder
+public class LogMetadata {
+ private String service;
+ private String serviceInstance;
+ private String endpoint;
+ private String layer;
+ private long timestamp;
+ @Builder.Default
+ private TraceContext traceContext = TraceContext.EMPTY;
+
+ @Data
+ @Builder
+ public static class TraceContext {
+ static final TraceContext EMPTY =
+ TraceContext.builder().traceId("").traceSegmentId("").spanId(0).build();
+
+ @Builder.Default
+ private String traceId = "";
+ @Builder.Default
+ private String traceSegmentId = "";
+ private int spanId;
+ }
+}
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadataUtils.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadataUtils.java
new file mode 100644
index 0000000000..d0420fee04
--- /dev/null
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source/LogMetadataUtils.java
@@ -0,0 +1,71 @@
+/*
+ * 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.apm.network.logging.v3.TraceContext;
+
+/**
+ * Utility for building {@link LogMetadata} from protobuf {@link LogData}.
+ */
+public final class LogMetadataUtils {
+
+ private LogMetadataUtils() {
+ }
+
+ /**
+ * Build LogMetadata from a LogData protobuf message.
+ */
+ public static LogMetadata fromLogData(final LogData logData) {
+ final LogMetadata.TraceContext tc = buildTraceContext(logData.getTraceContext());
+ return LogMetadata.builder()
+ .service(logData.getService())
+ .serviceInstance(logData.getServiceInstance())
+ .endpoint(logData.getEndpoint())
+ .layer(logData.getLayer())
+ .timestamp(logData.getTimestamp())
+ .traceContext(tc)
+ .build();
+ }
+
+ /**
+ * Build LogMetadata from a LogData.Builder without building a full LogData.
+ */
+ public static LogMetadata fromLogData(final LogData.Builder builder) {
+ final LogMetadata.TraceContext tc = buildTraceContext(builder.getTraceContext());
+ return LogMetadata.builder()
+ .service(builder.getService())
+ .serviceInstance(builder.getServiceInstance())
+ .endpoint(builder.getEndpoint())
+ .layer(builder.getLayer())
+ .timestamp(builder.getTimestamp())
+ .traceContext(tc)
+ .build();
+ }
+
+ private static LogMetadata.TraceContext buildTraceContext(final TraceContext ctxProto) {
+ if (ctxProto == null || ctxProto == TraceContext.getDefaultInstance()) {
+ return LogMetadata.TraceContext.EMPTY;
+ }
+ return LogMetadata.TraceContext.builder()
+ .traceId(ctxProto.getTraceId())
+ .traceSegmentId(ctxProto.getTraceSegmentId())
+ .spanId(ctxProto.getSpanId())
+ .build();
+ }
+}
diff --git a/oap-server/server-fetcher-plugin/kafka-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/analyzer/agent/kafka/provider/handler/LogHandler.java b/oap-server/server-fetcher-plugin/kafka-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/analyzer/agent/kafka/provider/handler/LogHandler.java
index c39925fad5..18e1ca6351 100644
--- a/oap-server/server-fetcher-plugin/kafka-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/analyzer/agent/kafka/provider/handler/LogHandler.java
+++ b/oap-server/server-fetcher-plugin/kafka-fetcher-plugin/src/main/java/org/apache/skywalking/oap/server/analyzer/agent/kafka/provider/handler/LogHandler.java
@@ -20,8 +20,8 @@ package org.apache.skywalking.oap.server.analyzer.agent.kafka.provider.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.utils.Bytes;
-import java.util.Optional;
import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
import org.apache.skywalking.oap.server.analyzer.agent.kafka.module.KafkaFetcherConfig;
@@ -71,8 +71,8 @@ public class LogHandler extends AbstractKafkaHandler {
@Override
public void handle(final ConsumerRecord<String, Bytes> record) {
try (HistogramMetrics.Timer ignore = histogram.createTimer()) {
- LogData logData = parseConsumerRecord(record);
- logAnalyzerService.doAnalysis(logData, Optional.empty());
+ LogData.Builder builder = parseConsumerRecord(record).toBuilder();
+ logAnalyzerService.doAnalysis(LogMetadataUtils.fromLogData(builder), builder);
} catch (Exception e) {
errorCounter.inc();
log.error(e.getMessage(), e);
diff --git a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/LogTestQuery.java b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/LogTestQuery.java
index 8e3b481208..461fd62080 100644
--- a/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/LogTestQuery.java
+++ b/oap-server/server-query-plugin/query-graphql-plugin/src/main/java/org/apache/skywalking/oap/query/graphql/resolver/LogTestQuery.java
@@ -29,6 +29,7 @@ import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.apm.network.logging.v3.LogTags;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.DSL;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
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;
@@ -39,6 +40,8 @@ import org.apache.skywalking.oap.query.graphql.type.Metrics;
import org.apache.skywalking.oap.server.core.analysis.IDManager;
import org.apache.skywalking.oap.server.core.query.type.KeyValue;
import org.apache.skywalking.oap.server.core.query.type.Log;
+import org.apache.skywalking.oap.server.core.source.LogBuilder;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
@@ -72,17 +75,21 @@ public class LogTestQuery implements GraphQLQueryResolver {
final LogData.Builder log = LogData.newBuilder();
ProtoBufJsonUtils.fromJSON(request.getLog(), log);
- ctx.log(log);
+ final LogMetadata metadata = LogMetadataUtils.fromLogData(log);
+ ctx.init(metadata, log);
- ctx.captureLog(true);
+ ctx.dryRun(true);
ctx.metricsContainer(new ArrayList<>());
dsl.evaluate(ctx);
final LogTestResponse.LogTestResponseBuilder builder = LogTestResponse.builder();
- ctx.logContainer().ifPresent(it -> {
- final Log l = new Log();
+ if (ctx.shouldSave() && ctx.outputAsBuilder() instanceof LogBuilder) {
+ final LogBuilder logBuilder = (LogBuilder) ctx.outputAsBuilder();
+ logBuilder.init(metadata, log, moduleManager);
+ final org.apache.skywalking.oap.server.core.source.Log it = logBuilder.toLog();
+ final Log l = new Log();
if (isNotBlank(it.getServiceId())) {
l.setServiceName(IDManager.ServiceID.analysisId(it.getServiceId()).getName());
}
@@ -113,9 +120,8 @@ public class LogTestQuery implements GraphQLQueryResolver {
// ignore
}
}
-
builder.log(l);
- });
+ }
ctx.metricsContainer().ifPresent(it -> {
final List<Metrics> samples =
it.stream()
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilder.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilder.java
index 60ba08618c..9478939481 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilder.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilder.java
@@ -19,12 +19,11 @@
package org.apache.skywalking.oap.server.receiver.envoy.persistence;
import com.google.protobuf.Message;
-import java.util.Optional;
import lombok.SneakyThrows;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.oap.server.core.query.type.ContentType;
import org.apache.skywalking.oap.server.core.source.Log;
import org.apache.skywalking.oap.server.core.source.LogBuilder;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
@@ -35,9 +34,9 @@ import org.apache.skywalking.oap.server.library.util.ProtoBufJsonUtils;
* as JSON content. The serialization is deferred to {@link #toLog()} so that
* it only happens when the log is actually persisted (after LAL filtering).
*
- * <p>The {@link #init} method stores the extra log entry (e.g.,
- * {@code HTTPAccessLogEntry}) for JSON serialization, then delegates to
- * the base class for standard field population from {@code LogData}.
+ * <p>The {@link #init} method stores the access log entry for JSON
+ * serialization, then delegates to the base class for metadata-only
+ * field population (no LogData in the envoy path).
*/
public class EnvoyAccessLogBuilder extends LogBuilder {
public static final String NAME = "EnvoyAccessLog";
@@ -50,10 +49,13 @@ public class EnvoyAccessLogBuilder extends LogBuilder {
}
@Override
- public void init(final LogData logData, final Optional<Object> extraLog,
+ public void init(final LogMetadata metadata, final Object input,
final ModuleManager moduleManager) {
- extraLog.ifPresent(entry -> this.accessLogEntry = entry);
- super.init(logData, extraLog, moduleManager);
+ if (input != null) {
+ this.accessLogEntry = input;
+ }
+ ensureInitialized(moduleManager);
+ initFromMetadata(metadata);
}
@Override
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/LogsPersistence.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/LogsPersistence.java
index 09437cbe3b..f2ac92c15f 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/LogsPersistence.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/LogsPersistence.java
@@ -20,13 +20,12 @@ package org.apache.skywalking.oap.server.receiver.envoy.persistence;
import io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry;
import io.envoyproxy.envoy.service.accesslog.v3.StreamAccessLogsMessage;
-import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.apm.network.servicemesh.v3.HTTPServiceMeshMetric;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
import org.apache.skywalking.oap.server.receiver.envoy.EnvoyMetricReceiverConfig;
@@ -66,8 +65,16 @@ public class LogsPersistence implements ALSHTTPAnalysis {
return result;
}
- final LogData logData = convertToLogData(entry, result);
- logAnalyzerService.doAnalysis(logData, Optional.of(entry));
+ final ServiceMetaInfo service = result.getService();
+ final HTTPServiceMeshMetric.Builder metrics =
+ new LogEntry2MetricsAdapter(entry, null, null).adaptCommonPart();
+ final LogMetadata metadata = LogMetadata.builder()
+ .service(service.getServiceName())
+ .serviceInstance(service.getServiceInstanceName())
+ .timestamp(metrics.getEndTime())
+ .layer(Layer.MESH.name())
+ .build();
+ logAnalyzerService.doAnalysis(metadata, entry);
} catch (final Exception e) {
log.error("Failed to persist Envoy access log", e);
}
@@ -78,19 +85,4 @@ public class LogsPersistence implements ALSHTTPAnalysis {
public Role identify(final StreamAccessLogsMessage.Identifier alsIdentifier, final Role prev) {
return prev;
}
-
- public LogData convertToLogData(final HTTPAccessLogEntry logEntry, final Result result) {
- final ServiceMetaInfo service = result.getService();
-
- final HTTPServiceMeshMetric.Builder metrics =
- new LogEntry2MetricsAdapter(logEntry, null, null).adaptCommonPart();
-
- return LogData
- .newBuilder()
- .setService(service.getServiceName())
- .setServiceInstance(service.getServiceInstanceName())
- .setTimestamp(metrics.getEndTime())
- .setLayer(Layer.MESH.name())
- .build();
- }
}
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/TCPLogsPersistence.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/TCPLogsPersistence.java
index ac66002d1f..be942164d8 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/TCPLogsPersistence.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/TCPLogsPersistence.java
@@ -18,12 +18,11 @@
package org.apache.skywalking.oap.server.receiver.envoy.persistence;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.apm.network.servicemesh.v3.TCPServiceMeshMetric;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
import org.apache.skywalking.oap.server.core.analysis.Layer;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.module.ModuleStartException;
import org.apache.skywalking.oap.server.receiver.envoy.EnvoyMetricReceiverConfig;
@@ -66,8 +65,16 @@ public class TCPLogsPersistence implements TCPAccessLogAnalyzer {
return result;
}
- final LogData logData = convertToLogData(entry, result);
- logAnalyzerService.doAnalysis(logData, Optional.of(entry));
+ final ServiceMetaInfo service = result.getService();
+ final TCPServiceMeshMetric.Builder metrics =
+ new TCPLogEntry2MetricsAdapter(entry, null, null).adaptCommonPart();
+ final LogMetadata metadata = LogMetadata.builder()
+ .service(service.getServiceName())
+ .serviceInstance(service.getServiceInstanceName())
+ .timestamp(metrics.getEndTime())
+ .layer(Layer.MESH.name())
+ .build();
+ logAnalyzerService.doAnalysis(metadata, entry);
} catch (final Exception e) {
log.error("Failed to persist Envoy access log", e);
}
@@ -78,19 +85,4 @@ public class TCPLogsPersistence implements TCPAccessLogAnalyzer {
public Role identify(final StreamAccessLogsMessage.Identifier alsIdentifier, final Role prev) {
return prev;
}
-
- public LogData convertToLogData(final TCPAccessLogEntry logEntry, final Result result) {
- final ServiceMetaInfo service = result.getService();
-
- final TCPServiceMeshMetric.Builder metrics =
- new TCPLogEntry2MetricsAdapter(logEntry, null, null).adaptCommonPart();
-
- return LogData
- .newBuilder()
- .setService(service.getServiceName())
- .setServiceInstance(service.getServiceInstanceName())
- .setTimestamp(metrics.getEndTime())
- .setLayer(Layer.MESH.name())
- .build();
- }
}
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilderTest.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilderTest.java
index 7bc3b0f89d..2c3b39536f 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilderTest.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAccessLogBuilderTest.java
@@ -23,8 +23,7 @@ import io.envoyproxy.envoy.data.accesslog.v3.AccessLogCommon;
import io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry;
import io.envoyproxy.envoy.data.accesslog.v3.HTTPResponseProperties;
import java.util.Collections;
-import java.util.Optional;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALClassGenerator;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression;
@@ -87,11 +86,11 @@ class EnvoyAccessLogBuilderTest {
builder.setService("test-svc");
builder.setTimestamp(1609459200000L);
- final LogData logData = LogData.newBuilder()
- .setService("test-svc")
- .setTimestamp(1609459200000L)
+ final LogMetadata metadata = LogMetadata.builder()
+ .service("test-svc")
+ .timestamp(1609459200000L)
.build();
- builder.init(logData, Optional.of(entry), moduleManager);
+ builder.init(metadata, entry, moduleManager);
final Log log = builder.toLog();
@@ -109,12 +108,12 @@ class EnvoyAccessLogBuilderTest {
builder.setService("test-svc");
builder.setTimestamp(1609459200000L);
- final LogData logData = LogData.newBuilder()
- .setService("test-svc")
- .setTimestamp(1609459200000L)
+ final LogMetadata metadata = LogMetadata.builder()
+ .service("test-svc")
+ .timestamp(1609459200000L)
.build();
// Pass a default (empty) entry — no response code, so toLog() serializes empty JSON
- builder.init(logData, Optional.of(HTTPAccessLogEntry.getDefaultInstance()), moduleManager);
+ builder.init(metadata, HTTPAccessLogEntry.getDefaultInstance(), moduleManager);
final Log log = builder.toLog();
@@ -159,12 +158,13 @@ class EnvoyAccessLogBuilderTest {
final LalExpression expr = generator.compile(dsl);
- // Build LogData with service/instance (simulates what LogsPersistence creates)
- final LogData.Builder logData = LogData.newBuilder()
- .setService("envoy-test-svc")
- .setServiceInstance("envoy-test-instance")
- .setTimestamp(1609459200000L)
- .setLayer("MESH");
+ // Build LogMetadata (simulates what LogsPersistence creates)
+ final LogMetadata metadata = LogMetadata.builder()
+ .service("envoy-test-svc")
+ .serviceInstance("envoy-test-instance")
+ .timestamp(1609459200000L)
+ .layer("MESH")
+ .build();
// Build HTTPAccessLogEntry with response code 503
final HTTPAccessLogEntry entry = HTTPAccessLogEntry.newBuilder()
@@ -177,8 +177,7 @@ class EnvoyAccessLogBuilderTest {
// Execute
final FilterSpec filterSpec = buildFilterSpec();
final ExecutionContext ctx = new ExecutionContext();
- ctx.log(logData);
- ctx.extraLog(entry);
+ ctx.init(metadata, entry);
expr.execute(filterSpec, ctx);
// Verify output is EnvoyAccessLogBuilder
@@ -196,7 +195,7 @@ class EnvoyAccessLogBuilderTest {
when(cs.getSearchableLogsTags()).thenReturn("status.code,svc");
// Reset static initialized flag so the new config takes effect
resetLogBuilderState();
- output.init(logData.build(), Optional.of(entry), testMm);
+ output.init(metadata, entry, testMm);
final Log log = output.toLog();
assertTrue(log.getTags().stream().anyMatch(
diff --git a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAlsLalTest.java b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAlsLalTest.java
index 9a9cb6a96e..050fdae597 100644
--- a/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAlsLalTest.java
+++ b/oap-server/server-receiver-plugin/envoy-metrics-receiver-plugin/src/test/java/org/apache/skywalking/oap/server/receiver/envoy/persistence/EnvoyAlsLalTest.java
@@ -26,9 +26,8 @@ import io.envoyproxy.envoy.data.accesslog.v3.HTTPAccessLogEntry;
import io.envoyproxy.envoy.data.accesslog.v3.HTTPResponseProperties;
import java.lang.reflect.Field;
import java.util.Collections;
-import java.util.Optional;
import javassist.ClassPool;
-import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadata;
import org.apache.skywalking.oap.log.analyzer.v2.compiler.LALClassGenerator;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext;
import org.apache.skywalking.oap.log.analyzer.v2.dsl.LalExpression;
@@ -306,15 +305,15 @@ class EnvoyAlsLalTest {
private ExecutionContext execute(
final LalExpression expr,
final HTTPAccessLogEntry entry) throws Exception {
- final LogData.Builder logData = LogData.newBuilder()
- .setService("als-test-svc")
- .setTimestamp(1609459200000L)
- .setLayer("MESH");
+ final LogMetadata metadata = LogMetadata.builder()
+ .service("als-test-svc")
+ .timestamp(1609459200000L)
+ .layer("MESH")
+ .build();
final FilterSpec filterSpec = buildFilterSpec();
final ExecutionContext ctx = new ExecutionContext();
- ctx.log(logData);
- ctx.extraLog(entry);
+ ctx.init(metadata, entry);
expr.execute(filterSpec, ctx);
return ctx;
}
@@ -332,7 +331,7 @@ class EnvoyAlsLalTest {
resetLogBuilderState();
final ModuleManager tagMm = buildCoreModuleManager(
String.join(",", searchableTagKeys));
- output.init(ctx.log().build(), Optional.of(entry), tagMm);
+ output.init(ctx.metadata(), entry, tagMm);
return output.toLog();
}
diff --git a/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryLogHandler.java b/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryLogHandler.java
index b2b526eef8..9bebac1077 100644
--- a/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryLogHandler.java
+++ b/oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/otel/otlp/OpenTelemetryLogHandler.java
@@ -25,7 +25,6 @@ import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse;
import io.opentelemetry.proto.collector.logs.v1.LogsServiceGrpc;
import io.opentelemetry.proto.common.v1.KeyValue;
import io.opentelemetry.proto.logs.v1.LogRecord;
-import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.Getter;
@@ -34,6 +33,7 @@ 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.LogTags;
import org.apache.skywalking.apm.network.logging.v3.TextLog;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
import org.apache.skywalking.oap.server.core.server.GRPCHandlerRegister;
@@ -122,16 +122,15 @@ public class OpenTelemetryLogHandler
private void doAnalysisQuietly(String service, String layer, String serviceInstance, LogRecord logRecord) {
try {
- logAnalyzerService().doAnalysis(
- LogData
- .newBuilder()
- .setService(service)
- .setServiceInstance(serviceInstance)
- .setTimestamp(logRecord.getTimeUnixNano() / 1_000_000)
- .setTags(buildTags(logRecord))
- .setBody(buildBody(logRecord))
- .setLayer(layer),
- Optional.empty());
+ final LogData.Builder builder = LogData
+ .newBuilder()
+ .setService(service)
+ .setServiceInstance(serviceInstance)
+ .setTimestamp(logRecord.getTimeUnixNano() / 1_000_000)
+ .setTags(buildTags(logRecord))
+ .setBody(buildBody(logRecord))
+ .setLayer(layer);
+ logAnalyzerService().doAnalysis(LogMetadataUtils.fromLogData(builder), builder);
} catch (Exception e) {
log.error("Failed to analyze logs", e);
}
diff --git a/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/grpc/LogReportServiceGrpcHandler.java b/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/grpc/LogReportServiceGrpcHandler.java
index d243142b60..4d7563fd09 100644
--- a/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/grpc/LogReportServiceGrpcHandler.java
+++ b/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/grpc/LogReportServiceGrpcHandler.java
@@ -18,11 +18,11 @@
package org.apache.skywalking.oap.server.receiver.log.provider.handler.grpc;
import io.grpc.stub.StreamObserver;
-import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.network.common.v3.Commands;
import org.apache.skywalking.apm.network.logging.v3.LogData;
import org.apache.skywalking.apm.network.logging.v3.LogReportServiceGrpc;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
import org.apache.skywalking.oap.server.library.util.StringUtil;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
@@ -93,7 +93,7 @@ public class LogReportServiceGrpcHandler extends LogReportServiceGrpc.LogReportS
try {
LogData.Builder builder = logData.toBuilder();
setServiceName(builder);
- logAnalyzerService.doAnalysis(builder, Optional.empty());
+ logAnalyzerService.doAnalysis(LogMetadataUtils.fromLogData(builder), builder);
} catch (Exception e) {
errorCounter.inc();
log.error(e.getMessage(), e);
diff --git a/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/rest/LogReportServiceHTTPHandler.java b/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/rest/LogReportServiceHTTPHandler.java
index 32a3a761fc..bcb64282ec 100644
--- a/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/rest/LogReportServiceHTTPHandler.java
+++ b/oap-server/server-receiver-plugin/skywalking-log-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/log/provider/handler/rest/LogReportServiceHTTPHandler.java
@@ -19,10 +19,10 @@ package org.apache.skywalking.oap.server.receiver.log.provider.handler.rest;
import com.linecorp.armeria.server.annotation.Post;
import java.util.List;
-import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.network.common.v3.Commands;
import org.apache.skywalking.apm.network.logging.v3.LogData;
+import org.apache.skywalking.oap.server.core.source.LogMetadataUtils;
import org.apache.skywalking.oap.log.analyzer.v2.module.LogAnalyzerModule;
import org.apache.skywalking.oap.log.analyzer.v2.provider.log.ILogAnalyzerService;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
@@ -64,7 +64,10 @@ public class LogReportServiceHTTPHandler {
@Post("/v3/logs")
public Commands collectLogs(final List<LogData> logs) {
try (final HistogramMetrics.Timer ignored = histogram.createTimer()) {
- logs.forEach(it -> logAnalyzerService.doAnalysis(it, Optional.empty()));
+ logs.forEach(it -> {
+ final LogData.Builder builder = it.toBuilder();
+ logAnalyzerService.doAnalysis(LogMetadataUtils.fromLogData(builder), builder);
+ });
return Commands.newBuilder().build();
} catch (final Throwable e) {
errorCounter.inc();
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 b8178394b2..1ff1b92e79 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
@@ -239,12 +239,14 @@ public class LalBenchmark {
public void executeV2(final Blackhole bh) {
for (int i = 0; i < v2Exprs.size(); i++) {
try {
+ final LogData logData = testLogs.get(i);
+ final Message extraLog = extraLogs.get(i);
+ final org.apache.skywalking.oap.server.core.source.LogMetadata metadata =
+ org.apache.skywalking.oap.server.core.source.LogMetadataUtils.fromLogData(logData);
+ final Object input = extraLog != null ? extraLog : logData.toBuilder();
final org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext ctx =
- new org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext()
- .log(testLogs.get(i));
- if (extraLogs.get(i) != null) {
- ctx.extraLog(extraLogs.get(i));
- }
+ new org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext();
+ ctx.init(metadata, input);
v2Exprs.get(i).execute(v2FilterSpec, ctx);
bh.consume(ctx);
} catch (Exception ignored) {
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 70b6a104d4..437dfdd54b 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
@@ -165,12 +165,13 @@ class LalComparisonTest {
final String v2CompileError,
final boolean v2Only) throws Exception {
- final LogData testLog;
+ final LogData.Builder testLogBuilder;
if (inputData != null) {
- testLog = LalLogDataBuilder.buildLogData(inputData).build();
+ testLogBuilder = LalLogDataBuilder.buildLogData(inputData);
} else {
- testLog = LalLogDataBuilder.buildSyntheticLogData(dsl);
+ testLogBuilder = LalLogDataBuilder.buildSyntheticLogData(dsl).toBuilder();
}
+ final LogData testLog = testLogBuilder.build();
// Build proto extraLog from input data if available
final Message extraLog = inputData != null
@@ -215,10 +216,11 @@ class LalComparisonTest {
v2Manager, new LogAnalyzerModuleConfig());
disableSinkListenersOnSpec(v2FilterSpec);
- v2Ctx = new org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext().log(testLog);
- if (extraLog != null) {
- v2Ctx.extraLog(extraLog);
- }
+ final org.apache.skywalking.oap.server.core.source.LogMetadata v2Metadata =
+ org.apache.skywalking.oap.server.core.source.LogMetadataUtils.fromLogData(testLog);
+ final Object v2Input = extraLog != null ? extraLog : testLogBuilder;
+ v2Ctx = new org.apache.skywalking.oap.log.analyzer.v2.dsl.ExecutionContext();
+ v2Ctx.init(v2Metadata, v2Input);
v2Expr.execute(v2FilterSpec, v2Ctx);
} catch (Exception e) {
@@ -247,25 +249,26 @@ class LalComparisonTest {
// If the extractor sets a field, v1 puts it on LogData, v2 puts it on LogBuilder.
// If the extractor doesn't set a field, both keep the original LogData value.
final Object v2Output = v2Ctx.output();
+ final org.apache.skywalking.oap.server.core.source.LogMetadata v2Meta = v2Ctx.metadata();
if (v2Output instanceof org.apache.skywalking.oap.server.core.source.LogBuilder) {
final org.apache.skywalking.oap.server.core.source.LogBuilder v2Builder =
(org.apache.skywalking.oap.server.core.source.LogBuilder) v2Output;
assertV2Field(testName, "service", v1Log.getService(),
- getFieldValue(v2Builder, "service", null), v2Ctx.log().getService());
+ getFieldValue(v2Builder, "service", null), v2Meta.getService());
assertV2Field(testName, "serviceInstance", v1Log.getServiceInstance(),
getFieldValue(v2Builder, "serviceInstance", null),
- v2Ctx.log().getServiceInstance());
+ v2Meta.getServiceInstance());
assertV2Field(testName, "endpoint", v1Log.getEndpoint(),
- getFieldValue(v2Builder, "endpoint", null), v2Ctx.log().getEndpoint());
+ getFieldValue(v2Builder, "endpoint", null), v2Meta.getEndpoint());
assertV2Field(testName, "layer", v1Log.getLayer(),
- getFieldValue(v2Builder, "layer", null), v2Ctx.log().getLayer());
+ getFieldValue(v2Builder, "layer", null), v2Meta.getLayer());
final long v2Ts = v2Builder.getTimestamp();
- assertEquals(v1Log.getTimestamp(), v2Ts != 0 ? v2Ts : v2Ctx.log().getTimestamp(),
+ assertEquals(v1Log.getTimestamp(), v2Ts != 0 ? v2Ts : v2Meta.getTimestamp(),
testName + ": timestamp mismatch");
// Compare tags: v1 stores in LogData.tags, v2 stores in LogBuilder.lalTags
compareV1TagsToV2Builder(testName, v1Log, v2Builder);
- } else {
- final LogData.Builder v2Log = v2Ctx.log();
+ } else if (v2Ctx.input() instanceof LogData.Builder) {
+ final LogData.Builder v2Log = (LogData.Builder) v2Ctx.input();
assertEquals(v1Log.getService(), v2Log.getService(),
testName + ": service mismatch");
assertEquals(v1Log.getServiceInstance(), v2Log.getServiceInstance(),
@@ -278,6 +281,16 @@ class LalComparisonTest {
testName + ": timestamp mismatch");
assertEquals(v1Log.getTags(), v2Log.getTags(),
testName + ": tags mismatch");
+ } else {
+ // ALS case: no LogData, compare metadata
+ assertEquals(v1Log.getService(), v2Meta.getService(),
+ testName + ": service mismatch");
+ assertEquals(v1Log.getServiceInstance(), v2Meta.getServiceInstance(),
+ testName + ": serviceInstance mismatch");
+ assertEquals(v1Log.getLayer(), v2Meta.getLayer(),
+ testName + ": layer mismatch");
+ assertEquals(v1Log.getTimestamp(), v2Meta.getTimestamp(),
+ testName + ": timestamp mismatch");
}
}
@@ -287,7 +300,8 @@ class LalComparisonTest {
final Map<String, Object> expect =
(Map<String, Object>) inputData.get("expect");
if (expect != null) {
- final LogData.Builder v2Log = v2Ctx.log();
+ final LogData.Builder v2Log = v2Ctx.input() instanceof LogData.Builder
+ ? (LogData.Builder) v2Ctx.input() : null;
validateExpected(testName, v2Ctx, v2Log, expect);
}
}