{"record":{"id":"3b5ed25147c07b9c","repo":"apache/skywalking","slug":"phase-2-register-failed-for","errorCode":null,"errorMessage":"phase-2 register failed for {}","messagePattern":"phase-2 register failed for (.+?)","errorType":"exception","errorClass":"MetricConvert.PartialRegistrationException","httpStatus":null,"severity":"error","filePath":"oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/MetricConvert.java","lineNumber":185,"sourceCode":"                    putIfNonEmpty(meta, \"exp\", r.getExp());\n                    putIfNonEmpty(meta, \"expSuffix\", rule.getExpSuffix());\n                    putIfNonEmpty(meta, \"expPrefix\", rule.getExpPrefix());\n                    holder.setMetadata(meta);\n                }\n                return analyzer;\n            }\n        ).collect(toList());\n        // Phase 2 — register. Track each metric name as it's successfully registered so a\n        // mid-phase throw gives the caller an accurate \"actually registered\" set. The previous\n        // design left the caller using the full enumerated metric list for rollback, which was\n        // catastrophic for FILTER_ONLY edits: a compile surprise between register() calls would\n        // wipe the old bundle's metrics that this apply attempt never touched.\n        final Set<String> registered = new LinkedHashSet<>(prepared.size());\n        for (final Analyzer a : prepared) {\n            try {\n                a.register();\n            } catch (final Throwable t) {\n                throw new PartialRegistrationException(\n                    \"phase-2 register failed for \" + a.getMetricName(),\n                    t, Collections.unmodifiableSet(new LinkedHashSet<>(registered)));\n            }\n            registered.add(a.getMetricName());\n        }\n        this.analyzers = prepared;\n        this.registeredMetricNames = Collections.unmodifiableSet(registered);\n    }\n\n    /**\n     * Metric names that completed phase-2 register on this instance — the set the caller would\n     * unregister to undo a successful apply. Same as {@code analyzers.stream().map(getMetricName)}\n     * for a fully-constructed instance; the field exists so {@link PartialRegistrationException}\n     * can carry the same value for the partial case.\n     */\n    @Getter\n    private final Set<String> registeredMetricNames;\n","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/apache/skywalking/blob/102af09b4a56064e22050dded10e2c52e490d040/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/MetricConvert.java#L167-L203","documentation":"Thrown by MetricConvert.apply (phase 2) when an individual Analyzer's register() call fails during a MAL rule application — e.g. a metric name colliding with an existing registration or a meter-system rejection. It is a PartialRegistrationException carrying the accurate set of metric names that DID register before the failure, so the caller can roll back exactly those and leave the previously active bundle intact (critical for FILTER_ONLY or partial edits where blindly unregistering the full enumerated list would wipe untouched metrics).","triggerScenarios":"A runtime MAL rule update (dynamic configuration / runtime-rule channel) where one metric in the rule fails register() — duplicate metric name across rules, incompatible metric type vs an existing metric, or analyzer validation errors; also at startup apply of log-mal-rules.","commonSituations":"Two meter rules both declaring metric 'x'; changing a metric's type (e.g. histogram → counter) while the old one is still registered; a live rule edit rejected mid-bundle.","solutions":["Read the message for the failing metric name and the cause for why register() rejected it","Fix the rule (rename the colliding metric, keep the metric type consistent with the existing registration)","On failure, unregister exactly the names in the exception's registered set — do NOT unregister the full rule's metric list","After fixing, re-apply the rule; the old bundle remains active until a clean apply succeeds"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before apply, check every metric name is free or owned by this rule:\n// for (String name : ruleMetricNames) {\n//     MeterSystem existing = ...; if (registeredElsewhere(name)) reject early;\n// }","typeGuard":null,"tryCatchPattern":"try {\n    convert.apply(...);\n} catch (PartialRegistrationException e) {\n    // roll back ONLY e.getRegisteredMetricNames() (the accurate partial set),\n    // never the full enumerated metric list; the previous bundle stays active\n    for (String name : e.getRegisteredMetricNames()) {\n        meterSystem.unregister(name); // or your registry's removal API\n    }\n    // surface the failing metric name from e.getMessage() to the rule author\n}","preventionTips":["Keep metric names globally unique across all MAL rules","Never change a live metric's type via rule edit — unregister first or rename","Always use the exception's registered set for rollback, not the rule's declared set"],"tags":["mal","meter-analyzer","runtime-rules","registration"],"backgroundTag":null,"analyzedSha":"102af09b4a56064e22050dded10e2c52e490d040","analyzedAt":"2026-08-14T10:47:52.647Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}