@krak-stack/registry 0.1.39 → 0.1.41
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 +158 -0
- package/dist/components/ui/data-table.d.ts +27 -10
- package/dist/components/ui/data-table.js +81 -36
- package/dist/components/ui/google-map.d.ts +14 -1
- package/dist/components/ui/google-map.js +97 -18
- package/dist/lib/query.d.ts +30 -3
- package/dist/lib/query.js +13 -3
- package/dist/services/opentelemetry/api.d.ts +1 -1
- package/dist/services/opentelemetry/api.js +165 -13
- package/dist/services/opentelemetry/browser.d.ts +21 -3
- package/dist/services/opentelemetry/browser.js +267 -21
- package/dist/services/opentelemetry/events.d.ts +37 -0
- package/dist/services/opentelemetry/schema.d.ts +44 -0
- package/dist/services/opentelemetry/server.d.ts +20 -0
- package/dist/services/opentelemetry/server.js +154 -0
- package/package.json +5 -1
|
@@ -1,21 +1,153 @@
|
|
|
1
1
|
// ../../src/services/opentelemetry/browser.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Context as Context2,
|
|
4
|
+
Effect as Effect2,
|
|
5
|
+
FiberSet,
|
|
6
|
+
Layer as Layer2,
|
|
7
|
+
Predicate,
|
|
8
|
+
Ref,
|
|
9
|
+
References as References2,
|
|
10
|
+
Schema as Schema3,
|
|
11
|
+
Stream
|
|
12
|
+
} from "effect";
|
|
3
13
|
import { FetchHttpClient, HttpClientRequest } from "effect/http";
|
|
4
14
|
import {
|
|
5
|
-
OtlpExporter,
|
|
15
|
+
OtlpExporter as OtlpExporter2,
|
|
6
16
|
OtlpLogger,
|
|
7
17
|
OtlpMetrics,
|
|
8
|
-
OtlpSerialization,
|
|
18
|
+
OtlpSerialization as OtlpSerialization2,
|
|
9
19
|
OtlpTracer
|
|
10
20
|
} from "effect/observability";
|
|
11
21
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
// ../../src/services/opentelemetry/events.ts
|
|
23
|
+
import {
|
|
24
|
+
Clock,
|
|
25
|
+
Context,
|
|
26
|
+
DateTime,
|
|
27
|
+
Effect,
|
|
28
|
+
Layer,
|
|
29
|
+
Option,
|
|
30
|
+
References,
|
|
31
|
+
Schema as Schema2
|
|
32
|
+
} from "effect";
|
|
33
|
+
import {
|
|
34
|
+
OtlpExporter,
|
|
35
|
+
OtlpResource,
|
|
36
|
+
OtlpSerialization
|
|
37
|
+
} from "effect/observability";
|
|
38
|
+
|
|
39
|
+
// ../../src/services/opentelemetry/schema.ts
|
|
40
|
+
import { Schema } from "effect";
|
|
41
|
+
var EventAttributeValue = Schema.Union([
|
|
42
|
+
Schema.String,
|
|
43
|
+
Schema.Finite,
|
|
44
|
+
Schema.Boolean,
|
|
45
|
+
Schema.Array(Schema.String),
|
|
46
|
+
Schema.Array(Schema.Finite),
|
|
47
|
+
Schema.Array(Schema.Boolean)
|
|
48
|
+
]);
|
|
49
|
+
var EventAttributesSchema = Schema.Record(Schema.String, EventAttributeValue).annotate({ identifier: "EventAttributes" });
|
|
50
|
+
var EventOptionsSchema = Schema.Struct({
|
|
51
|
+
userId: Schema.optional(Schema.NonEmptyString),
|
|
52
|
+
anonymousId: Schema.optional(Schema.NonEmptyString),
|
|
53
|
+
sessionId: Schema.optional(Schema.NonEmptyString),
|
|
54
|
+
messageId: Schema.optional(Schema.NonEmptyString),
|
|
55
|
+
timestamp: Schema.optional(Schema.DateTimeUtc),
|
|
56
|
+
context: Schema.optional(EventAttributesSchema)
|
|
57
|
+
}).annotate({ identifier: "EventOptions" });
|
|
58
|
+
var TelemetryEvent = Schema.Struct({
|
|
59
|
+
name: Schema.NonEmptyString,
|
|
60
|
+
properties: Schema.optional(EventAttributesSchema),
|
|
61
|
+
options: Schema.optional(EventOptionsSchema)
|
|
62
|
+
}).annotate({ identifier: "TelemetryEvent" });
|
|
63
|
+
var PageEvent = Schema.Struct({
|
|
64
|
+
name: Schema.optional(Schema.NonEmptyString),
|
|
65
|
+
category: Schema.optional(Schema.NonEmptyString),
|
|
66
|
+
path: Schema.optional(Schema.NonEmptyString),
|
|
67
|
+
properties: Schema.optional(EventAttributesSchema),
|
|
68
|
+
options: Schema.optional(EventOptionsSchema)
|
|
69
|
+
}).annotate({ identifier: "PageEvent" });
|
|
70
|
+
var BrowserIdentity = Schema.Struct({
|
|
71
|
+
userId: Schema.optional(Schema.NonEmptyString),
|
|
72
|
+
organizationId: Schema.optional(Schema.NonEmptyString)
|
|
73
|
+
}).annotate({ identifier: "BrowserTelemetryIdentity" });
|
|
74
|
+
|
|
75
|
+
// ../../src/services/opentelemetry/events.ts
|
|
76
|
+
class OtlpEvents extends Context.Service()("krakstack/opentelemetry/Events") {
|
|
77
|
+
static disabledLayer = Layer.succeed(this, { push: () => {} });
|
|
78
|
+
static layer = (options) => Layer.effect(this, Effect.gen(function* () {
|
|
79
|
+
const serialization = yield* OtlpSerialization.OtlpSerialization;
|
|
80
|
+
const resource = yield* OtlpResource.fromConfig(options.resource);
|
|
81
|
+
return yield* OtlpExporter.make({
|
|
82
|
+
...options,
|
|
83
|
+
label: "OtlpEvents",
|
|
84
|
+
body: (logRecords) => [
|
|
85
|
+
serialization.logs({
|
|
86
|
+
resourceLogs: [
|
|
87
|
+
{
|
|
88
|
+
resource,
|
|
89
|
+
scopeLogs: [
|
|
90
|
+
{
|
|
91
|
+
scope: { name: "krakstack.events" },
|
|
92
|
+
logRecords
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}),
|
|
98
|
+
Effect.void
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
var track = Effect.fnUntraced(function* (name, properties, options) {
|
|
104
|
+
const event = yield* Schema2.decodeUnknownEffect(TelemetryEvent)({
|
|
105
|
+
name,
|
|
106
|
+
properties,
|
|
107
|
+
options
|
|
108
|
+
});
|
|
109
|
+
const events = yield* OtlpEvents;
|
|
110
|
+
const clock = yield* Clock.Clock;
|
|
111
|
+
const observedTime = clock.currentTimeNanosUnsafe().toString();
|
|
112
|
+
const timestamp = event.options?.timestamp ? (BigInt(DateTime.toEpochMillis(event.options.timestamp)) * 1000000n).toString() : observedTime;
|
|
113
|
+
const span = yield* Effect.currentSpan.pipe(Effect.option);
|
|
114
|
+
const attributes = { ...event.options?.context, ...event.properties };
|
|
115
|
+
if (event.options?.userId)
|
|
116
|
+
attributes["user.id"] = event.options.userId;
|
|
117
|
+
if (event.options?.anonymousId)
|
|
118
|
+
attributes["analytics.anonymous_id"] = event.options.anonymousId;
|
|
119
|
+
if (event.options?.sessionId)
|
|
120
|
+
attributes["session.id"] = event.options.sessionId;
|
|
121
|
+
if (event.options?.messageId)
|
|
122
|
+
attributes["log.record.uid"] = event.options.messageId;
|
|
123
|
+
const annotations = yield* References.CurrentLogAnnotations;
|
|
124
|
+
events.push({
|
|
125
|
+
eventName: event.name,
|
|
126
|
+
timeUnixNano: timestamp,
|
|
127
|
+
observedTimeUnixNano: observedTime,
|
|
128
|
+
severityNumber: 9,
|
|
129
|
+
attributes: OtlpResource.entriesToAttributes(Object.entries({
|
|
130
|
+
...attributes,
|
|
131
|
+
...annotations
|
|
132
|
+
})),
|
|
133
|
+
droppedAttributesCount: 0,
|
|
134
|
+
traceId: Option.isSome(span) ? span.value.traceId : undefined,
|
|
135
|
+
spanId: Option.isSome(span) ? span.value.spanId : undefined
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ../../src/services/opentelemetry/browser.ts
|
|
140
|
+
class BrowserIdentityState extends Context2.Service()("krakstack/opentelemetry/BrowserIdentity") {
|
|
141
|
+
static layer = Layer2.effect(this, Ref.make({}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class BrowserReportedError extends Schema3.TaggedError()("BrowserReportedError", {
|
|
145
|
+
type: Schema3.Literals(["window.error", "unhandledrejection"]),
|
|
146
|
+
message: Schema3.String,
|
|
147
|
+
stack: Schema3.optional(Schema3.String)
|
|
16
148
|
}) {
|
|
17
149
|
}
|
|
18
|
-
var reportBrowserException = (exception) =>
|
|
150
|
+
var reportBrowserException = (exception) => Effect2.fail(exception).pipe(Effect2.withSpan("browser.exception", {
|
|
19
151
|
root: true,
|
|
20
152
|
sampled: true,
|
|
21
153
|
attributes: {
|
|
@@ -23,8 +155,8 @@ var reportBrowserException = (exception) => Effect.fail(exception).pipe(Effect.w
|
|
|
23
155
|
"exception.message": exception.message,
|
|
24
156
|
"exception.stacktrace": exception.stack ?? ""
|
|
25
157
|
}
|
|
26
|
-
}),
|
|
27
|
-
var BrowserExceptionTrackingLive =
|
|
158
|
+
}), Effect2.ignoreCause);
|
|
159
|
+
var BrowserExceptionTrackingLive = Layer2.effectDiscard(Stream.merge(Stream.fromEventListener(globalThis, "error", {
|
|
28
160
|
bufferSize: 100
|
|
29
161
|
}).pipe(Stream.map((event) => {
|
|
30
162
|
const error = event.error;
|
|
@@ -43,13 +175,13 @@ var BrowserExceptionTrackingLive = Layer.effectDiscard(Stream.merge(Stream.fromE
|
|
|
43
175
|
type: "unhandledrejection",
|
|
44
176
|
message: "Unhandled promise rejection"
|
|
45
177
|
});
|
|
46
|
-
}))).pipe(Stream.runForEach(reportBrowserException),
|
|
47
|
-
var BrowserLifecycleFlushingLive =
|
|
48
|
-
const flusher = yield*
|
|
178
|
+
}))).pipe(Stream.runForEach(reportBrowserException), Effect2.forkScoped));
|
|
179
|
+
var BrowserLifecycleFlushingLive = Layer2.effectDiscard(Effect2.gen(function* () {
|
|
180
|
+
const flusher = yield* OtlpExporter2.Flusher;
|
|
49
181
|
const fibers = yield* FiberSet.make();
|
|
50
182
|
const run = yield* FiberSet.runtime(fibers)();
|
|
51
183
|
const flush = () => {
|
|
52
|
-
run(flusher.flush.pipe(
|
|
184
|
+
run(flusher.flush.pipe(Effect2.provideService(FetchHttpClient.RequestInit, {
|
|
53
185
|
keepalive: true
|
|
54
186
|
})));
|
|
55
187
|
};
|
|
@@ -57,50 +189,164 @@ var BrowserLifecycleFlushingLive = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
57
189
|
if (document.visibilityState === "hidden")
|
|
58
190
|
flush();
|
|
59
191
|
};
|
|
60
|
-
yield*
|
|
192
|
+
yield* Effect2.acquireRelease(Effect2.sync(() => {
|
|
61
193
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
62
194
|
globalThis.addEventListener("pagehide", flush);
|
|
63
|
-
}), () =>
|
|
195
|
+
}), () => Effect2.sync(() => {
|
|
64
196
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
65
197
|
globalThis.removeEventListener("pagehide", flush);
|
|
66
198
|
}));
|
|
67
199
|
}));
|
|
200
|
+
var browserEffects = {
|
|
201
|
+
identify: Effect2.fnUntraced(function* (input) {
|
|
202
|
+
const identity = yield* Schema3.decodeUnknownEffect(BrowserIdentity)(input === null ? {} : input);
|
|
203
|
+
const state = yield* BrowserIdentityState;
|
|
204
|
+
yield* Ref.set(state, identity);
|
|
205
|
+
}),
|
|
206
|
+
track: Effect2.fnUntraced(function* (name, properties, options) {
|
|
207
|
+
const state = yield* BrowserIdentityState;
|
|
208
|
+
const identity = yield* Ref.get(state);
|
|
209
|
+
const annotations = {};
|
|
210
|
+
if (identity.userId)
|
|
211
|
+
annotations["user.id"] = identity.userId;
|
|
212
|
+
if (identity.organizationId)
|
|
213
|
+
annotations["organization.id"] = identity.organizationId;
|
|
214
|
+
const scoped = yield* References2.CurrentLogAnnotations;
|
|
215
|
+
yield* track(name, properties, options).pipe(Effect2.annotateLogs({ ...annotations, ...scoped }));
|
|
216
|
+
}),
|
|
217
|
+
page: Effect2.fnUntraced(function* (input = {}) {
|
|
218
|
+
const page = yield* Schema3.decodeUnknownEffect(PageEvent)(input);
|
|
219
|
+
const properties = { ...page.properties };
|
|
220
|
+
if (page.name)
|
|
221
|
+
properties["analytics.page.name"] = page.name;
|
|
222
|
+
if (page.category)
|
|
223
|
+
properties["analytics.page.category"] = page.category;
|
|
224
|
+
properties["url.path"] = (page.path ?? globalThis.location.pathname).split(/[?#]/, 1)[0] ?? "/";
|
|
225
|
+
yield* browserEffects.track("browser.page_view", properties, page.options);
|
|
226
|
+
})
|
|
227
|
+
};
|
|
228
|
+
var browserClients = [];
|
|
229
|
+
var runBrowser = (program) => Effect2.suspend(() => {
|
|
230
|
+
const client = browserClients.at(-1);
|
|
231
|
+
if (!client)
|
|
232
|
+
return Effect2.void;
|
|
233
|
+
return program.pipe(Effect2.provideService(BrowserIdentityState, client.identity), Effect2.provideService(OtlpEvents, client.events));
|
|
234
|
+
});
|
|
235
|
+
var BrowserClientLive = Layer2.effectDiscard(Effect2.gen(function* () {
|
|
236
|
+
const identity = yield* BrowserIdentityState;
|
|
237
|
+
const events = yield* OtlpEvents;
|
|
238
|
+
yield* Effect2.acquireRelease(Effect2.sync(() => {
|
|
239
|
+
const client = { identity, events };
|
|
240
|
+
browserClients.push(client);
|
|
241
|
+
return client;
|
|
242
|
+
}), (client) => Effect2.sync(() => {
|
|
243
|
+
const index = browserClients.indexOf(client);
|
|
244
|
+
if (index !== -1)
|
|
245
|
+
browserClients.splice(index, 1);
|
|
246
|
+
}));
|
|
247
|
+
}));
|
|
248
|
+
var pageViewListeners = new Set;
|
|
249
|
+
var stopPageViewTracking;
|
|
250
|
+
var subscribePageViews = (listener) => {
|
|
251
|
+
if (pageViewListeners.size === 0) {
|
|
252
|
+
const history = globalThis.history;
|
|
253
|
+
const pushState = history.pushState;
|
|
254
|
+
const replaceState = history.replaceState;
|
|
255
|
+
const notify = () => {
|
|
256
|
+
for (const onPage of pageViewListeners)
|
|
257
|
+
onPage();
|
|
258
|
+
};
|
|
259
|
+
const trackedPushState = (...args) => {
|
|
260
|
+
pushState.apply(history, args);
|
|
261
|
+
notify();
|
|
262
|
+
};
|
|
263
|
+
const trackedReplaceState = (...args) => {
|
|
264
|
+
replaceState.apply(history, args);
|
|
265
|
+
notify();
|
|
266
|
+
};
|
|
267
|
+
history.pushState = trackedPushState;
|
|
268
|
+
history.replaceState = trackedReplaceState;
|
|
269
|
+
globalThis.addEventListener("popstate", notify);
|
|
270
|
+
stopPageViewTracking = () => {
|
|
271
|
+
globalThis.removeEventListener("popstate", notify);
|
|
272
|
+
if (history.pushState === trackedPushState)
|
|
273
|
+
history.pushState = pushState;
|
|
274
|
+
if (history.replaceState === trackedReplaceState)
|
|
275
|
+
history.replaceState = replaceState;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
pageViewListeners.add(listener);
|
|
279
|
+
return () => {
|
|
280
|
+
pageViewListeners.delete(listener);
|
|
281
|
+
if (pageViewListeners.size === 0) {
|
|
282
|
+
stopPageViewTracking?.();
|
|
283
|
+
stopPageViewTracking = undefined;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
var BrowserPageViewsLive = Layer2.effectDiscard(Effect2.gen(function* () {
|
|
288
|
+
const fibers = yield* FiberSet.make();
|
|
289
|
+
const run = yield* FiberSet.runtime(fibers)();
|
|
290
|
+
let previousPath = globalThis.location.pathname;
|
|
291
|
+
yield* Effect2.acquireRelease(Effect2.sync(() => subscribePageViews(() => {
|
|
292
|
+
const path = globalThis.location.pathname;
|
|
293
|
+
if (path === previousPath)
|
|
294
|
+
return;
|
|
295
|
+
previousPath = path;
|
|
296
|
+
run(browserEffects.page({ path }).pipe(Effect2.ignore));
|
|
297
|
+
})), (unsubscribe) => Effect2.sync(unsubscribe));
|
|
298
|
+
yield* browserEffects.page({ path: previousPath }).pipe(Effect2.ignore);
|
|
299
|
+
}));
|
|
68
300
|
|
|
69
301
|
class BrowserOtlp {
|
|
302
|
+
static identify = (identity) => runBrowser(browserEffects.identify(identity)).pipe(Effect2.catch(() => Effect2.logWarning("Invalid browser telemetry identity; keeping previous identity")));
|
|
303
|
+
static track = (name, properties, options) => runBrowser(browserEffects.track(name, properties, options));
|
|
304
|
+
static page = (options) => runBrowser(browserEffects.page(options));
|
|
305
|
+
static pageView = BrowserOtlp.page;
|
|
70
306
|
static layer = ({
|
|
71
307
|
attributes,
|
|
72
308
|
baseUrl = "/api/otel",
|
|
73
309
|
logs = false,
|
|
310
|
+
events = false,
|
|
74
311
|
metrics = false,
|
|
75
312
|
serviceName,
|
|
76
313
|
serviceVersion,
|
|
77
314
|
traces = {},
|
|
78
315
|
headers
|
|
79
316
|
}) => {
|
|
317
|
+
const { pageViews = true, ...eventOptions } = events === false ? {} : events;
|
|
80
318
|
const resource = { attributes, serviceName, serviceVersion };
|
|
81
319
|
const request = HttpClientRequest.get(baseUrl);
|
|
82
320
|
const signalUrl = (signal) => HttpClientRequest.appendUrl(request, `/v1/${signal}`).url;
|
|
83
|
-
const exporters =
|
|
321
|
+
const exporters = Layer2.mergeAll(OtlpExporter2.layerFlusher, events !== false ? OtlpEvents.layer({
|
|
322
|
+
exportInterval: "30 seconds",
|
|
323
|
+
maxBatchSize: 100,
|
|
324
|
+
shutdownTimeout: "3 seconds",
|
|
325
|
+
...eventOptions,
|
|
326
|
+
url: signalUrl("logs"),
|
|
327
|
+
resource,
|
|
328
|
+
headers
|
|
329
|
+
}) : OtlpEvents.disabledLayer, traces !== false ? OtlpTracer.layer({
|
|
84
330
|
exportInterval: "30 seconds",
|
|
85
331
|
...traces,
|
|
86
332
|
url: signalUrl("traces"),
|
|
87
333
|
resource,
|
|
88
334
|
headers
|
|
89
|
-
}) :
|
|
335
|
+
}) : Layer2.empty, metrics !== false ? OtlpMetrics.layer({
|
|
90
336
|
exportInterval: "60 seconds",
|
|
91
337
|
...metrics,
|
|
92
338
|
url: signalUrl("metrics"),
|
|
93
339
|
resource,
|
|
94
340
|
headers
|
|
95
|
-
}) :
|
|
341
|
+
}) : Layer2.empty, logs !== false ? OtlpLogger.layer({
|
|
96
342
|
excludeLogSpans: true,
|
|
97
343
|
mergeWithExisting: true,
|
|
98
344
|
...logs,
|
|
99
345
|
url: signalUrl("logs"),
|
|
100
346
|
resource,
|
|
101
347
|
headers
|
|
102
|
-
}) :
|
|
103
|
-
return
|
|
348
|
+
}) : Layer2.empty).pipe(Layer2.provideMerge(OtlpExporter2.layerFlusher), Layer2.provide(OtlpSerialization2.layerJson), Layer2.provide(FetchHttpClient.layer));
|
|
349
|
+
return Layer2.mergeAll(BrowserClientLive, BrowserLifecycleFlushingLive, events !== false && pageViews ? BrowserPageViewsLive : Layer2.empty, traces !== false ? BrowserExceptionTrackingLive : Layer2.empty).pipe(Layer2.provideMerge(Layer2.merge(BrowserIdentityState.layer, exporters)));
|
|
104
350
|
};
|
|
105
351
|
}
|
|
106
352
|
export {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Context, DateTime, Effect, Layer, Schema } from "effect";
|
|
2
|
+
import { OtlpExporter, OtlpResource, OtlpSerialization } from "effect/observability";
|
|
3
|
+
type ExporterOptions = Pick<Parameters<typeof OtlpExporter.make>[0], "url" | "headers" | "exportInterval" | "maxBatchSize" | "shutdownTimeout"> & {
|
|
4
|
+
readonly resource?: Parameters<typeof OtlpResource.fromConfig>[0];
|
|
5
|
+
};
|
|
6
|
+
export type EventExportOptions = Partial<Pick<ExporterOptions, "exportInterval" | "maxBatchSize" | "shutdownTimeout">>;
|
|
7
|
+
declare const OtlpEvents_base: Context.ServiceClass<OtlpEvents, "krakstack/opentelemetry/Events", {
|
|
8
|
+
readonly push: (record: EventRecord) => void;
|
|
9
|
+
}>;
|
|
10
|
+
export declare class OtlpEvents extends OtlpEvents_base {
|
|
11
|
+
static readonly disabledLayer: Layer.Layer<OtlpEvents, never, never>;
|
|
12
|
+
static readonly layer: (options: ExporterOptions) => Layer.Layer<OtlpEvents, never, OtlpExporter.Flusher | import("effect/http/HttpClient").HttpClient | OtlpSerialization.OtlpSerialization>;
|
|
13
|
+
}
|
|
14
|
+
interface EventRecord {
|
|
15
|
+
readonly eventName: string;
|
|
16
|
+
readonly timeUnixNano: string;
|
|
17
|
+
readonly observedTimeUnixNano: string;
|
|
18
|
+
readonly severityNumber: 9;
|
|
19
|
+
readonly attributes: ReturnType<typeof OtlpResource.entriesToAttributes>;
|
|
20
|
+
readonly droppedAttributesCount: 0;
|
|
21
|
+
readonly traceId?: string;
|
|
22
|
+
readonly spanId?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Emits an INFO event without creating a tracing span or persisting identity. */
|
|
25
|
+
export declare const track: (name: string, properties?: {
|
|
26
|
+
readonly [x: string]: string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
27
|
+
} | undefined, options?: {
|
|
28
|
+
readonly userId?: string | undefined;
|
|
29
|
+
readonly anonymousId?: string | undefined;
|
|
30
|
+
readonly sessionId?: string | undefined;
|
|
31
|
+
readonly messageId?: string | undefined;
|
|
32
|
+
readonly timestamp?: DateTime.Utc | undefined;
|
|
33
|
+
readonly context?: {
|
|
34
|
+
readonly [x: string]: string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
35
|
+
} | undefined;
|
|
36
|
+
} | undefined) => Effect.Effect<void, Schema.SchemaError, OtlpEvents>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export declare const EventAttributesSchema: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>;
|
|
3
|
+
export declare const EventOptionsSchema: Schema.Struct<{
|
|
4
|
+
readonly userId: Schema.optional<Schema.NonEmptyString>;
|
|
5
|
+
readonly anonymousId: Schema.optional<Schema.NonEmptyString>;
|
|
6
|
+
readonly sessionId: Schema.optional<Schema.NonEmptyString>;
|
|
7
|
+
readonly messageId: Schema.optional<Schema.NonEmptyString>;
|
|
8
|
+
readonly timestamp: Schema.optional<Schema.DateTimeUtc>;
|
|
9
|
+
readonly context: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const TelemetryEvent: Schema.Struct<{
|
|
12
|
+
readonly name: Schema.NonEmptyString;
|
|
13
|
+
readonly properties: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>>;
|
|
14
|
+
readonly options: Schema.optional<Schema.Struct<{
|
|
15
|
+
readonly userId: Schema.optional<Schema.NonEmptyString>;
|
|
16
|
+
readonly anonymousId: Schema.optional<Schema.NonEmptyString>;
|
|
17
|
+
readonly sessionId: Schema.optional<Schema.NonEmptyString>;
|
|
18
|
+
readonly messageId: Schema.optional<Schema.NonEmptyString>;
|
|
19
|
+
readonly timestamp: Schema.optional<Schema.DateTimeUtc>;
|
|
20
|
+
readonly context: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>>;
|
|
21
|
+
}>>;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const PageEvent: Schema.Struct<{
|
|
24
|
+
readonly name: Schema.optional<Schema.NonEmptyString>;
|
|
25
|
+
readonly category: Schema.optional<Schema.NonEmptyString>;
|
|
26
|
+
readonly path: Schema.optional<Schema.NonEmptyString>;
|
|
27
|
+
readonly properties: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>>;
|
|
28
|
+
readonly options: Schema.optional<Schema.Struct<{
|
|
29
|
+
readonly userId: Schema.optional<Schema.NonEmptyString>;
|
|
30
|
+
readonly anonymousId: Schema.optional<Schema.NonEmptyString>;
|
|
31
|
+
readonly sessionId: Schema.optional<Schema.NonEmptyString>;
|
|
32
|
+
readonly messageId: Schema.optional<Schema.NonEmptyString>;
|
|
33
|
+
readonly timestamp: Schema.optional<Schema.DateTimeUtc>;
|
|
34
|
+
readonly context: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Finite, Schema.Boolean, Schema.$Array<Schema.String>, Schema.$Array<Schema.Finite>, Schema.$Array<Schema.Boolean>]>>>;
|
|
35
|
+
}>>;
|
|
36
|
+
}>;
|
|
37
|
+
export type EventAttributes = typeof EventAttributesSchema.Type;
|
|
38
|
+
export type EventOptions = typeof EventOptionsSchema.Type;
|
|
39
|
+
export type PageOptions = typeof PageEvent.Type;
|
|
40
|
+
export declare const BrowserIdentity: Schema.Struct<{
|
|
41
|
+
readonly userId: Schema.optional<Schema.NonEmptyString>;
|
|
42
|
+
readonly organizationId: Schema.optional<Schema.NonEmptyString>;
|
|
43
|
+
}>;
|
|
44
|
+
export type BrowserIdentity = typeof BrowserIdentity.Type;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Effect, Layer, Schema } from "effect";
|
|
2
|
+
import { OtlpExporter } from "effect/observability";
|
|
3
|
+
import { OtlpEvents } from "./events.js";
|
|
4
|
+
export type { EventAttributes, EventOptions } from "./schema.js";
|
|
5
|
+
/** Runtime-neutral server telemetry for HTTP applications, workers, and CLIs. */
|
|
6
|
+
export declare class ServerOtlp {
|
|
7
|
+
static readonly track: (name: string, properties?: {
|
|
8
|
+
readonly [x: string]: string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
9
|
+
} | undefined, options?: {
|
|
10
|
+
readonly userId?: string | undefined;
|
|
11
|
+
readonly anonymousId?: string | undefined;
|
|
12
|
+
readonly sessionId?: string | undefined;
|
|
13
|
+
readonly messageId?: string | undefined;
|
|
14
|
+
readonly timestamp?: import("effect/DateTime").Utc | undefined;
|
|
15
|
+
readonly context?: {
|
|
16
|
+
readonly [x: string]: string | number | boolean | readonly string[] | readonly number[] | readonly boolean[];
|
|
17
|
+
} | undefined;
|
|
18
|
+
} | undefined) => Effect.Effect<void, Schema.SchemaError, OtlpEvents>;
|
|
19
|
+
static readonly layer: Layer.Layer<OtlpExporter.Flusher | OtlpEvents, never, never>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// ../../src/services/opentelemetry/server.ts
|
|
2
|
+
import { Config, Effect as Effect2, Layer as Layer2, Schema as Schema3 } from "effect";
|
|
3
|
+
import { FetchHttpClient } from "effect/http";
|
|
4
|
+
import { Otlp, OtlpExporter as OtlpExporter2, OtlpSerialization as OtlpSerialization2 } from "effect/observability";
|
|
5
|
+
|
|
6
|
+
// ../../src/services/opentelemetry/events.ts
|
|
7
|
+
import {
|
|
8
|
+
Clock,
|
|
9
|
+
Context,
|
|
10
|
+
DateTime,
|
|
11
|
+
Effect,
|
|
12
|
+
Layer,
|
|
13
|
+
Option,
|
|
14
|
+
References,
|
|
15
|
+
Schema as Schema2
|
|
16
|
+
} from "effect";
|
|
17
|
+
import {
|
|
18
|
+
OtlpExporter,
|
|
19
|
+
OtlpResource,
|
|
20
|
+
OtlpSerialization
|
|
21
|
+
} from "effect/observability";
|
|
22
|
+
|
|
23
|
+
// ../../src/services/opentelemetry/schema.ts
|
|
24
|
+
import { Schema } from "effect";
|
|
25
|
+
var EventAttributeValue = Schema.Union([
|
|
26
|
+
Schema.String,
|
|
27
|
+
Schema.Finite,
|
|
28
|
+
Schema.Boolean,
|
|
29
|
+
Schema.Array(Schema.String),
|
|
30
|
+
Schema.Array(Schema.Finite),
|
|
31
|
+
Schema.Array(Schema.Boolean)
|
|
32
|
+
]);
|
|
33
|
+
var EventAttributesSchema = Schema.Record(Schema.String, EventAttributeValue).annotate({ identifier: "EventAttributes" });
|
|
34
|
+
var EventOptionsSchema = Schema.Struct({
|
|
35
|
+
userId: Schema.optional(Schema.NonEmptyString),
|
|
36
|
+
anonymousId: Schema.optional(Schema.NonEmptyString),
|
|
37
|
+
sessionId: Schema.optional(Schema.NonEmptyString),
|
|
38
|
+
messageId: Schema.optional(Schema.NonEmptyString),
|
|
39
|
+
timestamp: Schema.optional(Schema.DateTimeUtc),
|
|
40
|
+
context: Schema.optional(EventAttributesSchema)
|
|
41
|
+
}).annotate({ identifier: "EventOptions" });
|
|
42
|
+
var TelemetryEvent = Schema.Struct({
|
|
43
|
+
name: Schema.NonEmptyString,
|
|
44
|
+
properties: Schema.optional(EventAttributesSchema),
|
|
45
|
+
options: Schema.optional(EventOptionsSchema)
|
|
46
|
+
}).annotate({ identifier: "TelemetryEvent" });
|
|
47
|
+
var PageEvent = Schema.Struct({
|
|
48
|
+
name: Schema.optional(Schema.NonEmptyString),
|
|
49
|
+
category: Schema.optional(Schema.NonEmptyString),
|
|
50
|
+
path: Schema.optional(Schema.NonEmptyString),
|
|
51
|
+
properties: Schema.optional(EventAttributesSchema),
|
|
52
|
+
options: Schema.optional(EventOptionsSchema)
|
|
53
|
+
}).annotate({ identifier: "PageEvent" });
|
|
54
|
+
var BrowserIdentity = Schema.Struct({
|
|
55
|
+
userId: Schema.optional(Schema.NonEmptyString),
|
|
56
|
+
organizationId: Schema.optional(Schema.NonEmptyString)
|
|
57
|
+
}).annotate({ identifier: "BrowserTelemetryIdentity" });
|
|
58
|
+
|
|
59
|
+
// ../../src/services/opentelemetry/events.ts
|
|
60
|
+
class OtlpEvents extends Context.Service()("krakstack/opentelemetry/Events") {
|
|
61
|
+
static disabledLayer = Layer.succeed(this, { push: () => {} });
|
|
62
|
+
static layer = (options) => Layer.effect(this, Effect.gen(function* () {
|
|
63
|
+
const serialization = yield* OtlpSerialization.OtlpSerialization;
|
|
64
|
+
const resource = yield* OtlpResource.fromConfig(options.resource);
|
|
65
|
+
return yield* OtlpExporter.make({
|
|
66
|
+
...options,
|
|
67
|
+
label: "OtlpEvents",
|
|
68
|
+
body: (logRecords) => [
|
|
69
|
+
serialization.logs({
|
|
70
|
+
resourceLogs: [
|
|
71
|
+
{
|
|
72
|
+
resource,
|
|
73
|
+
scopeLogs: [
|
|
74
|
+
{
|
|
75
|
+
scope: { name: "krakstack.events" },
|
|
76
|
+
logRecords
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}),
|
|
82
|
+
Effect.void
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
var track = Effect.fnUntraced(function* (name, properties, options) {
|
|
88
|
+
const event = yield* Schema2.decodeUnknownEffect(TelemetryEvent)({
|
|
89
|
+
name,
|
|
90
|
+
properties,
|
|
91
|
+
options
|
|
92
|
+
});
|
|
93
|
+
const events = yield* OtlpEvents;
|
|
94
|
+
const clock = yield* Clock.Clock;
|
|
95
|
+
const observedTime = clock.currentTimeNanosUnsafe().toString();
|
|
96
|
+
const timestamp = event.options?.timestamp ? (BigInt(DateTime.toEpochMillis(event.options.timestamp)) * 1000000n).toString() : observedTime;
|
|
97
|
+
const span = yield* Effect.currentSpan.pipe(Effect.option);
|
|
98
|
+
const attributes = { ...event.options?.context, ...event.properties };
|
|
99
|
+
if (event.options?.userId)
|
|
100
|
+
attributes["user.id"] = event.options.userId;
|
|
101
|
+
if (event.options?.anonymousId)
|
|
102
|
+
attributes["analytics.anonymous_id"] = event.options.anonymousId;
|
|
103
|
+
if (event.options?.sessionId)
|
|
104
|
+
attributes["session.id"] = event.options.sessionId;
|
|
105
|
+
if (event.options?.messageId)
|
|
106
|
+
attributes["log.record.uid"] = event.options.messageId;
|
|
107
|
+
const annotations = yield* References.CurrentLogAnnotations;
|
|
108
|
+
events.push({
|
|
109
|
+
eventName: event.name,
|
|
110
|
+
timeUnixNano: timestamp,
|
|
111
|
+
observedTimeUnixNano: observedTime,
|
|
112
|
+
severityNumber: 9,
|
|
113
|
+
attributes: OtlpResource.entriesToAttributes(Object.entries({
|
|
114
|
+
...attributes,
|
|
115
|
+
...annotations
|
|
116
|
+
})),
|
|
117
|
+
droppedAttributesCount: 0,
|
|
118
|
+
traceId: Option.isSome(span) ? span.value.traceId : undefined,
|
|
119
|
+
spanId: Option.isSome(span) ? span.value.spanId : undefined
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// ../../src/services/opentelemetry/server.ts
|
|
124
|
+
var ServerEventsLive = Layer2.unwrap(Effect2.gen(function* () {
|
|
125
|
+
const disabled = yield* Config.Boolean("OTEL_SDK_DISABLED").pipe(Config.withDefault(false));
|
|
126
|
+
const exporters = yield* Config.Array(Schema3.String, "OTEL_LOGS_EXPORTER").pipe(Config.withDefault([]));
|
|
127
|
+
if (disabled || !exporters.some((value) => value.trim().toLowerCase() === "otlp")) {
|
|
128
|
+
return OtlpEvents.disabledLayer;
|
|
129
|
+
}
|
|
130
|
+
const endpoint = yield* Config.URL("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT").pipe(Config.orElse(() => Config.URL("OTEL_EXPORTER_OTLP_ENDPOINT").pipe(Config.map((url) => {
|
|
131
|
+
url.pathname += `${url.pathname.endsWith("/") ? "" : "/"}v1/logs`;
|
|
132
|
+
return url;
|
|
133
|
+
}))), Config.withDefault(undefined));
|
|
134
|
+
if (!endpoint) {
|
|
135
|
+
return OtlpEvents.disabledLayer;
|
|
136
|
+
}
|
|
137
|
+
const headersConfig = (name) => Config.Record(Schema3.String, Schema3.StringFromUriComponent, name);
|
|
138
|
+
const headers = yield* headersConfig("OTEL_EXPORTER_OTLP_LOGS_HEADERS").pipe(Config.orElse(() => headersConfig("OTEL_EXPORTER_OTLP_HEADERS")), Config.withDefault(undefined));
|
|
139
|
+
return OtlpEvents.layer({
|
|
140
|
+
url: endpoint.toString(),
|
|
141
|
+
headers,
|
|
142
|
+
exportInterval: "1 second",
|
|
143
|
+
maxBatchSize: 1000,
|
|
144
|
+
shutdownTimeout: "3 seconds"
|
|
145
|
+
});
|
|
146
|
+
}).pipe(Effect2.orDie));
|
|
147
|
+
|
|
148
|
+
class ServerOtlp {
|
|
149
|
+
static track = track;
|
|
150
|
+
static layer = Layer2.mergeAll(Otlp.layerFromConfig(), ServerEventsLive).pipe(Layer2.provideMerge(OtlpExporter2.layerFlusher), Layer2.provide(OtlpSerialization2.layerJson), Layer2.provide(FetchHttpClient.layer));
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
ServerOtlp
|
|
154
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krak-stack/registry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"description": "Tree-shakable KrakStack components and Effect services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"types": "./dist/services/opentelemetry/browser.d.ts",
|
|
50
50
|
"import": "./dist/services/opentelemetry/browser.js"
|
|
51
51
|
},
|
|
52
|
+
"./opentelemetry/server": {
|
|
53
|
+
"types": "./dist/services/opentelemetry/server.d.ts",
|
|
54
|
+
"import": "./dist/services/opentelemetry/server.js"
|
|
55
|
+
},
|
|
52
56
|
"./docs": {
|
|
53
57
|
"types": "./dist/lib/docs.d.ts",
|
|
54
58
|
"import": "./dist/lib/docs.js"
|