{"record":{"id":"77650fd92ffdb279","repo":"apache/skywalking","slug":"decorate-should-be-invoked-after-service","errorCode":null,"errorMessage":"decorate() should be invoked after service()","messagePattern":"decorate\\(\\) should be invoked after service\\(\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALMetadataExtractor.java","lineNumber":171,"sourceCode":"            }\n        }\n\n        // Validate decorate() usage\n        boolean hasDecorate = false;\n        for (final List<MALExpressionModel.MethodCall> chain : allChains) {\n            for (final MALExpressionModel.MethodCall mc : chain) {\n                if (\"decorate\".equals(mc.getName())) {\n                    hasDecorate = true;\n                    break;\n                }\n            }\n            if (hasDecorate) {\n                break;\n            }\n        }\n        if (hasDecorate) {\n            if (scopeType != null && scopeType != ScopeType.SERVICE) {\n                throw new IllegalStateException(\n                    \"decorate() should be invoked after service()\");\n            }\n            if (isHistogram) {\n                throw new IllegalStateException(\n                    \"decorate() not supported for histogram metrics\");\n            }\n        }\n\n        return new ExpressionMetadata(\n            new ArrayList<>(sampleNames),\n            scopeType,\n            scopeLabels,\n            aggregationLabels,\n            downsampling,\n            isHistogram,\n            percentiles\n        );\n    }","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/compiler/MALMetadataExtractor.java#L153-L189","documentation":"Thrown by MALMetadataExtractor during compile-time AST analysis when a MAL expression chain contains a decorate({...}) call but the declared scope is not service scope. decorate() attaches service-level attributes via a DecorateFunction closure, which is only meaningful when the metric is aggregated into Service entities, so the extractor rejects any other scope (ServiceInstance, Endpoint, ServiceRelation, All). It fires at OAP startup while compiling the rule, before any data is processed.","triggerScenarios":"A MAL rule whose expression uses decorate({ me -> ... }) together with an expSuffix scope function other than service(...), e.g. '...sum([\\'idc\\']).serviceInstance([\\'idc\\']).decorate({ me -> me.attr0 = ... })', or where a file-level expPrefix injects decorate into a chain that ends in a non-service scope. Concretely: hasDecorate is true after scanning the method chain AND scopeType != null AND scopeType != ScopeType.SERVICE.","commonSituations":"Copying an existing service-scope decorate rule (e.g. from the SkyWalking service-level dashboards) and changing expSuffix to serviceInstance or endpoint while forgetting to remove the decorate() call; upgrading from v1 Groovy engine where decorate was silently tolerated on other scopes; hand-editing otel-rules YAML files.","solutions":["Move/keep decorate() only in expressions whose expSuffix is service([...]), e.g. \"expr: http_success_request.sum(['idc']).service(['idc'], Layer.GENERAL).decorate({ me -> me.attr0 = me.layer.name() })\"","If you intended instance/endpoint scope, delete the .decorate({...}) segment entirely","Check the file-level expPrefix: if it contains decorate(), it is injected into every rule in the file, so it conflicts with any rule not using service() scope — scope it per-rule instead","Restart OAP after fixing the YAML; the error is raised at rule compilation, not at runtime"],"exampleFix":"# before (rule YAML)\nexpr: http_success_request.sum(['idc']).serviceInstance(['idc']).decorate({ me -> me.attr0 = 'x' })\n\n# after\nexpr: http_success_request.sum(['idc']).serviceInstance(['idc'])\n# or, if service attributes are wanted:\nexpr: http_success_request.sum(['idc']).service(['idc']).decorate({ me -> me.attr0 = 'x' })","handlingStrategy":"validation","validationCode":"// before compiling, assert the rule's scope if it uses decorate\nString expr = rule.getExp();\nif (expr.contains(\"decorate(\")) {\n    boolean serviceScoped = expr.matches(\"(?s).*\\\\.service\\\\(.*\\\\.decorate\\\\(.*\");\n    if (!serviceScoped) {\n        throw new IllegalArgumentException(\"decorate() requires service() scope: \" + expr);\n    }\n}","typeGuard":null,"tryCatchPattern":"catch (IllegalStateException e) when starting the analyzer; log rule name + expression and fail OAP startup fast so the misconfiguration is visible immediately","preventionTips":["Keep decorate() only in the service-scope rule template; document one canonical example per team","Never put decorate() in a file-level expPrefix — it would be injected into every rule including non-service scopes","Run the MAL rule comparison test suite (dsl-scripts-test) over custom rule files in CI"],"tags":["mal","configuration","rule-validation","startup"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}