You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SWIP-12 Support WeChat & Alipay Mini Program Monitoring
Motivation
WeChat (微信) and Alipay (支付宝) Mini Programs are among the most widely used client-side
platforms in China — many businesses ship a mini-program before (or instead of) a native
mobile app. Observability for mini-programs is as important as iOS/Android monitoring.
The SkyAPM mini-program-monitor SDK
provides a single JavaScript client that covers both WeChat and Alipay runtimes. It emits:
SkyWalking native trace segments (opt-in) — one SegmentObject per sampled
outgoing request, posted to /v3/segments with an sw8 header injected on the wire
so downstream services join the same trace. As of SDK v0.3.0, every exit span carries
per-platform componentId (10002 WeChat, 10003 Alipay) and a miniprogram.platform tag
Because the SDK speaks standard OTLP + SkyWalking native, no new receiver is required.
This SWIP focuses on: two new layers, platform-aware MAL/LAL routing, service/instance/
endpoint entity convention, componentId-driven layer mapping for native trace segments,
menu/dashboard support, and a data generator for skywalking-showcase.
This SWIP builds on LAL layer: auto / sourceAttribute() (SWIP-11) and the existing SegmentParserListenerManager trace-analyzer pipeline — no new general-purpose
infrastructure is needed.
/** * WeChat Mini Program monitoring via mini-program-monitor SDK */WECHAT_MINI_PROGRAM(48, true),
/** * Alipay Mini Program monitoring via mini-program-monitor SDK */ALIPAY_MINI_PROGRAM(49, true),
Both are normal layers (isNormal=true) — the mini-program is the observed service itself.
Why two layers, not one: the two platforms expose different sets of metrics (see §3),
and users want to compare across WeChat apps or across Alipay apps separately. Using a
tag would force every query and dashboard widget to filter by platform — a layer per
platform is cleaner and mirrors how the SkyWalking UI organizes other platforms.
2. Service / Instance / Endpoint Mapping
The SDK's OTLP resource block provides three identifiers, but only two are usable as
aggregation dimensions:
SkyWalking Entity
Source
Cardinality
Rationale
Service
service.name
1 per app
Fleet-wide app health
ServiceInstance
service.instance.id (recommended pattern: operator sets it to service.version)
tens per app
Version regression / rollout monitoring. Coherence depends on operator following the recommended pattern — see "Instance coherence" below.
Endpoint
miniprogram.page.path
dozens per app
Which in-app page is slow / errors — matches browser-agent semantics
What we deliberately drop:
Dropped
Instead used as
Why
Per-device service.instance.id
Not aggregated as an entity
Unbounded cardinality — millions for any real user base. SDK ≥ v0.4.0 no longer auto-generates a device id; operators set serviceInstance to a version-scoped value (see §8).
server.address (remote host for outbound requests)
Metric label on miniprogram.request.duration + peer on segments
Not a mini-program entity; topology handles it via tracing
All three entities are needed — each answers a distinct question:
Service → how is the app doing overall?
Instance (= version) → did v1.3 regress vs v1.2?
Endpoint (= page) → which page is slow / error-prone?
Skipping any of them loses a class of question.
Instance coherence across signals
The three signal pipelines key off different attributes by default:
Signal
Source attribute used as instance
OTLP metrics
OTLP resource service.instance.id (omitted by SDK if serviceInstance unset)
Native trace segments
serviceInstance field on the segment (substituted with literal - if unset)
OTLP logs (via LAL)
sourceAttribute("service.instance.id") (the recommended LAL extractor — see §5)
For all three to land on the same OAP instance entity, the operator must set init({ serviceInstance: <some-string> }) — recommended value is service.version so
the same string appears as both service.instance.id (OTLP) and segment serviceInstance.
When serviceInstance is unset, the three pipelines do not uniformly fall back to
the same placeholder — they each handle absence differently:
Pipeline
Behavior on absent serviceInstance
Native trace segment
SDK substitutes the literal - at the wire (mini-program-monitor request.ts:147); OAP records the instance entity literally as -.
OTLP log → LAL
TrafficSinkListener:83 short-circuits when metadata.serviceInstance is empty; no instance traffic is generated.
OTLP metric → MAL
SampleFamily.dim() (SampleFamily.java:715) collapses missing labels to the empty string — the instance dimension is empty, no instance entity is built.
So the unset case is not "all three aligned under -" — segments get a - entity,
logs and metrics get no instance entity at all. Operators who care about per-instance
dashboards must set serviceInstance. This is documented as the recommended pattern in
the SDK (README.md / SIGNALS.md) and pinned in the SDK's e2e CI.
The earlier draft of this SWIP set the LAL instance to sourceAttribute("service.version"),
which would make logs disagree with metrics + traces whenever serviceInstance != serviceVersion. §5 below sources from service.instance.id directly so when the
operator follows the recommended pattern, all three signal types share the same
instance entity.
3. Metric Coverage Per Platform
Not every signal is supported on both runtimes. MAL rules emit the same metric names
for both layers where supported; WeChat-only metrics produce data only under the WECHAT_MINI_PROGRAM layer.
PerformanceObserver firstPaint. Wall-clock epoch ms timestamp, not a duration. Not aggregated by MAL — surfaced only on individual page traces / log queries.
pageNotFound error log (no my.onPageNotFound hook on Alipay)
Precision caveat for Alipay perf metrics: on WeChat, perf values come from the native PerformanceObserver entries. On Alipay, the SDK falls back to lifecycle hooks
(App.onLaunch→onShow, Page.onLoad→onReady) because the Alipay base library does not
expose PerformanceObserver entries for the same events. These are approximations of
"time-to-interactive" rather than authoritative renderer timings. Dashboards should not
compare WeChat and Alipay perf values directly; this is documented in the per-platform
doc pages.
4. MAL Rules — Per-Platform × Per-Scope, Mirroring the iOS Layout
Following the iOS pattern (otel-rules/ios/ios-metrickit.yaml for service-scoped + ios-metrickit-instance.yaml for instance-scoped — service-scoped meters there have no service_instance_id dim so the "overall app health" view is genuinely
fleet-aggregated), this SWIP creates four files:
Each file has a single expSuffix (one Layer, one entity scope) and a filter block
that gates on miniprogram.platform so traffic from the wrong platform is dropped at
the rule level.
Drop the WeChat-only metrics Alipay doesn't emit
(route_duration, script_duration, package_load_duration)
Notes
Service-scoped rules sum/avg by service_name only — no service_instance_id
fragmentation. This produces the genuine fleet-aggregated view for the "overall app
health" dashboard panels. iOS's ios-metrickit.yaml is the precedent.
Instance-scoped rules go in their own file with expSuffix: instance(...). This
is what backs per-release / version-regression dashboards.
service_instance_id source: SDK ≥ v0.4.0 emits OTLP service.instance.id only
when the operator passes init({ serviceInstance: ... }). When unset, the attribute
is omitted; OAP records the instance entity as the literal -. The instance dashboard
is meaningful only when operators follow the recommended serviceInstance: serviceVersion pattern (otherwise everything aggregates under -). MAL itself can
add a fail-safe (tag {tags -> tags.service_instance_id = tags.service_instance_id ?: tags.service_name})
but standard practice is to rely on the SDK side.
The .endpoint(...) chain on service-scoped files — same expression-level
override pattern as APISIX (apisix.yaml:91-102) and RocketMQ. One rule emits to
service scope (default from expSuffix), the next emits to endpoint scope by
chaining .endpoint(...) at the end.
5. LAL Rules (Error Logs, layer: auto Fork by Platform)
Uses the layer: auto + sourceAttribute() mechanism from SWIP-11:
rules:
- name: miniprogram-errorslayer: autodsl: | filter { def platform = sourceAttribute("miniprogram.platform"); if (platform != "wechat" && platform != "alipay") { abort {} } if (tag("exception.type") == null) { abort {} } extractor { layer platform == "wechat" ? "WECHAT_MINI_PROGRAM" : "ALIPAY_MINI_PROGRAM" // Instance source matches what OTLP metrics use, so logs aggregate under // the same OAP instance entity when operator follows the recommended // serviceInstance == serviceVersion pattern. SDK ≥ v0.4.0 emits // service.instance.id only when init({serviceInstance: ...}) is set. // If absent, sourceAttribute() returns null/empty → TrafficSinkListener // skips the instance traffic, matching MAL's empty-dim behavior. instance sourceAttribute("service.instance.id") endpoint tag("miniprogram.page.path") tag 'platform': platform tag 'exception.type': tag("exception.type") tag 'exception.stacktrace': tag("exception.stacktrace") // ajax-specific extras, nullable tag 'http.method': tag("http.request.method") tag 'http.status': tag("http.response.status_code") tag 'server.address': tag("server.address") } sink { } }
The rule sets the layer script-side based on miniprogram.platform, so one rule file
produces two layers. Error counts per service / instance / endpoint / exception.type
can be derived via existing OAL log-metric machinery.
The SDK posts SegmentObject directly to /v3/segments (SkyWalking native protocol).
These segments are parsed by the normal trace pipeline — no new SPI is needed.
Service-layer assignment already lives in CommonAnalysisListener.identifyServiceLayer(SpanLayer) (a protected instance method
on the abstract base shared by SegmentAnalysisListener, RPCAnalysisListener, and EndpointDepFromCrossThreadAnalysisListener in the agent-analyzer module). Today it
maps SpanLayer.FAAS → Layer.FAAS and everything else to Layer.GENERAL. Extend it to
also accept the span's componentId (which the SDK already sets on every outbound span)
and dispatch to the mini-program layers.
The component name → id mapping lives in component-libraries.yml and is exposed only
at runtime via IComponentLibraryCatalogService
(ComponentLibraryCatalogService.java:84-104) — there are no auto-generated Java
constants. So the listener's abstract base resolves the two ids once at construction
time and caches them as int fields:
IComponentLibraryCatalogService is already a CoreModule service, so wiring it into
the listener factories is one constructor parameter. Each of the 5 existing call sites
in RPCAnalysisListener (×4) and EndpointDepFromCrossThreadAnalysisListener (×1)
adds span.getComponentId() to its current identifyServiceLayer(span.getSpanLayer())
call.
No new SPI, no new listener registration — all the work happens inside the existing SegmentParserListenerManager pipeline.
Persistence: default true — unlike iOS MetricKit spans (which represent 24-hour
windows and must be suppressed), mini-program segments are real outgoing HTTP spans
that belong in the trace UI.
7. Component Library Entries
Add to oap-server/server-starter/src/main/resources/component-libraries.yml, in the
JavaScript block [10000, 11000):
Status: as of mini-program-monitor v0.3.0, the SDK already emits these component
ids on every exit span. Without the OAP-side registration, current OAP releases render
them as "N/A" in topology even though the tag data is captured. Adding the two entries
to component-libraries.yml is the unblock for proper topology rendering and is what
makes the layer mapping in §6 effective.
8. SDK-Side Convention
All three originally-proposed SDK conventions are resolved as of mini-program-monitor v0.4.0 (released):
Convention
Status
Per-platform componentId on exit spans (10002 / 10003)
✅ shipped in v0.3.0
miniprogram.platform span tag on every exit span
✅ shipped in v0.3.0
Drop auto-generated per-device serviceInstance
✅ shipped in v0.4.0 — serviceInstance defaults to unset; OTLP omits service.instance.id, native segments substitute - (protocol-mandatory field)
Recommend version-scoped serviceInstance
✅ documented in SDK README / SIGNALS / SAMPLES + e2e CI pins it to service.version
The originally-imagined "default to service.version" was rejected upstream in favor
of a cleaner shape: the SDK has no opinion on what serviceInstance should be, but its
docs explicitly recommend a version-scoped value (mirroring service.version or a
release tag). When operators leave it unset, OTLP simply omits service.instance.id —
spec-allowed (it's RECOMMENDED, not REQUIRED) — and OAP aggregates at the service level.
This also means no need for a miniprogram.device span tag fallback — the device-id
problem is gone at the source. Operators that genuinely need per-session granularity
can still pass init({ serviceInstance: '<their-id>' }) themselves.
9. UI Menu and Dashboards
Menu
Extend the existing Mobile menu group (added in SWIP-11) in oap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml:
- title: Mobileicon: mobilemenus:
- title: iOSlayer: IOS...
- title: WeChat Mini Programlayer: WECHAT_MINI_PROGRAMdescription: WeChat Mini Program monitoring via mini-program-monitor SDK.documentLink: https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-wechat-mini-program-monitoring/i18nKey: wechat_mini_program
- title: Alipay Mini Programlayer: ALIPAY_MINI_PROGRAMdescription: Alipay Mini Program monitoring via mini-program-monitor SDK.documentLink: https://skywalking.apache.org/docs/main/next/en/setup/backend/backend-alipay-mini-program-monitoring/i18nKey: alipay_mini_program
Dashboards
UITemplateInitializer only loads folders listed in its hard-coded UI_TEMPLATE_FOLDER array, and the folder name is Layer.X.name().toLowerCase()
(UITemplateInitializer.java:45-88,101-103). Two changes are required:
Append the new layers to the allowlist in oap-server/server-core/src/main/java/.../UITemplateInitializer.java:
Hyphenated folder names (e.g. wechat-mini-program/) are silently skipped because
they don't match Layer.WECHAT_MINI_PROGRAM.name().toLowerCase().
Structure mirrors the iOS dashboards but adds a trace widget — because native
segments are queryable in the normal trace UI (unlike iOS's OTLP→Zipkin path).
Metric names below use per-platform prefixes from §4 (meter_wechat_mp / meter_wechat_mp_instance / meter_alipay_mp / meter_alipay_mp_instance). The
WeChat dashboard pulls from meter_wechat_mp_*; the Alipay dashboard pulls from meter_alipay_mp_*.
Per-service dashboard panels (WeChat):
Panel Group
Widgets
Notes
App Launch
meter_wechat_mp_app_launch_duration
Page Render
meter_wechat_mp_first_render_duration
first_paint.time is an epoch-ms timestamp, not aggregated by MAL — see §3
Error count by exception.type; top error endpoints
Derived from LAL-processed logs
Traces
Native trace list for the in-scope service (service list is layer-filtered upstream); endpoint trace drill-down
Mini-program only — iOS dashboards lack this because iOS traces go to Zipkin
Per-service dashboard panels (Alipay): same shape as WeChat, but only includes the
metrics Alipay actually emits (app_launch_duration, first_render_duration, request_duration_percentile, errors, traces). The Navigation row and the Page Render
row's WeChat-only first_paint mention are absent.
Per-instance (version) dashboard: same metric set scoped to the service instance —
backed by meter_wechat_mp_instance_* / meter_alipay_mp_instance_* (§4).
Per-endpoint (page) dashboard: uses the chained-.endpoint(...) per-page metrics
from §4 (endpoint_app_launch_duration, endpoint_first_render_duration, endpoint_request_duration_percentile), plus per-page error list.
UI Side
A separate PR in skywalking-booster-ui
is needed for i18n entries for the two new sub-menus.
10. Data Generator for skywalking-showcase
mini-program-monitor v0.3.0 ships a simulator ecosystem as a first-class deliverable,
not a separate harness — and it already publishes multi-arch (linux/amd64, linux/arm64)
images per commit:
skywalking-showcase consumes these
images directly — no new image to build, no driver scripts to maintain in the showcase
repo. Just two service entries pointing at OAP, e.g.:
Payload cost in the showcase: at default cadences, well below existing
Java/Python/Go showcase services. Pinning to a specific SHA / version (no :latest) is
mandated by the SDK side — the showcase manifest tracks an explicit version.
Imported Dependencies libs and their licenses
No new OAP-side dependencies. All processing uses existing OTLP receiver, native trace
receiver, OAL, LAL, and MAL infrastructure.
The mini-program-monitor SDK itself (Apache-2.0) is an external dependency of the
user's mini-program project, not of OAP. The showcase data generator images bundle the
SDK's compiled JS, same license.
Compatibility
Configuration: two new layers + a new menu section + new MAL/LAL rule files —
additive, opt-in.
Storage: no new storage structures. Uses existing trace / metric / log storage.
Protocols: no protocol changes. Uses existing OTLP and SkyWalking native receivers.
Layer mapping: the change to CommonAnalysisListener.getLayer() is additive —
it only redirects traffic carrying the two new component ids; all other segments
continue to resolve to Layer.GENERAL / Layer.FAAS as today.
Component library:10002 and 10003 are newly reserved ids in the JavaScript
range [10000, 11000); no collision with existing entries.
SDK version recommendation: mini-program-monitor ≥ v0.4.0 is the recommended
baseline. v0.3.0 also works but with the legacy instance-id behavior below.
SDK ≤ v0.2.x emits componentId = 10001 (ajax-inherited) — its segments resolve
to Layer.GENERAL and do not benefit from this SWIP's layer / topology integration.
OTLP metrics + logs still flow through MAL / LAL because they key on the miniprogram.platform resource attribute, which v0.2 already emits.
Instance entity behavior across SDK versions:
SDK ≤ v0.3.x auto-generated service.instance.id = mp-{random} per session,
creating one OAP instance entity per device — usually undesirable. Operators on
v0.3.x can avoid this by passing init({ serviceInstance: serviceVersion })
explicitly.
SDK ≥ v0.4.0 leaves service.instance.id unset by default. The three signal
pipelines then handle absence differently (see §2 "Instance coherence" table):
native segments produce a literal - instance entity; OTLP logs and metrics
create no instance entity at all. Per-instance dashboards are meaningful only
when the operator sets serviceInstance.
Recommended operator pattern (SDK docs + e2e CI): set serviceInstance to a
version-scoped value (mirroring service.version or a release tag). Then all
three signal pipelines aggregate under the same OAP instance entity.
Dashboards built against pre-v0.4 traffic see a long tail of mp-* instance ids;
after upgrade with no serviceInstance set, only the segment-side - entity
remains. Set serviceInstance to keep populated per-version dashboards.
server.address sentinel change in SDK v0.4.0: when the request URL has no
parseable https?://host prefix, OTLP now omits server.address (was "unknown")
and segments substitute - for peer. MAL queries that group / filter on server.address == "unknown" need to union the old sentinel with the new behavior
for data spanning the v0.3 → v0.4 upgrade boundary.
General usage docs
Prerequisites
Mini program instrumented with mini-program-monitor≥ v0.4.0 recommended (clean serviceInstance defaults). v0.3.0 still works with manual serviceInstance: serviceVersion workaround.
SkyWalking OAP with the changes from this SWIP — OTLP HTTP receiver enabled (default on core REST port 12800), and the two new component ids registered
Mini Program Setup
// WeChat (app.js)const{ init }=require('mini-program-monitor');App({onLaunch(){init({service: 'my-mini-program',serviceVersion: 'v1.2.0',// SDK ≥ v0.4.0 recommendation: set serviceInstance to a version-scoped value// (mirroring service.version or a release tag). Leaving it unset is fine — OAP// records the instance as the literal `-` — but per-version dashboards need this.serviceInstance: 'v1.2.0',collector: 'https://<oap-host>',platform: 'wechat',// optional — auto-detectedenable: {tracing: true},// opt-in: SkyWalking native segments});},});
// Alipay (app.js) — same API, different platform attributeinit({service: 'my-mini-program',serviceVersion: 'v1.2.0',serviceInstance: 'v1.2.0',collector: 'https://<oap-host>',platform: 'alipay',enable: {tracing: true},});
SkyWalking OAP Configuration
Append the mini-program glob to enabledOtelMetricsRules and the LAL file to lalFiles in application.yml (preserve the existing defaults — don't replace them):
miniprogram/* picks up all four MAL files under otel-rules/miniprogram/. The
existing defaults (distributed with OAP) are a long list including apisix, ios/*, kafka/*, and the default LAL rule — these must be kept; otherwise
non–mini-program workloads lose their MAL / LAL wiring.
Native trace segments (/v3/segments) need no additional config — handled by the
existing trace receiver. Layer is assigned automatically from the span's componentId.
What You'll See
Mobile > WeChat Mini Program and Mobile > Alipay Mini Program menu items
Service list per platform layer — one row per mini-program
Service dashboard — launch time, render/paint timings, request percentiles,
error counts, trace list
Instance (version) dashboard — same metrics scoped to a version, for rollout
and regression monitoring
Endpoint (page) dashboard — per-page perf + error list
Trace view — individual outgoing requests when enable.tracing = true, with sw8 propagation joining the mini-program's trace with downstream backend services
Limitations
Alipay perf metrics are lifecycle-based approximations, not native renderer
timings. Do not compare WeChat and Alipay perf numbers head-to-head.
WeChat-only metrics (first_paint_time, route_duration, script_duration, package_load_duration, pageNotFound error) are absent from Alipay dashboards.
Device-level per-user aggregation is not supported by design — serviceInstance
is intended to be a version-scoped identifier, not per-device. SDK v0.4.0 dropped
the per-device auto-generator entirely; operators who genuinely need per-session
granularity can pass any string they want via init({ serviceInstance: '…' }),
but be aware OAP aggregates one instance entity per distinct value.
WebSocket, memory-warning, and network-status-change signals are not instrumented by
the current SDK.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
SWIP-12 Support WeChat & Alipay Mini Program Monitoring
Motivation
WeChat (微信) and Alipay (支付宝) Mini Programs are among the most widely used client-side
platforms in China — many businesses ship a mini-program before (or instead of) a native
mobile app. Observability for mini-programs is as important as iOS/Android monitoring.
The SkyAPM
mini-program-monitorSDKprovides a single JavaScript client that covers both WeChat and Alipay runtimes. It emits:
pageNotFound(WeChat), HTTP failuresgauges (durations) + a
first_paint.timeepoch-ms timestamp gauge + arequest-duration delta histogram (per-flush)
SegmentObjectper sampledoutgoing request, posted to
/v3/segmentswith answ8header injected on the wireso downstream services join the same trace. As of SDK v0.3.0, every exit span carries
per-platform
componentId(10002WeChat,10003Alipay) and aminiprogram.platformtagBecause the SDK speaks standard OTLP + SkyWalking native, no new receiver is required.
This SWIP focuses on: two new layers, platform-aware MAL/LAL routing, service/instance/
endpoint entity convention, componentId-driven layer mapping for native trace segments,
menu/dashboard support, and a data generator for skywalking-showcase.
This SWIP builds on LAL
layer: auto/sourceAttribute()(SWIP-11) and the existingSegmentParserListenerManagertrace-analyzer pipeline — no new general-purposeinfrastructure is needed.
Architecture Graph
Proposed Changes
1. Two New Layers
Add in
Layer.java:Both are normal layers (
isNormal=true) — the mini-program is the observed service itself.Why two layers, not one: the two platforms expose different sets of metrics (see §3),
and users want to compare across WeChat apps or across Alipay apps separately. Using a
tag would force every query and dashboard widget to filter by platform — a layer per
platform is cleaner and mirrors how the SkyWalking UI organizes other platforms.
2. Service / Instance / Endpoint Mapping
The SDK's OTLP resource block provides three identifiers, but only two are usable as
aggregation dimensions:
service.nameservice.instance.id(recommended pattern: operator sets it toservice.version)miniprogram.page.pathWhat we deliberately drop:
service.instance.idserviceInstanceto a version-scoped value (see §8).server.address(remote host for outbound requests)miniprogram.request.duration+peeron segmentsAll three entities are needed — each answers a distinct question:
Skipping any of them loses a class of question.
Instance coherence across signals
The three signal pipelines key off different attributes by default:
service.instance.id(omitted by SDK ifserviceInstanceunset)serviceInstancefield on the segment (substituted with literal-if unset)sourceAttribute("service.instance.id")(the recommended LAL extractor — see §5)For all three to land on the same OAP instance entity, the operator must set
init({ serviceInstance: <some-string> })— recommended value isservice.versionsothe same string appears as both
service.instance.id(OTLP) and segmentserviceInstance.When
serviceInstanceis unset, the three pipelines do not uniformly fall back tothe same placeholder — they each handle absence differently:
serviceInstance-at the wire (mini-program-monitorrequest.ts:147); OAP records the instance entity literally as-.TrafficSinkListener:83short-circuits whenmetadata.serviceInstanceis empty; no instance traffic is generated.SampleFamily.dim()(SampleFamily.java:715) collapses missing labels to the empty string — the instance dimension is empty, no instance entity is built.So the unset case is not "all three aligned under
-" — segments get a-entity,logs and metrics get no instance entity at all. Operators who care about per-instance
dashboards must set
serviceInstance. This is documented as the recommended pattern inthe SDK (
README.md/SIGNALS.md) and pinned in the SDK's e2e CI.The earlier draft of this SWIP set the LAL
instancetosourceAttribute("service.version"),which would make logs disagree with metrics + traces whenever
serviceInstance != serviceVersion. §5 below sources fromservice.instance.iddirectly so when theoperator follows the recommended pattern, all three signal types share the same
instance entity.
3. Metric Coverage Per Platform
Not every signal is supported on both runtimes. MAL rules emit the same metric names
for both layers where supported; WeChat-only metrics produce data only under the
WECHAT_MINI_PROGRAMlayer.meter_miniprogram_*)app_launch_durationwx.getPerformance()(WeChat) /App.onLaunch→onShowfallback (Alipay)first_render_durationfirstRender(WeChat) /onLoad→onReady(Alipay)first_paint.time(passthrough — see note)firstPaint. Wall-clock epoch ms timestamp, not a duration. Not aggregated by MAL — surfaced only on individual page traces / log queries.route_durationnavigation/routescript_durationscriptpackage_load_durationloadPackagerequest_duration_percentile(P50–P99)miniprogram.request.durationhistogram (per-flush DELTA)error_countjs,promise,ajaxerror logs (via LAL)page_not_found_countpageNotFounderror log (nomy.onPageNotFoundhook on Alipay)Precision caveat for Alipay perf metrics: on WeChat, perf values come from the native
PerformanceObserverentries. On Alipay, the SDK falls back to lifecycle hooks(
App.onLaunch→onShow,Page.onLoad→onReady) because the Alipay base library does notexpose
PerformanceObserverentries for the same events. These are approximations of"time-to-interactive" rather than authoritative renderer timings. Dashboards should not
compare WeChat and Alipay perf values directly; this is documented in the per-platform
doc pages.
4. MAL Rules — Per-Platform × Per-Scope, Mirroring the iOS Layout
Following the iOS pattern (
otel-rules/ios/ios-metrickit.yamlfor service-scoped +ios-metrickit-instance.yamlfor instance-scoped — service-scoped meters there have noservice_instance_iddim so the "overall app health" view is genuinelyfleet-aggregated), this SWIP creates four files:
Each file has a single
expSuffix(one Layer, one entity scope) and afilterblockthat gates on
miniprogram.platformso traffic from the wrong platform is dropped atthe rule level.
wechat-mini-program.yaml— service-scopedwechat-mini-program-instance.yaml— instance-scoped (per version)alipay-mini-program.yaml/alipay-mini-program-instance.yamlMirror the WeChat files exactly, differing only in:
filter:tags['miniprogram_platform'] == 'alipay'expSuffixLayer:Layer.ALIPAY_MINI_PROGRAMmetricPrefix:meter_alipay_mp/meter_alipay_mp_instance(
route_duration,script_duration,package_load_duration)Notes
service_nameonly — noservice_instance_idfragmentation. This produces the genuine fleet-aggregated view for the "overall app
health" dashboard panels. iOS's
ios-metrickit.yamlis the precedent.expSuffix: instance(...). Thisis what backs per-release / version-regression dashboards.
service_instance_idsource: SDK ≥ v0.4.0 emits OTLPservice.instance.idonlywhen the operator passes
init({ serviceInstance: ... }). When unset, the attributeis omitted; OAP records the instance entity as the literal
-. The instance dashboardis meaningful only when operators follow the recommended
serviceInstance: serviceVersionpattern (otherwise everything aggregates under-). MAL itself canadd a fail-safe (
tag {tags -> tags.service_instance_id = tags.service_instance_id ?: tags.service_name})but standard practice is to rely on the SDK side.
.endpoint(...)chain on service-scoped files — same expression-leveloverride pattern as APISIX (
apisix.yaml:91-102) and RocketMQ. One rule emits toservice scope (default from
expSuffix), the next emits to endpoint scope bychaining
.endpoint(...)at the end.5. LAL Rules (Error Logs,
layer: autoFork by Platform)Create
oap-server/server-starter/src/main/resources/lal/miniprogram.yaml.Uses the
layer: auto+sourceAttribute()mechanism from SWIP-11:The rule sets the layer script-side based on
miniprogram.platform, so one rule fileproduces two layers. Error counts per service / instance / endpoint / exception.type
can be derived via existing OAL log-metric machinery.
6. Trace Segment Handling — Component-Driven Layer Mapping
The SDK posts
SegmentObjectdirectly to/v3/segments(SkyWalking native protocol).These segments are parsed by the normal trace pipeline — no new SPI is needed.
Service-layer assignment already lives in
CommonAnalysisListener.identifyServiceLayer(SpanLayer)(aprotectedinstance methodon the abstract base shared by
SegmentAnalysisListener,RPCAnalysisListener, andEndpointDepFromCrossThreadAnalysisListenerin the agent-analyzer module). Today itmaps
SpanLayer.FAAS → Layer.FAASand everything else toLayer.GENERAL. Extend it toalso accept the span's
componentId(which the SDK already sets on every outbound span)and dispatch to the mini-program layers.
The component name → id mapping lives in
component-libraries.ymland is exposed onlyat runtime via
IComponentLibraryCatalogService(
ComponentLibraryCatalogService.java:84-104) — there are no auto-generated Javaconstants. So the listener's abstract base resolves the two ids once at construction
time and caches them as
intfields:IComponentLibraryCatalogServiceis already aCoreModuleservice, so wiring it intothe listener factories is one constructor parameter. Each of the 5 existing call sites
in
RPCAnalysisListener(×4) andEndpointDepFromCrossThreadAnalysisListener(×1)adds
span.getComponentId()to its currentidentifyServiceLayer(span.getSpanLayer())call.
No new SPI, no new listener registration — all the work happens inside the existing
SegmentParserListenerManagerpipeline.Persistence: default
true— unlike iOS MetricKit spans (which represent 24-hourwindows and must be suppressed), mini-program segments are real outgoing HTTP spans
that belong in the trace UI.
7. Component Library Entries
Add to
oap-server/server-starter/src/main/resources/component-libraries.yml, in theJavaScript block
[10000, 11000):Status: as of mini-program-monitor v0.3.0, the SDK already emits these component
ids on every exit span. Without the OAP-side registration, current OAP releases render
them as "N/A" in topology even though the tag data is captured. Adding the two entries
to
component-libraries.ymlis the unblock for proper topology rendering and is whatmakes the layer mapping in §6 effective.
8. SDK-Side Convention
All three originally-proposed SDK conventions are resolved as of mini-program-monitor
v0.4.0 (released):
componentIdon exit spans (10002/10003)miniprogram.platformspan tag on every exit spanserviceInstanceserviceInstancedefaults to unset; OTLP omitsservice.instance.id, native segments substitute-(protocol-mandatory field)serviceInstanceservice.versionThe originally-imagined "default to
service.version" was rejected upstream in favorof a cleaner shape: the SDK has no opinion on what
serviceInstanceshould be, but itsdocs explicitly recommend a version-scoped value (mirroring
service.versionor arelease tag). When operators leave it unset, OTLP simply omits
service.instance.id—spec-allowed (it's RECOMMENDED, not REQUIRED) — and OAP aggregates at the service level.
This also means no need for a
miniprogram.devicespan tag fallback — the device-idproblem is gone at the source. Operators that genuinely need per-session granularity
can still pass
init({ serviceInstance: '<their-id>' })themselves.9. UI Menu and Dashboards
Menu
Extend the existing
Mobilemenu group (added in SWIP-11) inoap-server/server-starter/src/main/resources/ui-initialized-templates/menu.yaml:Dashboards
UITemplateInitializeronly loads folders listed in its hard-codedUI_TEMPLATE_FOLDERarray, and the folder name isLayer.X.name().toLowerCase()(
UITemplateInitializer.java:45-88,101-103). Two changes are required:oap-server/server-core/src/main/java/.../UITemplateInitializer.java:Layer.name().toLowerCase()):wechat-mini-program/) are silently skipped becausethey don't match
Layer.WECHAT_MINI_PROGRAM.name().toLowerCase().Structure mirrors the iOS dashboards but adds a trace widget — because native
segments are queryable in the normal trace UI (unlike iOS's OTLP→Zipkin path).
Metric names below use per-platform prefixes from §4 (
meter_wechat_mp/meter_wechat_mp_instance/meter_alipay_mp/meter_alipay_mp_instance). TheWeChat dashboard pulls from
meter_wechat_mp_*; the Alipay dashboard pulls frommeter_alipay_mp_*.Per-service dashboard panels (WeChat):
meter_wechat_mp_app_launch_durationmeter_wechat_mp_first_render_durationfirst_paint.timeis an epoch-ms timestamp, not aggregated by MAL — see §3meter_wechat_mp_route_duration,meter_wechat_mp_script_duration,meter_wechat_mp_package_load_durationmeter_wechat_mp_request_duration_percentile(P50/P75/P90/P95/P99)exception.type; top error endpointsPer-service dashboard panels (Alipay): same shape as WeChat, but only includes the
metrics Alipay actually emits (
app_launch_duration,first_render_duration,request_duration_percentile, errors, traces). The Navigation row and the Page Renderrow's WeChat-only
first_paintmention are absent.Per-instance (version) dashboard: same metric set scoped to the service instance —
backed by
meter_wechat_mp_instance_*/meter_alipay_mp_instance_*(§4).Per-endpoint (page) dashboard: uses the chained-
.endpoint(...)per-page metricsfrom §4 (
endpoint_app_launch_duration,endpoint_first_render_duration,endpoint_request_duration_percentile), plus per-page error list.UI Side
A separate PR in skywalking-booster-ui
is needed for i18n entries for the two new sub-menus.
10. Data Generator for skywalking-showcase
mini-program-monitor v0.3.0 ships a simulator ecosystem as a first-class deliverable,
not a separate harness — and it already publishes multi-arch (
linux/amd64,linux/arm64)images per commit:
ghcr.io/skyapm/mini-program-monitor/sim-wechat:<sha-or-version>ghcr.io/skyapm/mini-program-monitor/sim-alipay:<sha-or-version>skywalking-showcase consumes these
images directly — no new image to build, no driver scripts to maintain in the showcase
repo. Just two service entries pointing at OAP, e.g.:
Run modes (env
MODE):loop(forever, for demo),timed(forDURATION_MSthenexit),
once(one of each signal then exit, for CI parity).Scenarios (env
SCENARIO):demo(healthy + all four error surfaces),baseline(steady happy stream),
error-storm(high error rate + 5xx),slow-api(heavy taillatency).
Payload cost in the showcase: at default cadences, well below existing
Java/Python/Go showcase services. Pinning to a specific SHA / version (no
:latest) ismandated by the SDK side — the showcase manifest tracks an explicit version.
Imported Dependencies libs and their licenses
No new OAP-side dependencies. All processing uses existing OTLP receiver, native trace
receiver, OAL, LAL, and MAL infrastructure.
The mini-program-monitor SDK itself (Apache-2.0) is an external dependency of the
user's mini-program project, not of OAP. The showcase data generator images bundle the
SDK's compiled JS, same license.
Compatibility
additive, opt-in.
CommonAnalysisListener.getLayer()is additive —it only redirects traffic carrying the two new component ids; all other segments
continue to resolve to
Layer.GENERAL/Layer.FAASas today.10002and10003are newly reserved ids in the JavaScriptrange
[10000, 11000); no collision with existing entries.baseline. v0.3.0 also works but with the legacy instance-id behavior below.
componentId = 10001(ajax-inherited) — its segments resolveto
Layer.GENERALand do not benefit from this SWIP's layer / topology integration.OTLP metrics + logs still flow through MAL / LAL because they key on the
miniprogram.platformresource attribute, which v0.2 already emits.service.instance.id = mp-{random}per session,creating one OAP instance entity per device — usually undesirable. Operators on
v0.3.x can avoid this by passing
init({ serviceInstance: serviceVersion })explicitly.
service.instance.idunset by default. The three signalpipelines then handle absence differently (see §2 "Instance coherence" table):
native segments produce a literal
-instance entity; OTLP logs and metricscreate no instance entity at all. Per-instance dashboards are meaningful only
when the operator sets
serviceInstance.serviceInstanceto aversion-scoped value (mirroring
service.versionor a release tag). Then allthree signal pipelines aggregate under the same OAP instance entity.
mp-*instance ids;after upgrade with no
serviceInstanceset, only the segment-side-entityremains. Set
serviceInstanceto keep populated per-version dashboards.server.addresssentinel change in SDK v0.4.0: when the request URL has noparseable
https?://hostprefix, OTLP now omitsserver.address(was"unknown")and segments substitute
-forpeer. MAL queries that group / filter onserver.address == "unknown"need to union the old sentinel with the new behaviorfor data spanning the v0.3 → v0.4 upgrade boundary.
General usage docs
Prerequisites
serviceInstancedefaults). v0.3.0 still works with manualserviceInstance: serviceVersionworkaround.Mini Program Setup
SkyWalking OAP Configuration
Append the mini-program glob to
enabledOtelMetricsRulesand the LAL file tolalFilesinapplication.yml(preserve the existing defaults — don't replace them):miniprogram/*picks up all four MAL files underotel-rules/miniprogram/. Theexisting defaults (distributed with OAP) are a long list including
apisix,ios/*,kafka/*, and thedefaultLAL rule — these must be kept; otherwisenon–mini-program workloads lose their MAL / LAL wiring.
Native trace segments (
/v3/segments) need no additional config — handled by theexisting trace receiver. Layer is assigned automatically from the span's componentId.
What You'll See
error counts, trace list
and regression monitoring
enable.tracing = true, withsw8propagation joining the mini-program's trace with downstream backend servicesLimitations
timings. Do not compare WeChat and Alipay perf numbers head-to-head.
first_paint_time,route_duration,script_duration,package_load_duration,pageNotFounderror) are absent from Alipay dashboards.serviceInstanceis intended to be a version-scoped identifier, not per-device. SDK v0.4.0 dropped
the per-device auto-generator entirely; operators who genuinely need per-session
granularity can pass any string they want via
init({ serviceInstance: '…' }),but be aware OAP aggregates one instance entity per distinct value.
the current SDK.
Beta Was this translation helpful? Give feedback.
All reactions