Skip to content

Commit 681a5be

Browse files
authored
SWIP-12 design + UITemplateInitializer auto-discovery & dev hot-reload (#13834)
**1. SWIP-12 design doc** (`docs/en/swip/SWIP-12.md`) Proposes WeChat & Alipay Mini-Program monitoring as a new pair of `Layer` values. Covers SDK alignment (histogram bucket unit), native-trace SegmentListener SPI, entity model, layer partitioning, dashboards layout, and MAL/OAL scope split. Still a design-only proposal — implementation lands in follow-up PRs. **2. UITemplateInitializer extensibility + dev hot-reload** - `UI_TEMPLATE_FOLDER` is now computed from `Layer.values() + "custom"` at class-init time. Adding a new `Layer` enum value is enough — drop a `ui-initialized-templates/<layer-name-lowercased>/` folder on disk and it's scanned on the next boot. Removes the prior hardcoded allowlist that was easy to miss. - `SW_UI_TEMPLATE_FORCE_RELOAD` env var switches the initializer from `addIfNotExist` to a new `addOrReplace` helper on `UITemplateManagementService`. When true, shipped templates overwrite any seeded copy every boot — so dev/extension edits show up after a simple OAP restart without wiping storage. Unset / false preserves the production behavior where operator UI edits persist. - `UITemplateCheckerTest` updated to tolerate missing folders (several `Layer` values have no template folders today). **3. `new-monitoring-feature` skill** (`.claude/skills/new-monitoring-feature/SKILL.md`) A wiring map for contributors adding a new layer: which extension point handles which signal (OAL / MAL / LAL / SpanListener / SegmentListener), where contracts live, UI template + submodule touchpoints, and cross-cutting traps.
1 parent 0dabc70 commit 681a5be

6 files changed

Lines changed: 1256 additions & 48 deletions

File tree

.claude/skills/new-monitoring-feature/SKILL.md

Lines changed: 333 additions & 0 deletions
Large diffs are not rendered by default.

docs/en/swip/SWIP-12.md

Lines changed: 858 additions & 0 deletions
Large diffs are not rendered by default.

docs/en/swip/readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@ All accepted and proposed SWIPs can be found in [here](https://github.com/apache
6868

6969
## Known SWIPs
7070

71-
Next SWIP Number: 12
71+
Next SWIP Number: 13
7272

7373
### Proposed SWIPs
7474

7575
### Accepted SWIPs
7676

77+
- [SWIP-12 Support WeChat & Alipay Mini Program Monitoring](SWIP-12.md)
7778
- [SWIP-11 Support iOS App Monitoring via OpenTelemetry](SWIP-11.md)
7879
- [SWIP-10 Support Envoy AI Gateway Observability](SWIP-10/SWIP.md)
7980
- [SWIP-9 Support Flink Monitoring](SWIP-9.md)

oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateInitializer.java

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@
2525
import java.io.File;
2626
import java.io.FileNotFoundException;
2727
import java.io.IOException;
28+
import java.util.Arrays;
2829
import java.util.List;
2930
import java.util.Objects;
31+
import java.util.stream.Stream;
3032
import org.apache.skywalking.oap.server.core.CoreModule;
3133
import org.apache.skywalking.oap.server.core.analysis.Layer;
3234
import org.apache.skywalking.oap.server.core.query.input.DashboardSetting;
@@ -39,53 +41,37 @@
3941
* UITemplateInitializer load the template from the config file in json format. It depends on the UI implementation only.
4042
* Each config file should be only one dashboard setting json object.
4143
* The dashboard names should be different in the same Layer and entity.
44+
* <p>
45+
* Folder discovery is automatic: every {@link Layer} enum value is probed as a folder
46+
* (via {@code Layer.name().toLowerCase()}) plus the {@code "custom"} slot. Folders that
47+
* don't exist are silently skipped. Extensions adding a new {@code Layer} do not need to
48+
* touch this class.
49+
* <p>
50+
* Dev/extension reload: when environment variable {@code SW_UI_TEMPLATE_FORCE_RELOAD}
51+
* is {@code true}, each template on disk is written via {@code addOrReplace} rather
52+
* than {@code addIfNotExist}, so edits to shipped JSON take effect on the next OAP
53+
* restart without needing to wipe the storage container.
4254
*/
4355
@Slf4j
4456
public class UITemplateInitializer {
45-
public static String[] UI_TEMPLATE_FOLDER = new String[] {
46-
Layer.MESH.name(),
47-
Layer.GENERAL.name(),
48-
Layer.OS_LINUX.name(),
49-
Layer.MESH_CP.name(),
50-
Layer.MESH_DP.name(),
51-
Layer.MYSQL.name(),
52-
Layer.POSTGRESQL.name(),
53-
Layer.K8S.name(),
54-
Layer.BROWSER.name(),
55-
Layer.SO11Y_OAP.name(),
56-
Layer.VIRTUAL_DATABASE.name(),
57-
Layer.VIRTUAL_CACHE.name(),
58-
Layer.K8S_SERVICE.name(),
59-
Layer.SO11Y_SATELLITE.name(),
60-
Layer.APISIX.name(),
61-
Layer.VIRTUAL_MQ.name(),
62-
Layer.AWS_EKS.name(),
63-
Layer.OS_WINDOWS.name(),
64-
Layer.AWS_S3.name(),
65-
Layer.AWS_DYNAMODB.name(),
66-
Layer.AWS_GATEWAY.name(),
67-
Layer.REDIS.name(),
68-
Layer.ELASTICSEARCH.name(),
69-
Layer.RABBITMQ.name(),
70-
Layer.MONGODB.name(),
71-
Layer.KAFKA.name(),
72-
Layer.PULSAR.name(),
73-
Layer.BOOKKEEPER.name(),
74-
Layer.NGINX.name(),
75-
Layer.ROCKETMQ.name(),
76-
Layer.CLICKHOUSE.name(),
77-
Layer.ACTIVEMQ.name(),
78-
Layer.CILIUM_SERVICE.name(),
79-
Layer.SO11Y_JAVA_AGENT.name(),
80-
Layer.KONG.name(),
81-
Layer.SO11Y_GO_AGENT.name(),
82-
Layer.FLINK.name(),
83-
Layer.BANYANDB.name(),
84-
Layer.VIRTUAL_GENAI.name(),
85-
Layer.ENVOY_AI_GATEWAY.name(),
86-
Layer.IOS.name(),
87-
"custom"
88-
};
57+
/**
58+
* Every {@link Layer} enum value, lower-cased, plus the {@code "custom"} folder.
59+
* Computed once from {@link Layer#values()} so adding a new {@code Layer} automatically
60+
* causes its {@code ui-initialized-templates/<layer>/} folder to be scanned.
61+
*/
62+
public static final String[] UI_TEMPLATE_FOLDER = Stream.concat(
63+
Arrays.stream(Layer.values()).map(Layer::name),
64+
Stream.of("custom")
65+
).toArray(String[]::new);
66+
67+
/**
68+
* Environment variable: when {@code true}, templates on disk overwrite any previously
69+
* seeded copy in storage on every boot. Read from the OS environment directly so
70+
* operators / extenders can flip it without touching {@code application.yml}.
71+
*/
72+
private static final boolean FORCE_RELOAD =
73+
Boolean.parseBoolean(System.getenv("SW_UI_TEMPLATE_FORCE_RELOAD"));
74+
8975
private final UITemplateManagementService uiTemplateManagementService;
9076
private final ObjectMapper mapper;
9177

@@ -98,6 +84,9 @@ public UITemplateInitializer(ModuleManager manager) {
9884
}
9985

10086
public void initAll() throws IOException {
87+
if (FORCE_RELOAD) {
88+
log.info("SW_UI_TEMPLATE_FORCE_RELOAD=true — shipped UI templates will overwrite any previously seeded copy on this boot.");
89+
}
10190
for (String folder : UITemplateInitializer.UI_TEMPLATE_FOLDER) {
10291
try {
10392
File[] templateFiles = ResourceUtils.getPathFiles("ui-initialized-templates/" + folder.toLowerCase());
@@ -128,7 +117,11 @@ public void initTemplate(File template) throws IOException {
128117
setting.setId(inId);
129118
setting.setConfiguration(configNode.toString());
130119

131-
uiTemplateManagementService.addIfNotExist(setting);
120+
if (FORCE_RELOAD) {
121+
uiTemplateManagementService.addOrReplace(setting);
122+
} else {
123+
uiTemplateManagementService.addIfNotExist(setting);
124+
}
132125
}
133126

134127
private void verifyNameConflict(File template, String inId, String inNameKey) throws IOException {

oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/management/ui/template/UITemplateManagementService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,19 @@ public void addIfNotExist(DashboardSetting setting) throws IOException {
7575
getUITemplateManagementDAO().addTemplate(setting);
7676
}
7777
}
78+
79+
/**
80+
* Add or replace: if a template with the same id already exists, overwrite it;
81+
* otherwise create a new one. Used by the initializer when
82+
* {@code SW_UI_TEMPLATE_FORCE_RELOAD=true} so that edits to shipped templates
83+
* take effect without wiping the storage container.
84+
*/
85+
public void addOrReplace(DashboardSetting setting) throws IOException {
86+
DashboardConfiguration configuration = getUITemplateManagementDAO().getTemplate(setting.getId());
87+
if (configuration == null) {
88+
getUITemplateManagementDAO().addTemplate(setting);
89+
} else {
90+
getUITemplateManagementDAO().changeTemplate(setting);
91+
}
92+
}
7893
}

oap-server/server-starter/src/test/java/org/apache/skywalking/oap/server/starter/UITemplateCheckerTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.junit.jupiter.api.Test;
2929

3030
import java.io.File;
31+
import java.io.FileNotFoundException;
3132
import java.io.IOException;
3233
import java.util.HashSet;
3334
import java.util.Locale;
@@ -45,8 +46,15 @@ public void validateUITemplate() throws IOException {
4546
Set<String> dashboardIds = new HashSet<>();
4647
Set<String> dashboardNames = new HashSet<>();
4748
for (String folder : UITemplateInitializer.UI_TEMPLATE_FOLDER) {
48-
File[] templateFiles = ResourceUtils.getPathFiles("ui-initialized-templates/" + folder.toLowerCase(
49-
Locale.ROOT));
49+
File[] templateFiles;
50+
try {
51+
templateFiles = ResourceUtils.getPathFiles("ui-initialized-templates/" + folder.toLowerCase(
52+
Locale.ROOT));
53+
} catch (FileNotFoundException e) {
54+
// Layer enum values without an on-disk template folder are skipped —
55+
// mirrors UITemplateInitializer.initAll() behavior post-auto-discovery.
56+
continue;
57+
}
5058
for (File template : templateFiles) {
5159
JsonNode jsonNode;
5260
try {

0 commit comments

Comments
 (0)