brass-runtime 1.16.1 → 1.17.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/README.md +4 -5
- package/dist/{chunk-MT3OWDPC.mjs → chunk-4P2HHGAX.mjs} +28 -0
- package/dist/{chunk-HLWLMW2F.mjs → chunk-6RY2FFN4.mjs} +2 -2
- package/dist/{chunk-BABBZK4Y.js → chunk-7X3K5RMS.js} +2 -2
- package/dist/{chunk-VN44DYYT.cjs → chunk-7ZPEZ57L.cjs} +4 -4
- package/dist/{chunk-CIZFIMK5.js → chunk-BKK77SBA.js} +28 -0
- package/dist/{chunk-76YMRMH2.cjs → chunk-F6XWZQY4.cjs} +25 -25
- package/dist/{chunk-DNFO2EIZ.mjs → chunk-SK7UZRNI.mjs} +1 -1
- package/dist/{chunk-AVNQLJ5V.js → chunk-VWIPB6I5.js} +1 -1
- package/dist/{chunk-ENKODRU3.cjs → chunk-WBGRHGBP.cjs} +29 -1
- package/dist/http/index.cjs +4 -4
- package/dist/http/index.d.ts +1 -1
- package/dist/http/index.js +1 -1
- package/dist/http/index.mjs +1 -1
- package/dist/observability/index.cjs +5 -3
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.mjs +4 -2
- package/dist/perf/cli.cjs +18 -18
- package/dist/perf/cli.js +3 -3
- package/dist/perf/cli.mjs +3 -3
- package/dist/perf/index.cjs +5 -5
- package/dist/perf/index.js +3 -3
- package/dist/perf/index.mjs +3 -3
- package/dist/{server-GJPg8ZSG.d.ts → server-D6JZ15_e.d.ts} +12 -1
- package/docs/README.md +2 -0
- package/docs/ai/PUBLIC_API.md +3 -0
- package/docs/framework-integrations.md +38 -0
- package/docs/frameworks/angular.md +153 -0
- package/docs/frameworks/express.md +125 -0
- package/docs/frameworks/fastify.md +124 -0
- package/docs/frameworks/nestjs.md +282 -0
- package/docs/frameworks/nextjs.md +147 -0
- package/docs/frameworks/react.md +139 -0
- package/docs/frameworks/vanilla.md +224 -0
- package/docs/nestjs.md +6 -0
- package/docs/observability-framework-examples.md +12 -0
- package/docs/observability.md +107 -0
- package/package.json +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Vanilla integration
|
|
2
|
+
|
|
3
|
+
Use this when an app does not have a framework-level DI or lifecycle system.
|
|
4
|
+
The same shape works for plain browser TypeScript, Node HTTP handlers, CLIs,
|
|
5
|
+
and small services.
|
|
6
|
+
|
|
7
|
+
## Browser
|
|
8
|
+
|
|
9
|
+
Browser apps should send telemetry to a same-origin proxy. Do not ship Grafana
|
|
10
|
+
Cloud or collector credentials to the client.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// brass.browser.ts
|
|
14
|
+
import { Runtime } from "brass-runtime/core";
|
|
15
|
+
import {
|
|
16
|
+
defineHttpPolicyPresets,
|
|
17
|
+
makeDefaultHttpClient,
|
|
18
|
+
} from "brass-runtime/http";
|
|
19
|
+
import {
|
|
20
|
+
makeObservability,
|
|
21
|
+
makeOtlpOptions,
|
|
22
|
+
withHttpObservability,
|
|
23
|
+
} from "brass-runtime/observability";
|
|
24
|
+
|
|
25
|
+
const policyPresets = defineHttpPolicyPresets({
|
|
26
|
+
readModel: {
|
|
27
|
+
lane: "read-model",
|
|
28
|
+
priority: 3,
|
|
29
|
+
retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
|
|
30
|
+
},
|
|
31
|
+
command: {
|
|
32
|
+
lane: "command",
|
|
33
|
+
priority: 1,
|
|
34
|
+
retry: false,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const brass = (() => {
|
|
39
|
+
const observability = makeObservability({
|
|
40
|
+
serviceName: "shop-web",
|
|
41
|
+
resource: { "deployment.environment": "browser" },
|
|
42
|
+
logs: false,
|
|
43
|
+
sampling: { ratio: 0.1, respectRemoteSampled: true, forceSampleOnError: true },
|
|
44
|
+
redaction: {},
|
|
45
|
+
cardinality: { maxValuesPerLabel: 100 },
|
|
46
|
+
otlp: makeOtlpOptions({
|
|
47
|
+
endpoint: "/api/otel",
|
|
48
|
+
timeoutMs: 10_000,
|
|
49
|
+
retry: { attempts: 2, initialDelayMs: 100, maxDelayMs: 1_000 },
|
|
50
|
+
pipeline: { maxQueueSize: 2_000, batchSize: 128, dropPolicy: "drop-oldest" },
|
|
51
|
+
}),
|
|
52
|
+
flushIntervalMs: 15_000,
|
|
53
|
+
autoStart: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const runtime = new Runtime({
|
|
57
|
+
env: observability.env,
|
|
58
|
+
hooks: observability.hooks,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const http = makeDefaultHttpClient({
|
|
62
|
+
baseUrl: "/api",
|
|
63
|
+
preset: "balanced",
|
|
64
|
+
timeoutMs: 5_000,
|
|
65
|
+
policyPresets,
|
|
66
|
+
middleware: [withHttpObservability(observability)],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
observability,
|
|
71
|
+
runtime,
|
|
72
|
+
http,
|
|
73
|
+
shutdown: async () => {
|
|
74
|
+
await http.shutdown();
|
|
75
|
+
await observability.shutdown();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
})();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use it from ordinary browser code:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
type User = {
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly name: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export async function loadCurrentUser(): Promise<User> {
|
|
90
|
+
const response = await brass.runtime.toPromise(
|
|
91
|
+
brass.http.getJson<User>("/users/me", {
|
|
92
|
+
policy: "readModel",
|
|
93
|
+
timeoutMs: 2_000,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return response.body;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
window.addEventListener("pagehide", () => {
|
|
101
|
+
void brass.shutdown();
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Node
|
|
106
|
+
|
|
107
|
+
On the server, Brass can send directly to a collector because credentials stay
|
|
108
|
+
inside the trusted process.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// brass.node.ts
|
|
112
|
+
import { Runtime } from "brass-runtime/core";
|
|
113
|
+
import {
|
|
114
|
+
defineHttpPolicyPresets,
|
|
115
|
+
makeDefaultHttpClient,
|
|
116
|
+
} from "brass-runtime/http";
|
|
117
|
+
import {
|
|
118
|
+
makeObservability,
|
|
119
|
+
makeOtlpOptions,
|
|
120
|
+
withHttpObservability,
|
|
121
|
+
} from "brass-runtime/observability";
|
|
122
|
+
|
|
123
|
+
const policyPresets = defineHttpPolicyPresets({
|
|
124
|
+
readModel: {
|
|
125
|
+
lane: "read-model",
|
|
126
|
+
priority: 3,
|
|
127
|
+
retry: { maxRetries: 2, baseDelayMs: 100, maxDelayMs: 1_000 },
|
|
128
|
+
},
|
|
129
|
+
command: {
|
|
130
|
+
lane: "command",
|
|
131
|
+
priority: 1,
|
|
132
|
+
retry: false,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const brass = (() => {
|
|
137
|
+
const observability = makeObservability({
|
|
138
|
+
serviceName: process.env.OTEL_SERVICE_NAME ?? "vanilla-node",
|
|
139
|
+
serviceVersion: process.env.OTEL_SERVICE_VERSION,
|
|
140
|
+
resource: {
|
|
141
|
+
"deployment.environment": process.env.NODE_ENV ?? "development",
|
|
142
|
+
},
|
|
143
|
+
logs: { minLevel: "info" },
|
|
144
|
+
sampling: { ratio: 0.25, respectRemoteSampled: true, forceSampleOnError: true },
|
|
145
|
+
redaction: {},
|
|
146
|
+
cardinality: { maxValuesPerLabel: 100 },
|
|
147
|
+
otlp: makeOtlpOptions({
|
|
148
|
+
endpoint: process.env.GRAFANA_OTLP_ENDPOINT ?? "http://grafana-alloy:4318",
|
|
149
|
+
headers: process.env.GRAFANA_OTLP_AUTHORIZATION
|
|
150
|
+
? { Authorization: process.env.GRAFANA_OTLP_AUTHORIZATION }
|
|
151
|
+
: undefined,
|
|
152
|
+
timeoutMs: 10_000,
|
|
153
|
+
retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
|
|
154
|
+
pipeline: {
|
|
155
|
+
maxQueueSize: 10_000,
|
|
156
|
+
batchSize: 512,
|
|
157
|
+
dropPolicy: "drop-oldest",
|
|
158
|
+
shutdownTimeoutMs: 10_000,
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
flushIntervalMs: 10_000,
|
|
162
|
+
autoStart: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const runtime = new Runtime({
|
|
166
|
+
env: observability.env,
|
|
167
|
+
hooks: observability.hooks,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const http = makeDefaultHttpClient({
|
|
171
|
+
baseUrl: process.env.API_BASE_URL ?? "https://api.internal",
|
|
172
|
+
preset: "production",
|
|
173
|
+
timeoutMs: 5_000,
|
|
174
|
+
policyPresets,
|
|
175
|
+
middleware: [withHttpObservability(observability)],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
observability,
|
|
180
|
+
runtime,
|
|
181
|
+
http,
|
|
182
|
+
shutdown: async () => {
|
|
183
|
+
await http.shutdown();
|
|
184
|
+
await observability.shutdown();
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
})();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Use the Node request adapter when you receive `IncomingMessage`-like requests:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { createServer } from "node:http";
|
|
194
|
+
import {
|
|
195
|
+
makeNodeRequestObservabilityContext,
|
|
196
|
+
} from "brass-runtime/observability";
|
|
197
|
+
import { brass } from "./brass.node";
|
|
198
|
+
|
|
199
|
+
const server = createServer(async (req, res) => {
|
|
200
|
+
const ctx = makeNodeRequestObservabilityContext(brass.observability, req, {
|
|
201
|
+
route: req.url?.startsWith("/users/") ? "/users/:id" : req.url,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const response = await ctx.run(
|
|
205
|
+
ctx.withRequestSpan(
|
|
206
|
+
brass.http.getJson("/users/42", {
|
|
207
|
+
policy: "readModel",
|
|
208
|
+
timeoutMs: 2_000,
|
|
209
|
+
}),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
res.setHeader("content-type", "application/json");
|
|
214
|
+
res.end(JSON.stringify(response.body));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
process.once("SIGTERM", async () => {
|
|
218
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
219
|
+
await brass.shutdown();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
server.listen(process.env.PORT ?? 3000);
|
|
223
|
+
```
|
|
224
|
+
|
package/docs/nestjs.md
ADDED
|
@@ -7,6 +7,10 @@ flush OTLP metrics/traces.
|
|
|
7
7
|
They use optional framework dependencies so the runtime package does not force
|
|
8
8
|
Express, Fastify, or Nest into normal installs.
|
|
9
9
|
|
|
10
|
+
For production-style integration recipes across React, Next.js, Angular,
|
|
11
|
+
Express, Fastify, and Nest, see
|
|
12
|
+
[`docs/framework-integrations.md`](./framework-integrations.md).
|
|
13
|
+
|
|
10
14
|
## Express
|
|
11
15
|
|
|
12
16
|
```bash
|
|
@@ -61,9 +65,17 @@ curl http://localhost:3002/metrics
|
|
|
61
65
|
|
|
62
66
|
Source: `src/examples/observabilityNest.ts`.
|
|
63
67
|
|
|
68
|
+
For a production-style Nest module with DI tokens, Grafana/OTLP configuration,
|
|
69
|
+
an observed Brass HTTP client, and shutdown wiring, see
|
|
70
|
+
[`docs/frameworks/nestjs.md`](./frameworks/nestjs.md).
|
|
71
|
+
|
|
64
72
|
## What the examples demonstrate
|
|
65
73
|
|
|
66
74
|
- `makeObservabilityFromEnv(process.env)` for deployment-style setup.
|
|
75
|
+
- `makeOtlpOptions(...)` for collector endpoint configuration without adding
|
|
76
|
+
vendor-specific code to Brass.
|
|
77
|
+
- `withHttpObservability(...)` around the Brass HTTP client for outbound
|
|
78
|
+
metrics, spans, logs, policy context, and trace propagation.
|
|
67
79
|
- Framework-specific request adapters:
|
|
68
80
|
- `makeExpressRequestObservabilityContext`
|
|
69
81
|
- `makeFastifyRequestObservabilityContext`
|
package/docs/observability.md
CHANGED
|
@@ -327,6 +327,109 @@ collector does not create overlapping exports.
|
|
|
327
327
|
Finished spans are pruned after successful export and can also be bounded with
|
|
328
328
|
`traces.maxFinishedSpans` / `traces.maxSpanAgeMs`.
|
|
329
329
|
|
|
330
|
+
### Vendor-neutral collector recipes
|
|
331
|
+
|
|
332
|
+
Brass intentionally does not know about Grafana Cloud, AppDynamics, or any
|
|
333
|
+
OpenTelemetry SDK implementation. The runtime only needs OTLP HTTP endpoint
|
|
334
|
+
URLs, headers, and optional export tuning. Keep vendor naming in application
|
|
335
|
+
code by writing small helpers that call the backend-neutral `makeOtlpOptions`.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import {
|
|
339
|
+
makeObservability,
|
|
340
|
+
makeOtlpOptions,
|
|
341
|
+
type ObservabilityOtlpOptions,
|
|
342
|
+
} from "brass-runtime/observability";
|
|
343
|
+
|
|
344
|
+
function productionOtlp(input: {
|
|
345
|
+
readonly endpoint: string;
|
|
346
|
+
readonly headers?: Record<string, string>;
|
|
347
|
+
}): ObservabilityOtlpOptions {
|
|
348
|
+
return makeOtlpOptions({
|
|
349
|
+
endpoint: input.endpoint,
|
|
350
|
+
headers: input.headers,
|
|
351
|
+
timeoutMs: 10_000,
|
|
352
|
+
retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 2_000 },
|
|
353
|
+
pipeline: {
|
|
354
|
+
maxQueueSize: 10_000,
|
|
355
|
+
batchSize: 512,
|
|
356
|
+
dropPolicy: "drop-oldest",
|
|
357
|
+
shutdownTimeoutMs: 10_000,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Grafana Cloud can be configured as a direct OTLP endpoint or through
|
|
364
|
+
Grafana Alloy/OpenTelemetry Collector. The helper stays in your app and only
|
|
365
|
+
returns Brass OTLP config:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
function grafanaCloudCollector(input: {
|
|
369
|
+
readonly endpoint: string;
|
|
370
|
+
readonly authorization?: string;
|
|
371
|
+
}): ObservabilityOtlpOptions {
|
|
372
|
+
return productionOtlp({
|
|
373
|
+
endpoint: input.endpoint,
|
|
374
|
+
headers: input.authorization
|
|
375
|
+
? { Authorization: input.authorization }
|
|
376
|
+
: undefined,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const observability = makeObservability({
|
|
381
|
+
serviceName: "shopping-ms",
|
|
382
|
+
serviceVersion: "1.2.3",
|
|
383
|
+
resource: {
|
|
384
|
+
"service.namespace": "shopping",
|
|
385
|
+
"deployment.environment": "production",
|
|
386
|
+
},
|
|
387
|
+
otlp: grafanaCloudCollector({
|
|
388
|
+
endpoint: process.env.GRAFANA_OTLP_ENDPOINT!,
|
|
389
|
+
authorization: process.env.GRAFANA_OTLP_AUTHORIZATION,
|
|
390
|
+
}),
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
For AppDynamics, prefer sending Brass telemetry to the AppDynamics/OpenTelemetry
|
|
395
|
+
Collector deployed next to the service. Authentication and vendor-specific
|
|
396
|
+
exporters stay in the collector config:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
function appDynamicsCollector(input: {
|
|
400
|
+
readonly endpoint: string;
|
|
401
|
+
}): ObservabilityOtlpOptions {
|
|
402
|
+
return productionOtlp({
|
|
403
|
+
endpoint: input.endpoint,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const observability = makeObservability({
|
|
408
|
+
serviceName: "car-rental-ms",
|
|
409
|
+
serviceVersion: "1.2.3",
|
|
410
|
+
resource: {
|
|
411
|
+
"service.namespace": "car-rental",
|
|
412
|
+
"deployment.environment": "production",
|
|
413
|
+
},
|
|
414
|
+
otlp: appDynamicsCollector({
|
|
415
|
+
endpoint: process.env.APPD_OTEL_COLLECTOR_ENDPOINT ?? "http://appd-otel-collector:4318",
|
|
416
|
+
}),
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Then attach the same observability instance to HTTP without changing the
|
|
421
|
+
collector helpers:
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
import { makeDefaultHttpClient } from "brass-runtime/http";
|
|
425
|
+
import { withHttpObservability } from "brass-runtime/observability";
|
|
426
|
+
|
|
427
|
+
const http = makeDefaultHttpClient({
|
|
428
|
+
baseUrl: "https://api.example.com",
|
|
429
|
+
middleware: [withHttpObservability(observability)],
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
330
433
|
Sampling can be configured globally, by ratio, or with rules:
|
|
331
434
|
|
|
332
435
|
```ts
|
|
@@ -423,6 +526,10 @@ const router = makeHttpRouter([
|
|
|
423
526
|
|
|
424
527
|
Runnable framework examples live in
|
|
425
528
|
[`docs/observability-framework-examples.md`](./observability-framework-examples.md).
|
|
529
|
+
Production-style framework integration recipes live in
|
|
530
|
+
[`docs/framework-integrations.md`](./framework-integrations.md).
|
|
531
|
+
For a NestJS module recipe with Grafana/OTLP, DI tokens, HTTP client
|
|
532
|
+
observability, and shutdown wiring, see [`docs/frameworks/nestjs.md`](./frameworks/nestjs.md).
|
|
426
533
|
|
|
427
534
|
Collector smoke and performance budget helpers:
|
|
428
535
|
|