wu-sheng commented on code in PR #13737:
URL: https://github.com/apache/skywalking/pull/13737#discussion_r2912409621


##########
docs/en/concepts-and-designs/lal.md:
##########
@@ -145,6 +145,89 @@ filter {
 Extractors aim to extract metadata from the logs. The metadata can be a 
service name, a service instance name, an
 endpoint name, or even a trace ID, all of which can be associated with the 
existing traces and metrics.
 
+#### Local variables (`def`)
+
+You can use `def` to declare local variables in the extractor (or at the 
filter level). This is useful when an
+expression is reused multiple times, or when you want to break a long chain 
into readable steps.
+
+The syntax is:
+
+```
+def variableName = expression
+def variableName = expression as TypeName
+```
+
+The variable type is inferred from the initializer expression at compile time. 
`def` is not limited to JSON — it works
+with any value access expression whose type is resolvable on the classpath, 
including protobuf getter chains,
+`log.*` fields, and Gson JSON method chains. Subsequent method calls on the 
variable are validated at compile time
+against the inferred type.
+
+You can optionally add an explicit `as` type cast to narrow the variable type. 
The cast type can be a built-in
+type (`String`, `Long`, `Integer`, `Boolean`) or a fully qualified class name:
+
+```
+def value = someExpression as com.example.MyType
+```
+
+This is useful when the compiler infers a type that is too general (e.g., 
`Object` from a generic API return)
+and you know the concrete runtime type. The cast tells the compiler which type 
to use for subsequent method chain
+validation. Note that `as` performs a Java cast — it does **not** convert 
between types. For JSON conversion, use
+`toJson()` or `toJsonArray()` instead.
+
+The FQCN must be resolvable on the classpath at compile time. If the class is 
not found, the OAP server will fail
+to start.
+
+Two built-in conversion functions are provided for JSON interoperability:
+
+- `toJson(expr)` — converts a value to a Gson `JsonObject`. Works with JSON 
strings, `Map`, and protobuf `Struct`.
+- `toJsonArray(expr)` — converts a value to a Gson `JsonArray`. Works with 
JSON array strings.
+

Review Comment:
   Fixed in d990305. Added Map/List → Gson recursive conversion in 
toJsonObject(Object) and toJsonArray(Object). Docs are now accurate.



##########
oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/rt/LalRuntimeHelper.java:
##########
@@ -180,6 +188,115 @@ public String tagValue(final String key) {
         return "";
     }
 
+    // ==================== JSON conversion (for def variables) 
====================
+    //
+    // toJsonObject: converts to JsonObject. Overloaded for Struct, String, 
Object.
+    // toJsonArray:  converts to JsonArray.  Overloaded for String, Object.
+    //
+    // Primary use case: extract JWT claims from envoy ALS filter_metadata:
+    //   def jwt = toJson(parsed?.commonProperties?.metadata
+    //       ?.filterMetadataMap?.get("envoy.filters.http.jwt_authn"))
+    //   tag 'email': 
jwt?.getAsJsonObject("payload")?.get("email")?.getAsString()
+
+    /**
+     * Converts a protobuf {@link Struct} to a Gson {@link JsonObject}.
+     * Recursively converts nested Struct/Value/ListValue to Gson types.
+     */
+    public JsonObject toJsonObject(final Struct struct) {
+        if (struct == null) {
+            return null;
+        }
+        return structToJsonObject(struct);
+    }
+
+    /**
+     * Parses a JSON string into a {@link JsonObject}.
+     */
+    public JsonObject toJsonObject(final String s) {
+        if (s == null || s.isEmpty()) {
+            return null;
+        }
+        final JsonElement el = JsonParser.parseString(s);
+        return el.isJsonObject() ? el.getAsJsonObject() : null;
+    }
+
+    /**
+     * Fallback for {@code Map.get()} erasure — dispatches to typed overloads.
+     */
+    public JsonObject toJsonObject(final Object obj) {
+        if (obj == null) {
+            return null;
+        }
+        if (obj instanceof Struct) {
+            return toJsonObject((Struct) obj);
+        }
+        if (obj instanceof String) {
+            return toJsonObject((String) obj);
+        }
+        return null;
+    }

Review Comment:
   Fixed in d990305. toJsonObject(Object) now dispatches Map via a new 
toJsonObject(Map) overload with recursive conversion. toJsonArray(Object) also 
handles List input.



##########
oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java:
##########
@@ -1140,6 +1172,283 @@ static String generateExtraLogAccess(
         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 = "_d" + genCtx.localVarCounter++;
+
+        // 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 Object[] info = 
BUILTIN_FUNCTIONS.get(init.getFunctionCallName());
+            final String helperMethod = (String) info[0];
+            resolvedType = (Class<?>) info[1];
+
+            initExpr.append(helperMethod).append("(");
+            if (!init.getFunctionCallArgs().isEmpty()) {
+                generateValueAccess(initExpr,
+                    init.getFunctionCallArgs().get(0).getValue(), genCtx);
+            } else {
+                initExpr.append("null");
+            }
+            initExpr.append(")");
+        } else {

Review Comment:
   Fixed in d990305. toJson()/toJsonArray() now validate exactly 1 argument at 
compile time and throw IllegalArgumentException with a clear message otherwise.



##########
oap-server/analyzer/log-analyzer/CLAUDE.md:
##########
@@ -105,6 +110,68 @@ All generated methods include a `LocalVariableTable` 
attribute for debugger/deco
 
 LVT entries are added via `PrivateMethod` inner class which carries both 
source code and variable descriptors.
 
+## Local Variables (`def`)
+
+The `def` keyword declares local variables in the extractor (or filter level). 
The grammar rule:
+
+```
+defStatement: DEF IDENTIFIER ASSIGN valueAccess typeCast? ;
+```
+
+The optional `typeCast` supports built-in types (`String`, `Long`, `Integer`, 
`Boolean`) and
+fully qualified class names (`as com.example.MyType`). The FQCN is resolved 
via `Class.forName()`
+at compile time. If not found, compilation fails with 
`IllegalArgumentException`.
+
+### Type inference
+
+The variable type is inferred from the initializer expression:
+
+| Initializer | Inferred type | Generated code |
+|---|---|---|
+| `toJson(expr)` | `JsonObject` | `h.toJsonObject(expr)` |
+| `toJsonArray(expr)` | `JsonArray` | `h.toJsonArray(expr)` |
+| General value access | Last resolved type via reflection | Standard value 
access codegen |
+
+Built-in functions are registered in `BUILTIN_FUNCTIONS` map in 
`LALBlockCodegen`.
+
+### Chained def variables
+
+A `def` variable can be initialized from another `def` variable's method chain:
+
+```
+def jwt = 
toJson(parsed?.commonProperties?.metadata?.filterMetadataMap?.get("envoy.filters.http.jwt_authn"))
+def payload = jwt?.getAsJsonObject("payload")
+tag 'email': payload?.get("email")?.getAsString()
+```
+
+The general value access path in `generateDefStatement()` recognizes `jwt` as 
a def variable,
+delegates to `generateDefVarChain()` which uses reflection to resolve the 
chain, and
+`genCtx.lastResolvedType` captures the resolved type (`JsonObject` in this 
case).
+
+### Runtime helpers
+
+`LalRuntimeHelper` provides `toJsonObject()` and `toJsonArray()` overloads:
+
+| Method | Input type | Conversion |
+|---|---|---|
+| `toJsonObject(String)` | JSON string | 
`JsonParser.parseString().getAsJsonObject()` |
+| `toJsonObject(Map)` | Map (from JSON/YAML parser) | Recursive Gson 
conversion |
+| `toJsonObject(Struct)` | Protobuf `Struct` | Recursive field conversion 
preserving nested structures |
+| `toJsonObject(Object)` | Any (fallback) | Delegates to above based on 
runtime type |
+| `toJsonArray(String)` | JSON array string | 
`JsonParser.parseString().getAsJsonArray()` |
+| `toJsonArray(Object)` | Any (fallback) | String fallback |
+

Review Comment:
   Fixed in d990305. Map support is now implemented — docs and code are 
consistent.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to