@syncular/core 0.0.1-83 → 0.0.1-89
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +7 -24
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +6 -28
- package/dist/logger.js.map +1 -1
- package/dist/telemetry.d.ts +114 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +113 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/telemetry.test.ts +170 -0
- package/src/index.ts +2 -0
- package/src/logger.ts +10 -52
- package/src/telemetry.ts +238 -0
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,cAAc,SAAS,CAAC;AAExB,cAAc,YAAY,CAAC;AAE3B,cAAc,oBAAoB,CAAC;AAEnC,cAAc,UAAU,CAAC;AAEzB,cAAc,SAAS,CAAC;AAExB,cAAc,WAAW,CAAC;AAE1B,cAAc,UAAU,CAAC;AAEzB,cAAc,cAAc,CAAC;AAE7B,cAAc,SAAS,CAAC;AAExB,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,cAAc,SAAS,CAAC;AAExB,cAAc,YAAY,CAAC;AAE3B,cAAc,oBAAoB,CAAC;AAEnC,cAAc,UAAU,CAAC;AAEzB,cAAc,SAAS,CAAC;AAExB,cAAc,WAAW,CAAC;AAE1B,cAAc,UAAU,CAAC;AAEzB,cAAc,aAAa,CAAC;AAE5B,cAAc,cAAc,CAAC;AAE7B,cAAc,SAAS,CAAC;AAExB,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,8 @@ export * from './proxy/index.js';
|
|
|
23
23
|
export * from './schemas/index.js';
|
|
24
24
|
// Scope types, patterns, and utilities
|
|
25
25
|
export * from './scopes/index.js';
|
|
26
|
+
// Telemetry abstraction
|
|
27
|
+
export * from './telemetry.js';
|
|
26
28
|
// Data transformation hooks
|
|
27
29
|
export * from './transforms.js';
|
|
28
30
|
// Transport and conflict types (protocol types come from ./schemas)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kFAAkF;AAClF,cAAc,SAAS,CAAC;AACxB,+BAA+B;AAC/B,cAAc,YAAY,CAAC;AAC3B,0BAA0B;AAC1B,cAAc,oBAAoB,CAAC;AACnC,oBAAoB;AACpB,cAAc,UAAU,CAAC;AACzB,uBAAuB;AACvB,cAAc,SAAS,CAAC;AACxB,gBAAgB;AAChB,cAAc,WAAW,CAAC;AAC1B,uCAAuC;AACvC,cAAc,UAAU,CAAC;AACzB,4BAA4B;AAC5B,cAAc,cAAc,CAAC;AAC7B,oEAAoE;AACpE,cAAc,SAAS,CAAC;AACxB,2BAA2B;AAC3B,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kFAAkF;AAClF,cAAc,SAAS,CAAC;AACxB,+BAA+B;AAC/B,cAAc,YAAY,CAAC;AAC3B,0BAA0B;AAC1B,cAAc,oBAAoB,CAAC;AACnC,oBAAoB;AACpB,cAAc,UAAU,CAAC;AACzB,uBAAuB;AACvB,cAAc,SAAS,CAAC;AACxB,gBAAgB;AAChB,cAAc,WAAW,CAAC;AAC1B,uCAAuC;AACvC,cAAc,UAAU,CAAC;AACzB,wBAAwB;AACxB,cAAc,aAAa,CAAC;AAC5B,4BAA4B;AAC5B,cAAc,cAAc,CAAC;AAC7B,oEAAoE;AACpE,cAAc,SAAS,CAAC;AACxB,2BAA2B;AAC3B,cAAc,SAAS,CAAC"}
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @syncular/core - Structured logging utilities for sync operations
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Each log event includes a timestamp and event type.
|
|
4
|
+
* Uses the active telemetry backend configured via `configureSyncTelemetry()`.
|
|
6
5
|
*/
|
|
6
|
+
import { type SyncTelemetryEvent } from './telemetry';
|
|
7
7
|
/**
|
|
8
|
-
* Sync log event structure
|
|
8
|
+
* Sync log event structure.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
/** Event type identifier */
|
|
12
|
-
event: string;
|
|
13
|
-
/** User ID (optional) */
|
|
14
|
-
userId?: string;
|
|
15
|
-
/** Operation duration in milliseconds (optional) */
|
|
16
|
-
durationMs?: number;
|
|
17
|
-
/** Number of rows affected (optional) */
|
|
18
|
-
rowCount?: number;
|
|
19
|
-
/** Whether a full reset was required (optional) */
|
|
20
|
-
resetRequired?: boolean;
|
|
21
|
-
/** Error message if operation failed (optional) */
|
|
22
|
-
error?: string;
|
|
23
|
-
/** Additional arbitrary properties */
|
|
24
|
-
[key: string]: unknown;
|
|
25
|
-
}
|
|
10
|
+
export type SyncLogEvent = SyncTelemetryEvent;
|
|
26
11
|
/**
|
|
27
|
-
* Logger function type
|
|
12
|
+
* Logger function type.
|
|
28
13
|
*/
|
|
29
|
-
type SyncLogger = (event: SyncLogEvent) => void;
|
|
14
|
+
export type SyncLogger = (event: SyncLogEvent) => void;
|
|
30
15
|
/**
|
|
31
|
-
* Log a sync event using the
|
|
32
|
-
* For custom logging, create your own logger with createDefaultLogger pattern.
|
|
16
|
+
* Log a sync event using the currently configured telemetry backend.
|
|
33
17
|
*/
|
|
34
18
|
export declare const logSyncEvent: SyncLogger;
|
|
35
19
|
/**
|
|
@@ -42,5 +26,4 @@ export declare const logSyncEvent: SyncLogger;
|
|
|
42
26
|
* logSyncEvent({ event: 'work_complete', durationMs: elapsed() });
|
|
43
27
|
*/
|
|
44
28
|
export declare function createSyncTimer(): () => number;
|
|
45
|
-
export {};
|
|
46
29
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,kBAAkB,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAEvD;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,UAE1B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,MAAM,CAG9C"}
|
package/dist/logger.js
CHANGED
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @syncular/core - Structured logging utilities for sync operations
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Each log event includes a timestamp and event type.
|
|
4
|
+
* Uses the active telemetry backend configured via `configureSyncTelemetry()`.
|
|
6
5
|
*/
|
|
6
|
+
import { getSyncTelemetry } from './telemetry.js';
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Non-blocking - defers logging to avoid blocking the event loop.
|
|
10
|
-
*
|
|
11
|
-
* On server (Node.js), uses setImmediate.
|
|
12
|
-
* On client (browser), uses setTimeout(0).
|
|
13
|
-
*/
|
|
14
|
-
function createDefaultLogger() {
|
|
15
|
-
// Detect environment
|
|
16
|
-
const isNode = typeof globalThis !== 'undefined' &&
|
|
17
|
-
typeof globalThis.setImmediate === 'function';
|
|
18
|
-
const defer = isNode
|
|
19
|
-
? (fn) => globalThis.setImmediate(fn)
|
|
20
|
-
: (fn) => setTimeout(fn, 0);
|
|
21
|
-
return (event) => {
|
|
22
|
-
defer(() => {
|
|
23
|
-
console.log(JSON.stringify({
|
|
24
|
-
timestamp: new Date().toISOString(),
|
|
25
|
-
...event,
|
|
26
|
-
}));
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Log a sync event using the default logger.
|
|
32
|
-
* For custom logging, create your own logger with createDefaultLogger pattern.
|
|
8
|
+
* Log a sync event using the currently configured telemetry backend.
|
|
33
9
|
*/
|
|
34
|
-
export const logSyncEvent =
|
|
10
|
+
export const logSyncEvent = (event) => {
|
|
11
|
+
getSyncTelemetry().log(event);
|
|
12
|
+
};
|
|
35
13
|
/**
|
|
36
14
|
* Create a timer for measuring operation duration.
|
|
37
15
|
* Returns the elapsed time in milliseconds when called.
|
package/dist/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,gBAAgB,EAA2B,MAAM,aAAa,CAAC;AAYxE;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAe,CAAC,KAAK,EAAE,EAAE,CAAC;IACjD,gBAAgB,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAAA,CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,GAAiB;IAC9C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAChC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;AAAA,CACpD"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Runtime telemetry abstraction
|
|
3
|
+
*
|
|
4
|
+
* Provides vendor-neutral logging, tracing, and metrics interfaces so
|
|
5
|
+
* Syncular libraries can emit telemetry without coupling to a specific SDK.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Supported log levels.
|
|
9
|
+
*/
|
|
10
|
+
export type SyncTelemetryLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
11
|
+
/**
|
|
12
|
+
* Primitive attribute value used by traces and metrics.
|
|
13
|
+
*/
|
|
14
|
+
export type SyncTelemetryAttributeValue = string | number | boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Attribute bag used by traces and metrics.
|
|
17
|
+
*/
|
|
18
|
+
export type SyncTelemetryAttributes = Record<string, SyncTelemetryAttributeValue>;
|
|
19
|
+
/**
|
|
20
|
+
* Structured sync log event.
|
|
21
|
+
*/
|
|
22
|
+
export interface SyncTelemetryEvent {
|
|
23
|
+
event: string;
|
|
24
|
+
level?: SyncTelemetryLevel;
|
|
25
|
+
userId?: string;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
rowCount?: number;
|
|
28
|
+
resetRequired?: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Span creation options.
|
|
34
|
+
*/
|
|
35
|
+
export interface SyncSpanOptions {
|
|
36
|
+
name: string;
|
|
37
|
+
op?: string;
|
|
38
|
+
attributes?: SyncTelemetryAttributes;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Span API exposed to Syncular internals.
|
|
42
|
+
*/
|
|
43
|
+
export interface SyncSpan {
|
|
44
|
+
setAttribute(name: string, value: SyncTelemetryAttributeValue): void;
|
|
45
|
+
setAttributes(attributes: SyncTelemetryAttributes): void;
|
|
46
|
+
setStatus(status: 'ok' | 'error'): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Tracing interface.
|
|
50
|
+
*/
|
|
51
|
+
export interface SyncTracer {
|
|
52
|
+
startSpan<T>(options: SyncSpanOptions, callback: (span: SyncSpan) => T): T;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Metric record options.
|
|
56
|
+
*/
|
|
57
|
+
export interface SyncMetricOptions {
|
|
58
|
+
attributes?: SyncTelemetryAttributes;
|
|
59
|
+
unit?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Metrics interface.
|
|
63
|
+
*/
|
|
64
|
+
export interface SyncMetrics {
|
|
65
|
+
count(name: string, value?: number, options?: SyncMetricOptions): void;
|
|
66
|
+
gauge(name: string, value: number, options?: SyncMetricOptions): void;
|
|
67
|
+
distribution(name: string, value: number, options?: SyncMetricOptions): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Unified telemetry interface.
|
|
71
|
+
*/
|
|
72
|
+
export interface SyncTelemetry {
|
|
73
|
+
log(event: SyncTelemetryEvent): void;
|
|
74
|
+
tracer: SyncTracer;
|
|
75
|
+
metrics: SyncMetrics;
|
|
76
|
+
captureException(error: unknown, context?: Record<string, unknown>): void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create console-backed default telemetry (logs only; no-op tracing/metrics).
|
|
80
|
+
*/
|
|
81
|
+
export declare function createDefaultSyncTelemetry(): SyncTelemetry;
|
|
82
|
+
/**
|
|
83
|
+
* Get currently configured telemetry backend.
|
|
84
|
+
*/
|
|
85
|
+
export declare function getSyncTelemetry(): SyncTelemetry;
|
|
86
|
+
/**
|
|
87
|
+
* Replace active telemetry backend.
|
|
88
|
+
*/
|
|
89
|
+
export declare function configureSyncTelemetry(telemetry: SyncTelemetry): void;
|
|
90
|
+
/**
|
|
91
|
+
* Reset telemetry backend to default console implementation.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resetSyncTelemetry(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Capture an exception through the active telemetry backend.
|
|
96
|
+
*/
|
|
97
|
+
export declare function captureSyncException(error: unknown, context?: Record<string, unknown>): void;
|
|
98
|
+
/**
|
|
99
|
+
* Start a span through the active telemetry backend.
|
|
100
|
+
*/
|
|
101
|
+
export declare function startSyncSpan<T>(options: SyncSpanOptions, callback: (span: SyncSpan) => T): T;
|
|
102
|
+
/**
|
|
103
|
+
* Record a counter metric through the active telemetry backend.
|
|
104
|
+
*/
|
|
105
|
+
export declare function countSyncMetric(name: string, value?: number, options?: SyncMetricOptions): void;
|
|
106
|
+
/**
|
|
107
|
+
* Record a gauge metric through the active telemetry backend.
|
|
108
|
+
*/
|
|
109
|
+
export declare function gaugeSyncMetric(name: string, value: number, options?: SyncMetricOptions): void;
|
|
110
|
+
/**
|
|
111
|
+
* Record a distribution metric through the active telemetry backend.
|
|
112
|
+
*/
|
|
113
|
+
export declare function distributionSyncMetric(name: string, value: number, options?: SyncMetricOptions): void;
|
|
114
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Runtime telemetry abstraction
|
|
3
|
+
*
|
|
4
|
+
* Provides vendor-neutral logging, tracing, and metrics interfaces so
|
|
5
|
+
* Syncular libraries can emit telemetry without coupling to a specific SDK.
|
|
6
|
+
*/
|
|
7
|
+
const noopSpan = {
|
|
8
|
+
setAttribute() { },
|
|
9
|
+
setAttributes() { },
|
|
10
|
+
setStatus() { },
|
|
11
|
+
};
|
|
12
|
+
const noopTracer = {
|
|
13
|
+
startSpan(_options, callback) {
|
|
14
|
+
return callback(noopSpan);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const noopMetrics = {
|
|
18
|
+
count() { },
|
|
19
|
+
gauge() { },
|
|
20
|
+
distribution() { },
|
|
21
|
+
};
|
|
22
|
+
function createConsoleLogger() {
|
|
23
|
+
const isNode = typeof globalThis !== 'undefined' &&
|
|
24
|
+
typeof globalThis.setImmediate === 'function';
|
|
25
|
+
const defer = isNode
|
|
26
|
+
? (fn) => globalThis.setImmediate(fn)
|
|
27
|
+
: (fn) => setTimeout(fn, 0);
|
|
28
|
+
return (event) => {
|
|
29
|
+
defer(() => {
|
|
30
|
+
const level = event.level ?? (event.error ? 'error' : 'info');
|
|
31
|
+
const payload = {
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
level,
|
|
34
|
+
...event,
|
|
35
|
+
};
|
|
36
|
+
console.log(JSON.stringify(payload));
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create console-backed default telemetry (logs only; no-op tracing/metrics).
|
|
42
|
+
*/
|
|
43
|
+
export function createDefaultSyncTelemetry() {
|
|
44
|
+
const logger = createConsoleLogger();
|
|
45
|
+
return {
|
|
46
|
+
log(event) {
|
|
47
|
+
logger(event);
|
|
48
|
+
},
|
|
49
|
+
tracer: noopTracer,
|
|
50
|
+
metrics: noopMetrics,
|
|
51
|
+
captureException(error, context) {
|
|
52
|
+
const message = error instanceof Error
|
|
53
|
+
? error.message
|
|
54
|
+
: `Unknown error: ${String(error)}`;
|
|
55
|
+
logger({
|
|
56
|
+
event: 'sync.exception',
|
|
57
|
+
level: 'error',
|
|
58
|
+
error: message,
|
|
59
|
+
...(context ?? {}),
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
let activeSyncTelemetry = createDefaultSyncTelemetry();
|
|
65
|
+
/**
|
|
66
|
+
* Get currently configured telemetry backend.
|
|
67
|
+
*/
|
|
68
|
+
export function getSyncTelemetry() {
|
|
69
|
+
return activeSyncTelemetry;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Replace active telemetry backend.
|
|
73
|
+
*/
|
|
74
|
+
export function configureSyncTelemetry(telemetry) {
|
|
75
|
+
activeSyncTelemetry = telemetry;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Reset telemetry backend to default console implementation.
|
|
79
|
+
*/
|
|
80
|
+
export function resetSyncTelemetry() {
|
|
81
|
+
activeSyncTelemetry = createDefaultSyncTelemetry();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Capture an exception through the active telemetry backend.
|
|
85
|
+
*/
|
|
86
|
+
export function captureSyncException(error, context) {
|
|
87
|
+
activeSyncTelemetry.captureException(error, context);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start a span through the active telemetry backend.
|
|
91
|
+
*/
|
|
92
|
+
export function startSyncSpan(options, callback) {
|
|
93
|
+
return activeSyncTelemetry.tracer.startSpan(options, callback);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Record a counter metric through the active telemetry backend.
|
|
97
|
+
*/
|
|
98
|
+
export function countSyncMetric(name, value, options) {
|
|
99
|
+
activeSyncTelemetry.metrics.count(name, value, options);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Record a gauge metric through the active telemetry backend.
|
|
103
|
+
*/
|
|
104
|
+
export function gaugeSyncMetric(name, value, options) {
|
|
105
|
+
activeSyncTelemetry.metrics.gauge(name, value, options);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Record a distribution metric through the active telemetry backend.
|
|
109
|
+
*/
|
|
110
|
+
export function distributionSyncMetric(name, value, options) {
|
|
111
|
+
activeSyncTelemetry.metrics.distribution(name, value, options);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { logSyncEvent } from '../logger';
|
|
3
|
+
import {
|
|
4
|
+
captureSyncException,
|
|
5
|
+
configureSyncTelemetry,
|
|
6
|
+
countSyncMetric,
|
|
7
|
+
distributionSyncMetric,
|
|
8
|
+
gaugeSyncMetric,
|
|
9
|
+
getSyncTelemetry,
|
|
10
|
+
resetSyncTelemetry,
|
|
11
|
+
type SyncMetricOptions,
|
|
12
|
+
type SyncSpan,
|
|
13
|
+
type SyncSpanOptions,
|
|
14
|
+
type SyncTelemetry,
|
|
15
|
+
type SyncTelemetryEvent,
|
|
16
|
+
startSyncSpan,
|
|
17
|
+
} from '../telemetry';
|
|
18
|
+
|
|
19
|
+
interface CapturedCountMetric {
|
|
20
|
+
name: string;
|
|
21
|
+
value: number | undefined;
|
|
22
|
+
options: SyncMetricOptions | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CapturedValueMetric {
|
|
26
|
+
name: string;
|
|
27
|
+
value: number;
|
|
28
|
+
options: SyncMetricOptions | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createTestTelemetry(calls: {
|
|
32
|
+
logs: SyncTelemetryEvent[];
|
|
33
|
+
countMetrics: CapturedCountMetric[];
|
|
34
|
+
gaugeMetrics: CapturedValueMetric[];
|
|
35
|
+
distributionMetrics: CapturedValueMetric[];
|
|
36
|
+
spans: SyncSpanOptions[];
|
|
37
|
+
exceptions: Array<{
|
|
38
|
+
error: unknown;
|
|
39
|
+
context: Record<string, unknown> | undefined;
|
|
40
|
+
}>;
|
|
41
|
+
}): SyncTelemetry {
|
|
42
|
+
return {
|
|
43
|
+
log(event) {
|
|
44
|
+
calls.logs.push(event);
|
|
45
|
+
},
|
|
46
|
+
tracer: {
|
|
47
|
+
startSpan(options, callback) {
|
|
48
|
+
calls.spans.push(options);
|
|
49
|
+
const span: SyncSpan = {
|
|
50
|
+
setAttribute() {},
|
|
51
|
+
setAttributes() {},
|
|
52
|
+
setStatus() {},
|
|
53
|
+
};
|
|
54
|
+
return callback(span);
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
metrics: {
|
|
58
|
+
count(name, value, options) {
|
|
59
|
+
calls.countMetrics.push({ name, value, options });
|
|
60
|
+
},
|
|
61
|
+
gauge(name, value, options) {
|
|
62
|
+
calls.gaugeMetrics.push({ name, value, options });
|
|
63
|
+
},
|
|
64
|
+
distribution(name, value, options) {
|
|
65
|
+
calls.distributionMetrics.push({ name, value, options });
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
captureException(error, context) {
|
|
69
|
+
calls.exceptions.push({ error, context });
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe('sync telemetry configuration', () => {
|
|
75
|
+
test('routes logger, metrics, spans, and exceptions to configured backend', () => {
|
|
76
|
+
const calls = {
|
|
77
|
+
logs: [] as SyncTelemetryEvent[],
|
|
78
|
+
countMetrics: [] as CapturedCountMetric[],
|
|
79
|
+
gaugeMetrics: [] as CapturedValueMetric[],
|
|
80
|
+
distributionMetrics: [] as CapturedValueMetric[],
|
|
81
|
+
spans: [] as SyncSpanOptions[],
|
|
82
|
+
exceptions: [] as Array<{
|
|
83
|
+
error: unknown;
|
|
84
|
+
context: Record<string, unknown> | undefined;
|
|
85
|
+
}>,
|
|
86
|
+
};
|
|
87
|
+
const telemetry = createTestTelemetry(calls);
|
|
88
|
+
const previous = getSyncTelemetry();
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
configureSyncTelemetry(telemetry);
|
|
92
|
+
|
|
93
|
+
logSyncEvent({ event: 'sync.test.log', rowCount: 3 });
|
|
94
|
+
|
|
95
|
+
const spanResult = startSyncSpan(
|
|
96
|
+
{
|
|
97
|
+
name: 'sync.test.span',
|
|
98
|
+
op: 'sync.test',
|
|
99
|
+
attributes: { transport: 'ws' },
|
|
100
|
+
},
|
|
101
|
+
() => 'done'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
countSyncMetric('sync.test.count', 2, {
|
|
105
|
+
attributes: { source: 'unit-test' },
|
|
106
|
+
});
|
|
107
|
+
gaugeSyncMetric('sync.test.gauge', 7, { unit: 'millisecond' });
|
|
108
|
+
distributionSyncMetric('sync.test.dist', 13);
|
|
109
|
+
captureSyncException(new Error('boom'), {
|
|
110
|
+
operation: 'unit-test',
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
expect(spanResult).toBe('done');
|
|
114
|
+
expect(calls.logs).toEqual([{ event: 'sync.test.log', rowCount: 3 }]);
|
|
115
|
+
expect(calls.spans).toEqual([
|
|
116
|
+
{
|
|
117
|
+
name: 'sync.test.span',
|
|
118
|
+
op: 'sync.test',
|
|
119
|
+
attributes: { transport: 'ws' },
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
expect(calls.countMetrics).toEqual([
|
|
123
|
+
{
|
|
124
|
+
name: 'sync.test.count',
|
|
125
|
+
value: 2,
|
|
126
|
+
options: { attributes: { source: 'unit-test' } },
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
expect(calls.gaugeMetrics).toEqual([
|
|
130
|
+
{
|
|
131
|
+
name: 'sync.test.gauge',
|
|
132
|
+
value: 7,
|
|
133
|
+
options: { unit: 'millisecond' },
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
expect(calls.distributionMetrics).toEqual([
|
|
137
|
+
{
|
|
138
|
+
name: 'sync.test.dist',
|
|
139
|
+
value: 13,
|
|
140
|
+
options: undefined,
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
expect(calls.exceptions).toHaveLength(1);
|
|
144
|
+
expect(calls.exceptions[0]?.context).toEqual({ operation: 'unit-test' });
|
|
145
|
+
} finally {
|
|
146
|
+
configureSyncTelemetry(previous);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('resetSyncTelemetry swaps out custom telemetry backend', () => {
|
|
151
|
+
const calls = {
|
|
152
|
+
logs: [] as SyncTelemetryEvent[],
|
|
153
|
+
countMetrics: [] as CapturedCountMetric[],
|
|
154
|
+
gaugeMetrics: [] as CapturedValueMetric[],
|
|
155
|
+
distributionMetrics: [] as CapturedValueMetric[],
|
|
156
|
+
spans: [] as SyncSpanOptions[],
|
|
157
|
+
exceptions: [] as Array<{
|
|
158
|
+
error: unknown;
|
|
159
|
+
context: Record<string, unknown> | undefined;
|
|
160
|
+
}>,
|
|
161
|
+
};
|
|
162
|
+
const telemetry = createTestTelemetry(calls);
|
|
163
|
+
|
|
164
|
+
configureSyncTelemetry(telemetry);
|
|
165
|
+
resetSyncTelemetry();
|
|
166
|
+
logSyncEvent({ event: 'sync.default.logger' });
|
|
167
|
+
|
|
168
|
+
expect(calls.logs).toHaveLength(0);
|
|
169
|
+
});
|
|
170
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,8 @@ export * from './proxy';
|
|
|
24
24
|
export * from './schemas';
|
|
25
25
|
// Scope types, patterns, and utilities
|
|
26
26
|
export * from './scopes';
|
|
27
|
+
// Telemetry abstraction
|
|
28
|
+
export * from './telemetry';
|
|
27
29
|
// Data transformation hooks
|
|
28
30
|
export * from './transforms';
|
|
29
31
|
// Transport and conflict types (protocol types come from ./schemas)
|
package/src/logger.ts
CHANGED
|
@@ -1,69 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @syncular/core - Structured logging utilities for sync operations
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Each log event includes a timestamp and event type.
|
|
4
|
+
* Uses the active telemetry backend configured via `configureSyncTelemetry()`.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
* Sync log event structure
|
|
10
|
-
*/
|
|
11
|
-
interface SyncLogEvent {
|
|
12
|
-
/** Event type identifier */
|
|
13
|
-
event: string;
|
|
14
|
-
/** User ID (optional) */
|
|
15
|
-
userId?: string;
|
|
16
|
-
/** Operation duration in milliseconds (optional) */
|
|
17
|
-
durationMs?: number;
|
|
18
|
-
/** Number of rows affected (optional) */
|
|
19
|
-
rowCount?: number;
|
|
20
|
-
/** Whether a full reset was required (optional) */
|
|
21
|
-
resetRequired?: boolean;
|
|
22
|
-
/** Error message if operation failed (optional) */
|
|
23
|
-
error?: string;
|
|
24
|
-
/** Additional arbitrary properties */
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
}
|
|
7
|
+
import { getSyncTelemetry, type SyncTelemetryEvent } from './telemetry';
|
|
27
8
|
|
|
28
9
|
/**
|
|
29
|
-
*
|
|
10
|
+
* Sync log event structure.
|
|
30
11
|
*/
|
|
31
|
-
type
|
|
12
|
+
export type SyncLogEvent = SyncTelemetryEvent;
|
|
32
13
|
|
|
33
14
|
/**
|
|
34
|
-
*
|
|
35
|
-
* Non-blocking - defers logging to avoid blocking the event loop.
|
|
36
|
-
*
|
|
37
|
-
* On server (Node.js), uses setImmediate.
|
|
38
|
-
* On client (browser), uses setTimeout(0).
|
|
15
|
+
* Logger function type.
|
|
39
16
|
*/
|
|
40
|
-
|
|
41
|
-
// Detect environment
|
|
42
|
-
const isNode =
|
|
43
|
-
typeof globalThis !== 'undefined' &&
|
|
44
|
-
typeof globalThis.setImmediate === 'function';
|
|
45
|
-
|
|
46
|
-
const defer = isNode
|
|
47
|
-
? (fn: () => void) => globalThis.setImmediate(fn)
|
|
48
|
-
: (fn: () => void) => setTimeout(fn, 0);
|
|
49
|
-
|
|
50
|
-
return (event: SyncLogEvent) => {
|
|
51
|
-
defer(() => {
|
|
52
|
-
console.log(
|
|
53
|
-
JSON.stringify({
|
|
54
|
-
timestamp: new Date().toISOString(),
|
|
55
|
-
...event,
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
}
|
|
17
|
+
export type SyncLogger = (event: SyncLogEvent) => void;
|
|
61
18
|
|
|
62
19
|
/**
|
|
63
|
-
* Log a sync event using the
|
|
64
|
-
* For custom logging, create your own logger with createDefaultLogger pattern.
|
|
20
|
+
* Log a sync event using the currently configured telemetry backend.
|
|
65
21
|
*/
|
|
66
|
-
export const logSyncEvent: SyncLogger =
|
|
22
|
+
export const logSyncEvent: SyncLogger = (event) => {
|
|
23
|
+
getSyncTelemetry().log(event);
|
|
24
|
+
};
|
|
67
25
|
|
|
68
26
|
/**
|
|
69
27
|
* Create a timer for measuring operation duration.
|
package/src/telemetry.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/core - Runtime telemetry abstraction
|
|
3
|
+
*
|
|
4
|
+
* Provides vendor-neutral logging, tracing, and metrics interfaces so
|
|
5
|
+
* Syncular libraries can emit telemetry without coupling to a specific SDK.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported log levels.
|
|
10
|
+
*/
|
|
11
|
+
export type SyncTelemetryLevel =
|
|
12
|
+
| 'trace'
|
|
13
|
+
| 'debug'
|
|
14
|
+
| 'info'
|
|
15
|
+
| 'warn'
|
|
16
|
+
| 'error'
|
|
17
|
+
| 'fatal';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Primitive attribute value used by traces and metrics.
|
|
21
|
+
*/
|
|
22
|
+
export type SyncTelemetryAttributeValue = string | number | boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attribute bag used by traces and metrics.
|
|
26
|
+
*/
|
|
27
|
+
export type SyncTelemetryAttributes = Record<
|
|
28
|
+
string,
|
|
29
|
+
SyncTelemetryAttributeValue
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Structured sync log event.
|
|
34
|
+
*/
|
|
35
|
+
export interface SyncTelemetryEvent {
|
|
36
|
+
event: string;
|
|
37
|
+
level?: SyncTelemetryLevel;
|
|
38
|
+
userId?: string;
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
rowCount?: number;
|
|
41
|
+
resetRequired?: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Span creation options.
|
|
48
|
+
*/
|
|
49
|
+
export interface SyncSpanOptions {
|
|
50
|
+
name: string;
|
|
51
|
+
op?: string;
|
|
52
|
+
attributes?: SyncTelemetryAttributes;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Span API exposed to Syncular internals.
|
|
57
|
+
*/
|
|
58
|
+
export interface SyncSpan {
|
|
59
|
+
setAttribute(name: string, value: SyncTelemetryAttributeValue): void;
|
|
60
|
+
setAttributes(attributes: SyncTelemetryAttributes): void;
|
|
61
|
+
setStatus(status: 'ok' | 'error'): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Tracing interface.
|
|
66
|
+
*/
|
|
67
|
+
export interface SyncTracer {
|
|
68
|
+
startSpan<T>(options: SyncSpanOptions, callback: (span: SyncSpan) => T): T;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Metric record options.
|
|
73
|
+
*/
|
|
74
|
+
export interface SyncMetricOptions {
|
|
75
|
+
attributes?: SyncTelemetryAttributes;
|
|
76
|
+
unit?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Metrics interface.
|
|
81
|
+
*/
|
|
82
|
+
export interface SyncMetrics {
|
|
83
|
+
count(name: string, value?: number, options?: SyncMetricOptions): void;
|
|
84
|
+
gauge(name: string, value: number, options?: SyncMetricOptions): void;
|
|
85
|
+
distribution(name: string, value: number, options?: SyncMetricOptions): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Unified telemetry interface.
|
|
90
|
+
*/
|
|
91
|
+
export interface SyncTelemetry {
|
|
92
|
+
log(event: SyncTelemetryEvent): void;
|
|
93
|
+
tracer: SyncTracer;
|
|
94
|
+
metrics: SyncMetrics;
|
|
95
|
+
captureException(error: unknown, context?: Record<string, unknown>): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const noopSpan: SyncSpan = {
|
|
99
|
+
setAttribute() {},
|
|
100
|
+
setAttributes() {},
|
|
101
|
+
setStatus() {},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const noopTracer: SyncTracer = {
|
|
105
|
+
startSpan(_options, callback) {
|
|
106
|
+
return callback(noopSpan);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const noopMetrics: SyncMetrics = {
|
|
111
|
+
count() {},
|
|
112
|
+
gauge() {},
|
|
113
|
+
distribution() {},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function createConsoleLogger(): (event: SyncTelemetryEvent) => void {
|
|
117
|
+
const isNode =
|
|
118
|
+
typeof globalThis !== 'undefined' &&
|
|
119
|
+
typeof globalThis.setImmediate === 'function';
|
|
120
|
+
|
|
121
|
+
const defer = isNode
|
|
122
|
+
? (fn: () => void) => globalThis.setImmediate(fn)
|
|
123
|
+
: (fn: () => void) => setTimeout(fn, 0);
|
|
124
|
+
|
|
125
|
+
return (event: SyncTelemetryEvent) => {
|
|
126
|
+
defer(() => {
|
|
127
|
+
const level = event.level ?? (event.error ? 'error' : 'info');
|
|
128
|
+
const payload = {
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
level,
|
|
131
|
+
...event,
|
|
132
|
+
};
|
|
133
|
+
console.log(JSON.stringify(payload));
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create console-backed default telemetry (logs only; no-op tracing/metrics).
|
|
140
|
+
*/
|
|
141
|
+
export function createDefaultSyncTelemetry(): SyncTelemetry {
|
|
142
|
+
const logger = createConsoleLogger();
|
|
143
|
+
return {
|
|
144
|
+
log(event) {
|
|
145
|
+
logger(event);
|
|
146
|
+
},
|
|
147
|
+
tracer: noopTracer,
|
|
148
|
+
metrics: noopMetrics,
|
|
149
|
+
captureException(error, context) {
|
|
150
|
+
const message =
|
|
151
|
+
error instanceof Error
|
|
152
|
+
? error.message
|
|
153
|
+
: `Unknown error: ${String(error)}`;
|
|
154
|
+
logger({
|
|
155
|
+
event: 'sync.exception',
|
|
156
|
+
level: 'error',
|
|
157
|
+
error: message,
|
|
158
|
+
...(context ?? {}),
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let activeSyncTelemetry: SyncTelemetry = createDefaultSyncTelemetry();
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get currently configured telemetry backend.
|
|
168
|
+
*/
|
|
169
|
+
export function getSyncTelemetry(): SyncTelemetry {
|
|
170
|
+
return activeSyncTelemetry;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Replace active telemetry backend.
|
|
175
|
+
*/
|
|
176
|
+
export function configureSyncTelemetry(telemetry: SyncTelemetry): void {
|
|
177
|
+
activeSyncTelemetry = telemetry;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Reset telemetry backend to default console implementation.
|
|
182
|
+
*/
|
|
183
|
+
export function resetSyncTelemetry(): void {
|
|
184
|
+
activeSyncTelemetry = createDefaultSyncTelemetry();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Capture an exception through the active telemetry backend.
|
|
189
|
+
*/
|
|
190
|
+
export function captureSyncException(
|
|
191
|
+
error: unknown,
|
|
192
|
+
context?: Record<string, unknown>
|
|
193
|
+
): void {
|
|
194
|
+
activeSyncTelemetry.captureException(error, context);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Start a span through the active telemetry backend.
|
|
199
|
+
*/
|
|
200
|
+
export function startSyncSpan<T>(
|
|
201
|
+
options: SyncSpanOptions,
|
|
202
|
+
callback: (span: SyncSpan) => T
|
|
203
|
+
): T {
|
|
204
|
+
return activeSyncTelemetry.tracer.startSpan(options, callback);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Record a counter metric through the active telemetry backend.
|
|
209
|
+
*/
|
|
210
|
+
export function countSyncMetric(
|
|
211
|
+
name: string,
|
|
212
|
+
value?: number,
|
|
213
|
+
options?: SyncMetricOptions
|
|
214
|
+
): void {
|
|
215
|
+
activeSyncTelemetry.metrics.count(name, value, options);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Record a gauge metric through the active telemetry backend.
|
|
220
|
+
*/
|
|
221
|
+
export function gaugeSyncMetric(
|
|
222
|
+
name: string,
|
|
223
|
+
value: number,
|
|
224
|
+
options?: SyncMetricOptions
|
|
225
|
+
): void {
|
|
226
|
+
activeSyncTelemetry.metrics.gauge(name, value, options);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Record a distribution metric through the active telemetry backend.
|
|
231
|
+
*/
|
|
232
|
+
export function distributionSyncMetric(
|
|
233
|
+
name: string,
|
|
234
|
+
value: number,
|
|
235
|
+
options?: SyncMetricOptions
|
|
236
|
+
): void {
|
|
237
|
+
activeSyncTelemetry.metrics.distribution(name, value, options);
|
|
238
|
+
}
|