@decocms/blocks 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +97 -0
- package/src/cms/applySectionConventions.ts +128 -0
- package/src/cms/blockSource.test.ts +67 -0
- package/src/cms/blockSource.ts +124 -0
- package/src/cms/index.ts +104 -0
- package/src/cms/layoutCacheRace.test.ts +146 -0
- package/src/cms/loadDecofileDirectory.test.ts +52 -0
- package/src/cms/loadDecofileDirectory.ts +48 -0
- package/src/cms/loader.test.ts +184 -0
- package/src/cms/loader.ts +284 -0
- package/src/cms/registry.test.ts +118 -0
- package/src/cms/registry.ts +252 -0
- package/src/cms/resolve.test.ts +547 -0
- package/src/cms/resolve.ts +2003 -0
- package/src/cms/schema.ts +993 -0
- package/src/cms/sectionLoaders.test.ts +409 -0
- package/src/cms/sectionLoaders.ts +562 -0
- package/src/cms/sectionMixins.test.ts +163 -0
- package/src/cms/sectionMixins.ts +153 -0
- package/src/hooks/LazySection.tsx +121 -0
- package/src/hooks/LiveControls.tsx +122 -0
- package/src/hooks/RenderSection.test.tsx +81 -0
- package/src/hooks/RenderSection.tsx +91 -0
- package/src/hooks/SectionErrorFallback.tsx +97 -0
- package/src/hooks/index.ts +4 -0
- package/src/index.ts +8 -0
- package/src/matchers/builtins.test.ts +251 -0
- package/src/matchers/builtins.ts +437 -0
- package/src/matchers/countryNames.ts +104 -0
- package/src/matchers/override.test.ts +205 -0
- package/src/matchers/override.ts +136 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +133 -0
- package/src/middleware/hydrationContext.test.ts +61 -0
- package/src/middleware/hydrationContext.ts +79 -0
- package/src/middleware/index.ts +88 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.test.ts +238 -0
- package/src/middleware/observability.ts +620 -0
- package/src/middleware/validateSection.test.ts +147 -0
- package/src/middleware/validateSection.ts +100 -0
- package/src/sdk/abTesting.test.ts +326 -0
- package/src/sdk/abTesting.ts +499 -0
- package/src/sdk/analytics.ts +77 -0
- package/src/sdk/cacheHeaders.test.ts +115 -0
- package/src/sdk/cacheHeaders.ts +424 -0
- package/src/sdk/cachedLoader.ts +364 -0
- package/src/sdk/clx.ts +5 -0
- package/src/sdk/cn.test.ts +34 -0
- package/src/sdk/cn.ts +28 -0
- package/src/sdk/composite.test.ts +121 -0
- package/src/sdk/composite.ts +114 -0
- package/src/sdk/cookie.test.ts +108 -0
- package/src/sdk/cookie.ts +129 -0
- package/src/sdk/crypto.ts +185 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/djb2.ts +20 -0
- package/src/sdk/encoding.test.ts +71 -0
- package/src/sdk/encoding.ts +47 -0
- package/src/sdk/env.ts +31 -0
- package/src/sdk/http.test.ts +71 -0
- package/src/sdk/http.ts +124 -0
- package/src/sdk/index.ts +90 -0
- package/src/sdk/inflightTimeout.test.ts +35 -0
- package/src/sdk/inflightTimeout.ts +53 -0
- package/src/sdk/instrumentedFetch.test.ts +559 -0
- package/src/sdk/instrumentedFetch.ts +339 -0
- package/src/sdk/invoke.test.ts +115 -0
- package/src/sdk/invoke.ts +260 -0
- package/src/sdk/logger.test.ts +432 -0
- package/src/sdk/logger.ts +304 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/normalizeUrls.ts +91 -0
- package/src/sdk/observability.ts +109 -0
- package/src/sdk/otel.test.ts +526 -0
- package/src/sdk/otel.ts +981 -0
- package/src/sdk/otelAdapters/clickhouseCollector.ts +65 -0
- package/src/sdk/otelAdapters.test.ts +89 -0
- package/src/sdk/otelAdapters.ts +144 -0
- package/src/sdk/otelHttpLog.test.ts +457 -0
- package/src/sdk/otelHttpLog.ts +419 -0
- package/src/sdk/otelHttpMeter.test.ts +292 -0
- package/src/sdk/otelHttpMeter.ts +506 -0
- package/src/sdk/otelHttpTracer.test.ts +474 -0
- package/src/sdk/otelHttpTracer.ts +543 -0
- package/src/sdk/redirects.ts +225 -0
- package/src/sdk/requestContext.ts +281 -0
- package/src/sdk/requestContextStorage.browser.test.ts +29 -0
- package/src/sdk/requestContextStorage.browser.ts +43 -0
- package/src/sdk/requestContextStorage.ts +35 -0
- package/src/sdk/retry.ts +45 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +42 -0
- package/src/sdk/sitemap.ts +160 -0
- package/src/sdk/urlRedaction.test.ts +73 -0
- package/src/sdk/urlRedaction.ts +82 -0
- package/src/sdk/urlUtils.ts +134 -0
- package/src/sdk/useDevice.test.ts +130 -0
- package/src/sdk/useDevice.ts +109 -0
- package/src/sdk/useDeviceContext.tsx +108 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.test.ts +128 -0
- package/src/sdk/useScript.ts +210 -0
- package/src/sdk/useSuggestions.test.ts +230 -0
- package/src/sdk/useSuggestions.ts +188 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/setup.ts +119 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +14 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder for a future OTel-collector based exporter that ships
|
|
3
|
+
* logs / metrics / traces from this Worker to a co-deployed OTel Collector
|
|
4
|
+
* gateway, which in turn batches into ClickHouse.
|
|
5
|
+
*
|
|
6
|
+
* **NOT IMPLEMENTED in 5.0.0.** The framework today defaults to CF-native
|
|
7
|
+
* observability: logs and traces appear in the Cloudflare dashboard
|
|
8
|
+
* (`observability.{logs,traces}` in `wrangler.jsonc`), and metrics go to
|
|
9
|
+
* Workers Analytics Engine. When the platform-side ClickHouse + collector
|
|
10
|
+
* deployment lands, this file gets the real OTLP/HTTP exporter wiring back
|
|
11
|
+
* — mirror the implementation that lived in `src/sdk/otelAdapters.ts` and
|
|
12
|
+
* `src/sdk/sampler.ts` at the pre-5.0.0 git tag.
|
|
13
|
+
*
|
|
14
|
+
* The expected shape (`endpoint`, optional `headers`, optional `resource`,
|
|
15
|
+
* `exportIntervalMillis`) is recorded here so site-side bootstrapping code
|
|
16
|
+
* can compile against the future surface today and we don't churn the
|
|
17
|
+
* `OtelOptions` type when the implementation lands.
|
|
18
|
+
*
|
|
19
|
+
* Reference: https://clickhouse.com/docs/observability/integrating-opentelemetry
|
|
20
|
+
*
|
|
21
|
+
* Expected wiring once implemented:
|
|
22
|
+
* - Worker → OTLP/HTTP → OTel Collector (gateway, per region)
|
|
23
|
+
* - Collector pipelines:
|
|
24
|
+
* receivers[otlp] → processors[batch, memory_limiter] → exporters[clickhouse]
|
|
25
|
+
* - The Worker uses the collector URL as `endpoint`, NOT a direct
|
|
26
|
+
* ClickHouse URL. The collector owns the credentials, retry policy,
|
|
27
|
+
* and schema mapping.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface ClickhouseCollectorOptions {
|
|
31
|
+
/**
|
|
32
|
+
* OTel Collector OTLP/HTTP endpoint, e.g. `https://otel-collector.internal`.
|
|
33
|
+
* The Worker never talks to ClickHouse directly — it talks to the collector.
|
|
34
|
+
*/
|
|
35
|
+
endpoint: string;
|
|
36
|
+
/** Optional bearer token / mTLS identity headers — collector-defined. */
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* Resource attributes to stamp on every emitted record. Keep narrow
|
|
40
|
+
* (`service.name`, `deco.runtime.version`, `deployment.environment`) —
|
|
41
|
+
* higher-cardinality attributes belong on individual spans / log records.
|
|
42
|
+
*/
|
|
43
|
+
resource?: Record<string, string>;
|
|
44
|
+
/** Periodic metric reader push interval in ms. Defaults to 60_000 once wired. */
|
|
45
|
+
exportIntervalMillis?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns nothing today — throws to make accidental usage loud and obvious.
|
|
50
|
+
*
|
|
51
|
+
* The intended return shape is `{ logger: LoggerAdapter, meter: MeterAdapter,
|
|
52
|
+
* tracer: TracerAdapter, flush: () => Promise<void> }` so the wiring inside
|
|
53
|
+
* `instrumentWorker` can compose this with the AE meter (dual-emit) and the
|
|
54
|
+
* default console logger. None of that ships in 5.0.0.
|
|
55
|
+
*/
|
|
56
|
+
export function createClickhouseCollectorAdapter(_options: ClickhouseCollectorOptions): never {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"createClickhouseCollectorAdapter is not implemented in @decocms/start@5.x. " +
|
|
59
|
+
"ClickHouse + OTel-collector integration is on the roadmap. " +
|
|
60
|
+
"Until then, use Cloudflare-native observability " +
|
|
61
|
+
"(observability.{logs,traces} in wrangler.jsonc) plus the Workers " +
|
|
62
|
+
"Analytics Engine meter wired by instrumentWorker(). Track progress " +
|
|
63
|
+
"at https://github.com/decocms/deco-start/issues.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coverage for `createAnalyticsEngineMeterAdapter` and the
|
|
3
|
+
* `setRuntimeEnv` / `getRuntimeEnv` helpers.
|
|
4
|
+
*
|
|
5
|
+
* As of 5.0.0 this file no longer covers `createOtelLoggerAdapter` /
|
|
6
|
+
* `createOtelMeterAdapter` / `flushOtelProviders` — those exporters were
|
|
7
|
+
* removed when the framework converged on Cloudflare-native log + trace
|
|
8
|
+
* capture. AE remains the in-Worker metrics path.
|
|
9
|
+
*/
|
|
10
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
11
|
+
import { createAnalyticsEngineMeterAdapter, setRuntimeEnv } from "./otelAdapters";
|
|
12
|
+
import { RequestContext } from "./requestContext";
|
|
13
|
+
|
|
14
|
+
describe("createAnalyticsEngineMeterAdapter", () => {
|
|
15
|
+
afterEach(() => vi.restoreAllMocks());
|
|
16
|
+
|
|
17
|
+
it("no-ops when binding is missing (does not throw)", () => {
|
|
18
|
+
const meter = createAnalyticsEngineMeterAdapter({ bindingName: "MISSING" });
|
|
19
|
+
expect(() => meter.counterInc("foo", 1)).not.toThrow();
|
|
20
|
+
expect(() => meter.gaugeSet?.("bar", 5)).not.toThrow();
|
|
21
|
+
expect(() => meter.histogramRecord?.("baz", 100)).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("writes one data point per call to the resolved binding", () => {
|
|
25
|
+
const writeDataPoint = vi.fn();
|
|
26
|
+
const binding = { writeDataPoint };
|
|
27
|
+
|
|
28
|
+
const meter = createAnalyticsEngineMeterAdapter({ binding });
|
|
29
|
+
|
|
30
|
+
meter.counterInc("deco.cache.hits", 1, { profile: "product" });
|
|
31
|
+
meter.histogramRecord?.("http.server.request.duration", 150, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
path: "/",
|
|
34
|
+
status: 200,
|
|
35
|
+
});
|
|
36
|
+
meter.gaugeSet?.("custom_gauge", 7, { region: "gru" });
|
|
37
|
+
|
|
38
|
+
expect(writeDataPoint).toHaveBeenCalledTimes(3);
|
|
39
|
+
|
|
40
|
+
// Non-HTTP counter falls back to the metric name as the index.
|
|
41
|
+
expect(writeDataPoint.mock.calls[0]?.[0].indexes).toEqual(["deco.cache.hits"]);
|
|
42
|
+
expect(writeDataPoint.mock.calls[0]?.[0].doubles).toEqual([1]);
|
|
43
|
+
expect(writeDataPoint.mock.calls[0]?.[0].blobs?.[0]).toBe("deco.cache.hits");
|
|
44
|
+
|
|
45
|
+
// For canonical HTTP server histogram, indexes[0] must be the path.
|
|
46
|
+
expect(writeDataPoint.mock.calls[1]?.[0].indexes).toEqual(["/"]);
|
|
47
|
+
expect(writeDataPoint.mock.calls[1]?.[0].doubles).toEqual([150]);
|
|
48
|
+
|
|
49
|
+
// Non-HTTP metrics fall back to the metric name as the index.
|
|
50
|
+
expect(writeDataPoint.mock.calls[2]?.[0].indexes).toEqual(["custom_gauge"]);
|
|
51
|
+
expect(writeDataPoint.mock.calls[2]?.[0].doubles).toEqual([7]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("resolves binding via RequestContext when not provided directly", () => {
|
|
55
|
+
const writeDataPoint = vi.fn();
|
|
56
|
+
const env = { DECO_METRICS: { writeDataPoint } };
|
|
57
|
+
|
|
58
|
+
RequestContext.run(new Request("https://x.example/"), () => {
|
|
59
|
+
setRuntimeEnv(env);
|
|
60
|
+
const meter = createAnalyticsEngineMeterAdapter();
|
|
61
|
+
// Canonical HTTP server histogram → path is promoted to index[0].
|
|
62
|
+
meter.histogramRecord?.("http.server.request.duration", 1, { path: "/x" });
|
|
63
|
+
expect(writeDataPoint).toHaveBeenCalledOnce();
|
|
64
|
+
expect(writeDataPoint.mock.calls[0]?.[0].indexes).toEqual(["/x"]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("never throws when binding throws", () => {
|
|
69
|
+
const binding = {
|
|
70
|
+
writeDataPoint: () => {
|
|
71
|
+
throw new Error("AE down");
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const meter = createAnalyticsEngineMeterAdapter({ binding });
|
|
75
|
+
expect(() => meter.counterInc("x", 1)).not.toThrow();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("orders blob columns deterministically by sorted label keys", () => {
|
|
79
|
+
const writeDataPoint = vi.fn();
|
|
80
|
+
const binding = { writeDataPoint };
|
|
81
|
+
const meter = createAnalyticsEngineMeterAdapter({ binding });
|
|
82
|
+
|
|
83
|
+
meter.counterInc("metric", 1, { z: "last", a: "first", m: "mid" });
|
|
84
|
+
|
|
85
|
+
const blobs = writeDataPoint.mock.calls[0]?.[0].blobs as string[];
|
|
86
|
+
// blobs[0] is metric name; remaining must follow sorted label-key order: a, m, z
|
|
87
|
+
expect(blobs).toEqual(["metric", "first", "mid", "last"]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metric adapters for `@decocms/start`.
|
|
3
|
+
*
|
|
4
|
+
* The OTLP logger and OTLP meter adapters that lived here through 4.x were
|
|
5
|
+
* removed in 5.0.0. The framework now relies on:
|
|
6
|
+
* - Cloudflare's platform-managed log capture (`console.*` → CF dashboard
|
|
7
|
+
* via `observability.logs`) for log shipping
|
|
8
|
+
* - Cloudflare's auto-instrumented + global-tracer-bridged trace export
|
|
9
|
+
* (`observability.traces`) for trace shipping
|
|
10
|
+
* - Workers Analytics Engine — implemented in this file — for metrics
|
|
11
|
+
*
|
|
12
|
+
* Forwarding to a co-deployed OTel collector (the path to ClickHouse)
|
|
13
|
+
* lives in `./otelAdapters/clickhouseCollector.ts`. That file is a
|
|
14
|
+
* documented stub today; the implementation will reintroduce a thin
|
|
15
|
+
* OTLP/HTTP exporter aimed at the collector — never directly at a
|
|
16
|
+
* destination — when the collector is deployed.
|
|
17
|
+
*
|
|
18
|
+
* The `setRuntimeEnv` / `getRuntimeEnv` helpers stay here because
|
|
19
|
+
* `workerEntry.ts` and the AE adapter both depend on them.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { MeterAdapter } from "../middleware/observability";
|
|
23
|
+
import { MetricNames } from "../middleware/observability";
|
|
24
|
+
import { RequestContext } from "./requestContext";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Env / binding access
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Per-request access to the Cloudflare Worker `env` bag. Stashed by
|
|
32
|
+
* `workerEntry.ts` at the top of every request via
|
|
33
|
+
* `RequestContext.setBag("__deco_env", env)`.
|
|
34
|
+
*
|
|
35
|
+
* Adapters use this to look up the AE binding (or any future binding-driven
|
|
36
|
+
* destination) without forcing site code to thread `env` through every
|
|
37
|
+
* call site.
|
|
38
|
+
*/
|
|
39
|
+
const ENV_BAG_KEY = "__deco_env";
|
|
40
|
+
|
|
41
|
+
export function setRuntimeEnv(env: Record<string, unknown>): void {
|
|
42
|
+
RequestContext.setBag(ENV_BAG_KEY, env);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getRuntimeEnv(): Record<string, unknown> | undefined {
|
|
46
|
+
return RequestContext.getBag<Record<string, unknown>>(ENV_BAG_KEY);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// AnalyticsEngineMeterAdapter
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Workers Analytics Engine binding shape. Defined here so we don't need
|
|
55
|
+
* `@cloudflare/workers-types` as a dep.
|
|
56
|
+
*/
|
|
57
|
+
export interface AnalyticsEngineDataset {
|
|
58
|
+
writeDataPoint(point: { indexes?: string[]; blobs?: string[]; doubles?: number[] }): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AnalyticsEngineMeterAdapterOptions {
|
|
62
|
+
/** Env var name holding the AE binding. Defaults to "DECO_METRICS". */
|
|
63
|
+
bindingName?: string;
|
|
64
|
+
/** Pre-resolved binding (mainly for tests). Bypasses RequestContext lookup. */
|
|
65
|
+
binding?: AnalyticsEngineDataset;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Writes one Analytics Engine data point per metric call.
|
|
70
|
+
*
|
|
71
|
+
* Schema (must stay stable — dashboards and SQL queries depend on it):
|
|
72
|
+
* - `indexes[0]`: a low-cardinality dimension. For request metrics that's
|
|
73
|
+
* the normalized URL path; for cache/resolve metrics that's the metric
|
|
74
|
+
* name itself.
|
|
75
|
+
* - `blobs[0]`: metric name (so a single dataset can hold many metrics).
|
|
76
|
+
* - `blobs[1..]`: stringified label values, in stable label-name order.
|
|
77
|
+
* - `doubles[0]`: the metric value (count, ms, gauge value).
|
|
78
|
+
*
|
|
79
|
+
* AE truncates / charges per data point, so this adapter is intentionally
|
|
80
|
+
* coarse: one point per `counterInc` / `histogramRecord` / `gaugeSet`.
|
|
81
|
+
*/
|
|
82
|
+
export function createAnalyticsEngineMeterAdapter(
|
|
83
|
+
options: AnalyticsEngineMeterAdapterOptions = {},
|
|
84
|
+
): MeterAdapter {
|
|
85
|
+
const bindingName = options.bindingName ?? "DECO_METRICS";
|
|
86
|
+
|
|
87
|
+
function resolveBinding(): AnalyticsEngineDataset | null {
|
|
88
|
+
if (options.binding) return options.binding;
|
|
89
|
+
const env = getRuntimeEnv();
|
|
90
|
+
const b = env?.[bindingName] as AnalyticsEngineDataset | undefined;
|
|
91
|
+
return b ?? null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function write(name: string, value: number, labels?: Record<string, unknown>): void {
|
|
95
|
+
const binding = resolveBinding();
|
|
96
|
+
if (!binding) return; // No-op when binding is missing — never throw.
|
|
97
|
+
|
|
98
|
+
const indexValue = pickIndex(name, labels);
|
|
99
|
+
const blobs: string[] = [name];
|
|
100
|
+
if (labels) {
|
|
101
|
+
// Stable order — sort keys so the same blob index always means the
|
|
102
|
+
// same label across requests. AE has no schema; we enforce one here.
|
|
103
|
+
const keys = Object.keys(labels).sort();
|
|
104
|
+
for (const k of keys) {
|
|
105
|
+
const v = labels[k];
|
|
106
|
+
if (v !== undefined && v !== null) blobs.push(String(v));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
binding.writeDataPoint({
|
|
112
|
+
indexes: indexValue ? [indexValue] : undefined,
|
|
113
|
+
blobs,
|
|
114
|
+
doubles: [value],
|
|
115
|
+
});
|
|
116
|
+
} catch {
|
|
117
|
+
/* AE write failed — never fail the request */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
counterInc(name, value, labels) {
|
|
123
|
+
write(name, value ?? 1, labels);
|
|
124
|
+
},
|
|
125
|
+
histogramRecord(name, value, labels) {
|
|
126
|
+
write(name, value, labels);
|
|
127
|
+
},
|
|
128
|
+
gaugeSet(name, value, labels) {
|
|
129
|
+
write(name, value, labels);
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function pickIndex(metricName: string, labels?: Record<string, unknown>): string | undefined {
|
|
135
|
+
if (!labels) return metricName;
|
|
136
|
+
// For HTTP request metrics the natural index is the path (matches the
|
|
137
|
+
// schema: `indexes[0]=path`). For other metrics, fall back to the metric
|
|
138
|
+
// name so the dataset is always queryable by index.
|
|
139
|
+
if (metricName === MetricNames.HTTP_SERVER_REQUEST_DURATION) {
|
|
140
|
+
const p = labels.path;
|
|
141
|
+
if (typeof p === "string" && p.length > 0) return p;
|
|
142
|
+
}
|
|
143
|
+
return metricName;
|
|
144
|
+
}
|