Layer extension: register custom layers without modifying OAP source#13856
Merged
Layer extension: register custom layers without modifying OAP source#13856
Conversation
18ac7f6 to
3035228
Compare
`Layer` becomes a registry-backed value type with the same public API as
the prior enum (`Layer.SERVICE`, `value()`, `name()`, `valueOf(int)`,
`valueOf(String)`, `nameOf(String)`, `values()`). Built-in layers stay as
`public static final Layer` constants with frozen ordinals 0–49.
External layers can be registered through any of three new paths, all
funneling through `Layer.register(name, ordinal, normal)`:
- `layer-extensions.yml` on the OAP classpath / config dir — operator
config; one line per layer, no code change required.
- `LayerExtension` Java SPI under `META-INF/services/` — for plugin jars.
- Inline `layerDefinitions:` block at the top of any MAL or LAL rule
file — the cleanest path when a layer ships together with the rules
that produce its telemetry.
Storage encoding is unchanged: layers persist by ordinal int in BanyanDB,
Elasticsearch, and JDBC. Built-in ordinals 0–49 stay frozen; 50–999 are
reserved by convention for future built-ins; external layers are
recommended to start at >= 1000. The recommendation is informational —
collisions are reported loudly at boot via the ordinal-uniqueness check.
The registry is sealed at the start of `Core.notifyAfterCompleted()` after
every module's prepare/start has run. Subsequent `Layer.register` calls
throw, so runtime-rule MAL/LAL `/addOrUpdate` requests now reject inline
`layerDefinitions:` with a clear error pointing operators at the boot-time
registration paths.
`UITemplateInitializer` no longer iterates `Layer.values()` for folder
discovery; it walks `ui-initialized-templates/**/*.json` recursively and
trusts each template's own `configuration.layer` field, so dashboards for
external layers are auto-discovered without code changes.
`Layer.values()` now throws before seal — pre-seal the registry may still
grow, and a partial snapshot would silently mislead callers. The cached
sorted snapshot is computed once at seal so post-seal calls are O(1) plus
an array clone.
BanyanDB `MetadataRegistry.parseTagSpec` previously relied on
`Class.isEnum()` to map Layer columns to TAG_TYPE_INT; with `Layer` no
longer an enum, that path now treats `Layer.class` explicitly, matching
the existing ES / JDBC handling.
- LayerExtension.java: javadoc inside `<pre>{@code ... @OverRide ...}</pre>`
blew up because `{@code}` parses `@Override` as a nested unknown tag.
Drop the `{@code}` wrapper and use HTML-escaped `@Override` inside
plain `<pre>`.
- LALBlockCodegen.generateFieldToOutput: previously detected enum-typed
setters via `paramType.isEnum()` and emitted `Type.valueOf(string)` to
cast LAL extractor `layer "MYSQL"` (a String literal) into the typed
setter argument. With Layer no longer an enum, the `isEnum()` branch
was missed and codegen tried to pass `String` to `setLayer(Layer)`,
failing at compile-time. Add `paramType == Layer.class` to the same
branch — Layer's registry-backed `valueOf(String)` keeps the call
shape identical.
649abce to
aa6831e
Compare
…AL/LAL inline
Four new test classes covering each of the registration paths added by this PR. All
tests use partitioned ordinal ranges and idempotent re-registration so they share the
process-wide Layer registry safely without calling Layer.seal() (which would taint
sibling tests in the same surefire fork).
- LayerTest (9 tests, server-core, ordinals 1100-1149) — built-in lookups, valueOf
by name and ordinal, idempotent re-registration, name conflict, ordinal conflict,
name-shape rejection, external layer lookup parity.
- LayerExtensionLoaderTest (4 tests, server-core, ordinals 1150-1169) — yaml
fixture loaded via the new package-private LayerExtensionLoader.loadFromYaml(path)
overload, missing-file silent path, SPI loaded via a META-INF/services entry on
the test classpath registering TestSpiLayerExtension, and idempotent reload.
- RulesLayerDefinitionsTest (2 tests, meter-analyzer, ordinals 1200-1209) — MAL
rule file with a top-level layerDefinitions: block; verifies layers register
before the rule's expressions parse, and reload is idempotent.
- LALConfigsLayerDefinitionsTest (2 tests, log-analyzer, ordinals 1300-1309) —
same shape for LAL.
LayerExtensionLoader.loadFromYaml and loadFromSpi were narrowed from private to
package-private so tests can drive each path independently of the other.
wankai123
approved these changes
Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom Layers without modifying the OAP source
Layerbecomes a registry-backed value type with the same public API as the prior enum (Layer.SERVICE,value(),name(),valueOf(int),valueOf(String),nameOf(String),values()). Built-in layers stay aspublic static final Layerconstants with frozen ordinals 0–49.External layers can register through any of three new paths, all funneling through
Layer.register(name, ordinal, normal):layer-extensions.ymlon the OAP classpath / config dir — operator config, one line per layer, no code change.LayerExtensionJava SPI underMETA-INF/services/— for plugin jars.layerDefinitions:block at the top of any MAL or LAL rule file — the cleanest path when a layer ships together with the rules that produce its telemetry.Storage encoding is unchanged (ordinal int in BanyanDB / ES / JDBC). The registry is sealed at the start of
Core.notifyAfterCompleted()after every module's prepare/start has run; runtime-rule MAL/LAL/addOrUpdaterequests now reject inlinelayerDefinitions:with a clear error pointing operators at the boot-time registration paths.UITemplateInitializerno longer iteratesLayer.values()for folder discovery — it walksui-initialized-templates/**/*.jsonrecursively and trusts each template's ownconfiguration.layer, so dashboards for external layers are auto-discovered with no code change.Built-in ordinals 0–49 stay frozen; 50–999 are reserved by convention for future built-ins; external layers are recommended (not required) to start at
>= 1000. Collisions are reported loudly at boot via the ordinal-uniqueness check.If this is non-trivial feature, paste the links/URLs to the design doc.
Update the documentation to include this new feature.
Tests(including UT, IT, E2E) are added to verify the new feature.
If it's UI related, attach the screenshots below.
If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.
Update the `CHANGES` log.