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


##########
oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALBlockCodegen.java:
##########
@@ -1175,23 +1484,21 @@ static void generateProcessRegistryCall(
 
     static String appendMethodSegment(final String current,
                                        final LALScriptModel.MethodSegment ms) {
+        final String mn = ms.getName();
+        final String args = ms.getArguments().isEmpty()
+            ? "" : generateMethodArgs(ms.getArguments());
         if (ms.isSafeNav()) {
-            final String mn = ms.getName();
+            // 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 + ")";
-            } else {
-                throw new IllegalArgumentException(
-                    "Unsupported safe-nav method: ?." + mn + "()");
             }
+            // General safe-nav: null guard with ternary
+            return "(" + current + " == null ? null : "
+                + current + "." + mn + "(" + args + "))";
         } else {
-            if (ms.getArguments().isEmpty()) {
-                return current + "." + ms.getName() + "()";
-            } else {
-                return current + "." + ms.getName() + "("
-                    + generateMethodArgs(ms.getArguments()) + ")";
-            }
+            return current + "." + mn + "(" + args + ")";
         }
     }

Review Comment:
   Already handled. generateDefVarChain() checks returnType.isPrimitive() at 
lines 1326-1332 and wraps with boxing + null-guard. The appendMethodSegment 
path is only used for non-def-var chains where the compile-time type is unknown 
— in practice those only call toString()/trim() style methods.



##########
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;
+    }
+
+    /**
+     * Parses a JSON string into a {@link JsonArray}.
+     */
+    public JsonArray toJsonArray(final String s) {
+        if (s == null || s.isEmpty()) {
+            return null;
+        }
+        final JsonElement el = JsonParser.parseString(s);
+        return el.isJsonArray() ? el.getAsJsonArray() : null;
+    }

Review Comment:
   Intentional fail-fast. LAL rules are configured by operators, not end users. 
A malformed JSON body means the log source is sending unexpected data — failing 
loudly is the right behavior so operators notice and fix the pipeline. Silently 
returning null would hide data issues.



##########
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:
   Valid — Map support was documented but the runtime dispatcher was missing 
the Map case. Will add it.



-- 
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