@krak-stack/registry 0.1.31 → 0.1.32
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
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
} from "@krak-stack/registry/httpapi-toolkit";
|
|
21
21
|
import { ApiClient } from "@krak-stack/registry/httpapi/client";
|
|
22
22
|
import { HttpApiSpec } from "@krak-stack/registry/httpapi/helpers";
|
|
23
|
+
import { HttpApiOtlp } from "@krak-stack/registry/opentelemetry/api";
|
|
24
|
+
import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
|
|
23
25
|
import { createDocsSource, makeDocs } from "@krak-stack/registry/docs";
|
|
24
26
|
import { loadMdxDocsDirectory } from "@krak-stack/registry/docs/server";
|
|
25
27
|
import {
|
|
@@ -42,6 +44,25 @@ import {
|
|
|
42
44
|
import { NotificationService } from "@krak-stack/registry/service-notification";
|
|
43
45
|
```
|
|
44
46
|
|
|
47
|
+
Inject API telemetry and browser proxy routes into the application HTTP layer:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const appLayer = appRoutes.pipe(Layer.provideMerge(HttpApiOtlp.layer));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Install browser telemetry globally on the Atom runtime used by the API client:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { Atom } from "effect/unstable/reactivity";
|
|
57
|
+
import { BrowserOtlp } from "@krak-stack/registry/opentelemetry/browser";
|
|
58
|
+
|
|
59
|
+
const apiRuntime = Atom.context();
|
|
60
|
+
|
|
61
|
+
if (!import.meta.env.SSR) {
|
|
62
|
+
apiRuntime.addGlobalLayer(BrowserOtlp.layer({ serviceName: "my-app-web" }));
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
45
66
|
`@krak-stack/registry/service-notification` is the source-compatible direct
|
|
46
67
|
dispatcher. Use the separate `@krak-stack/notifications` package for a durable
|
|
47
68
|
inbox, package-owned migrations, persisted delivery jobs, retries, workers, and
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Config, Layer } from "effect";
|
|
2
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
3
|
+
export declare class HttpApiOtlp {
|
|
4
|
+
static readonly layer: Layer.Layer<import("effect/FileSystem").FileSystem | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform | import("effect/Path").Path, Config.ConfigError, HttpRouter.HttpRouter>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// ../../src/services/opentelemetry/api.ts
|
|
2
|
+
import { Config, Effect, Layer } from "effect";
|
|
3
|
+
import {
|
|
4
|
+
FetchHttpClient,
|
|
5
|
+
HttpClient,
|
|
6
|
+
HttpClientRequest,
|
|
7
|
+
HttpMiddleware,
|
|
8
|
+
HttpRouter,
|
|
9
|
+
HttpServer,
|
|
10
|
+
HttpServerRequest,
|
|
11
|
+
HttpServerResponse
|
|
12
|
+
} from "effect/unstable/http";
|
|
13
|
+
import { Otlp, OtlpSerialization } from "effect/unstable/observability";
|
|
14
|
+
var OpenTelemetryLive = Otlp.layerFromConfig().pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer));
|
|
15
|
+
var otlpProxyPaths = [
|
|
16
|
+
"/api/otel/v1/traces",
|
|
17
|
+
"/api/otel/v1/metrics",
|
|
18
|
+
"/api/otel/v1/logs"
|
|
19
|
+
];
|
|
20
|
+
var proxyOtlp = (baseUrl, signal) => Effect.gen(function* () {
|
|
21
|
+
const incoming = yield* HttpServerRequest.HttpServerRequest;
|
|
22
|
+
const client = yield* HttpClient.HttpClient;
|
|
23
|
+
const contentType = incoming.headers["content-type"] ?? "application/json";
|
|
24
|
+
const body = new Uint8Array(yield* incoming.arrayBuffer);
|
|
25
|
+
const request = HttpClientRequest.get(baseUrl).pipe(HttpClientRequest.appendUrl(`/v1/${signal}`), HttpClientRequest.setMethod("POST"), HttpClientRequest.bodyUint8Array(body, contentType));
|
|
26
|
+
const upstream = yield* client.execute(request);
|
|
27
|
+
const responseBody = new Uint8Array(yield* upstream.arrayBuffer);
|
|
28
|
+
return HttpServerResponse.uint8Array(responseBody, {
|
|
29
|
+
status: upstream.status,
|
|
30
|
+
contentType: upstream.headers["content-type"],
|
|
31
|
+
headers: {
|
|
32
|
+
"retry-after": upstream.headers["retry-after"]
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}).pipe(Effect.withTracerEnabled(false), HttpMiddleware.withLoggerDisabled, Effect.catch((error) => Effect.logError("Failed to proxy browser telemetry", error).pipe(Effect.as(HttpServerResponse.empty({ status: 502 })))), Effect.provide(FetchHttpClient.layer));
|
|
36
|
+
var otlpProxyLayer = Layer.unwrap(Effect.map(Config.url("OTEL_COLLECTOR_PROXY_URL").pipe(Config.withDefault(new URL("http://localhost:4318"))), (baseUrl) => Layer.mergeAll(HttpRouter.add("POST", otlpProxyPaths[0], proxyOtlp(baseUrl, "traces")), HttpRouter.add("POST", otlpProxyPaths[1], proxyOtlp(baseUrl, "metrics")), HttpRouter.add("POST", otlpProxyPaths[2], proxyOtlp(baseUrl, "logs")))));
|
|
37
|
+
var serverServices = HttpServer.layerServices.pipe(Layer.provideMerge(HttpMiddleware.layerTracerDisabledForUrls(otlpProxyPaths)));
|
|
38
|
+
|
|
39
|
+
class HttpApiOtlp {
|
|
40
|
+
static layer = Layer.mergeAll(OpenTelemetryLive, otlpProxyLayer, serverServices);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
HttpApiOtlp
|
|
44
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Layer } from "effect";
|
|
2
|
+
import { Otlp } from "effect/unstable/observability";
|
|
3
|
+
export declare class BrowserOtlp {
|
|
4
|
+
static readonly layer: ({ attributes, baseUrl, serviceName, serviceVersion, ...options }: Omit<Parameters<typeof Otlp.layerJson>[0], "baseUrl" | "resource"> & {
|
|
5
|
+
readonly serviceName: string;
|
|
6
|
+
readonly serviceVersion?: string;
|
|
7
|
+
readonly attributes?: NonNullable<Parameters<typeof Otlp.layerJson>[0]["resource"]>["attributes"];
|
|
8
|
+
readonly baseUrl?: string;
|
|
9
|
+
}) => Layer.Layer<never, never, never>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ../../src/services/opentelemetry/browser.ts
|
|
2
|
+
import { Effect, Layer, Predicate, Schema, Stream } from "effect";
|
|
3
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
4
|
+
import { Otlp } from "effect/unstable/observability";
|
|
5
|
+
|
|
6
|
+
class BrowserReportedError extends Schema.TaggedError()("BrowserReportedError", {
|
|
7
|
+
type: Schema.Literals(["window.error", "unhandledrejection"]),
|
|
8
|
+
message: Schema.String,
|
|
9
|
+
stack: Schema.optional(Schema.String)
|
|
10
|
+
}) {
|
|
11
|
+
}
|
|
12
|
+
var reportBrowserException = (exception) => Effect.fail(exception).pipe(Effect.withSpan("browser.exception", {
|
|
13
|
+
attributes: {
|
|
14
|
+
"exception.type": exception.type,
|
|
15
|
+
"exception.message": exception.message,
|
|
16
|
+
"exception.stacktrace": exception.stack ?? ""
|
|
17
|
+
}
|
|
18
|
+
}), Effect.ignoreCause);
|
|
19
|
+
var BrowserExceptionTrackingLive = Layer.effectDiscard(Stream.merge(Stream.fromEventListener(globalThis, "error", {
|
|
20
|
+
bufferSize: 100
|
|
21
|
+
}).pipe(Stream.map((event) => {
|
|
22
|
+
const error = event.error;
|
|
23
|
+
return new BrowserReportedError({
|
|
24
|
+
type: "window.error",
|
|
25
|
+
message: event.message,
|
|
26
|
+
stack: Predicate.isError(error) ? error.stack : undefined
|
|
27
|
+
});
|
|
28
|
+
})), Stream.fromEventListener(globalThis, "unhandledrejection", { bufferSize: 100 }).pipe(Stream.map((event) => {
|
|
29
|
+
const reason = event.reason;
|
|
30
|
+
return new BrowserReportedError(Predicate.isError(reason) ? {
|
|
31
|
+
type: "unhandledrejection",
|
|
32
|
+
message: reason.message,
|
|
33
|
+
stack: reason.stack
|
|
34
|
+
} : {
|
|
35
|
+
type: "unhandledrejection",
|
|
36
|
+
message: "Unhandled promise rejection"
|
|
37
|
+
});
|
|
38
|
+
}))).pipe(Stream.runForEach(reportBrowserException), Effect.forkScoped));
|
|
39
|
+
|
|
40
|
+
class BrowserOtlp {
|
|
41
|
+
static layer = ({
|
|
42
|
+
attributes,
|
|
43
|
+
baseUrl = "/api/otel",
|
|
44
|
+
serviceName,
|
|
45
|
+
serviceVersion,
|
|
46
|
+
...options
|
|
47
|
+
}) => BrowserExceptionTrackingLive.pipe(Layer.provideMerge(Otlp.layerJson({
|
|
48
|
+
tracerExportInterval: "5 seconds",
|
|
49
|
+
metricsExportInterval: "30 seconds",
|
|
50
|
+
loggerExcludeLogSpans: true,
|
|
51
|
+
loggerMergeWithExisting: true,
|
|
52
|
+
...options,
|
|
53
|
+
baseUrl,
|
|
54
|
+
resource: { attributes, serviceName, serviceVersion }
|
|
55
|
+
}).pipe(Layer.provide(FetchHttpClient.layer))));
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
BrowserOtlp
|
|
59
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krak-stack/registry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Tree-shakable KrakStack components and Effect services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,6 +48,14 @@
|
|
|
48
48
|
"types": "./dist/lib/httpapi-mcp.d.ts",
|
|
49
49
|
"import": "./dist/lib/httpapi-mcp.js"
|
|
50
50
|
},
|
|
51
|
+
"./opentelemetry/api": {
|
|
52
|
+
"types": "./dist/services/opentelemetry/api.d.ts",
|
|
53
|
+
"import": "./dist/services/opentelemetry/api.js"
|
|
54
|
+
},
|
|
55
|
+
"./opentelemetry/browser": {
|
|
56
|
+
"types": "./dist/services/opentelemetry/browser.d.ts",
|
|
57
|
+
"import": "./dist/services/opentelemetry/browser.js"
|
|
58
|
+
},
|
|
51
59
|
"./docs": {
|
|
52
60
|
"types": "./dist/lib/docs.d.ts",
|
|
53
61
|
"import": "./dist/lib/docs.js"
|