@syncular/observability-sentry 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/browser.d.ts +35 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +155 -0
- package/dist/browser.js.map +1 -0
- package/dist/cloudflare.d.ts +34 -0
- package/dist/cloudflare.d.ts.map +1 -0
- package/dist/cloudflare.js +135 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/shared.d.ts +31 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +147 -0
- package/dist/shared.js.map +1 -0
- package/package.json +55 -0
- package/src/browser.ts +215 -0
- package/src/cloudflare.ts +187 -0
- package/src/index.ts +3 -0
- package/src/shared.test.ts +145 -0
- package/src/shared.ts +200 -0
package/src/shared.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SyncMetricOptions,
|
|
3
|
+
SyncMetrics,
|
|
4
|
+
SyncSpan,
|
|
5
|
+
SyncSpanOptions,
|
|
6
|
+
SyncTelemetry,
|
|
7
|
+
SyncTelemetryAttributes,
|
|
8
|
+
SyncTelemetryAttributeValue,
|
|
9
|
+
SyncTelemetryEvent,
|
|
10
|
+
SyncTelemetryLevel,
|
|
11
|
+
SyncTracer,
|
|
12
|
+
} from '@syncular/core';
|
|
13
|
+
|
|
14
|
+
interface SentryLoggerAdapter {
|
|
15
|
+
trace?(message: string, attributes?: Record<string, unknown>): void;
|
|
16
|
+
debug?(message: string, attributes?: Record<string, unknown>): void;
|
|
17
|
+
info?(message: string, attributes?: Record<string, unknown>): void;
|
|
18
|
+
warn?(message: string, attributes?: Record<string, unknown>): void;
|
|
19
|
+
error?(message: string, attributes?: Record<string, unknown>): void;
|
|
20
|
+
fatal?(message: string, attributes?: Record<string, unknown>): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SentrySpanAdapter {
|
|
24
|
+
setAttribute?(name: string, value: SyncTelemetryAttributeValue): void;
|
|
25
|
+
setAttributes?(attributes: SyncTelemetryAttributes): void;
|
|
26
|
+
setStatus?(status: 'ok' | 'error' | string): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SentryMetricsAdapter {
|
|
30
|
+
count?(name: string, value: number, options?: SyncMetricOptions): void;
|
|
31
|
+
gauge?(name: string, value: number, options?: SyncMetricOptions): void;
|
|
32
|
+
distribution?(name: string, value: number, options?: SyncMetricOptions): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SentryTelemetryAdapter {
|
|
36
|
+
logger?: SentryLoggerAdapter;
|
|
37
|
+
startSpan?<T>(
|
|
38
|
+
options: SyncSpanOptions,
|
|
39
|
+
callback: (span: SentrySpanAdapter) => T
|
|
40
|
+
): T;
|
|
41
|
+
metrics?: SentryMetricsAdapter;
|
|
42
|
+
captureException?(error: unknown): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const noopSpan: SyncSpan = {
|
|
46
|
+
setAttribute() {},
|
|
47
|
+
setAttributes() {},
|
|
48
|
+
setStatus() {},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function resolveLogLevel(event: SyncTelemetryEvent): SyncTelemetryLevel {
|
|
52
|
+
if (event.level) return event.level;
|
|
53
|
+
return event.error ? 'error' : 'info';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveLogMethod(
|
|
57
|
+
logger: SentryLoggerAdapter | undefined,
|
|
58
|
+
level: SyncTelemetryLevel
|
|
59
|
+
): ((message: string, attributes?: Record<string, unknown>) => void) | null {
|
|
60
|
+
if (!logger) return null;
|
|
61
|
+
switch (level) {
|
|
62
|
+
case 'trace':
|
|
63
|
+
return logger.trace ?? logger.debug ?? logger.info ?? null;
|
|
64
|
+
case 'debug':
|
|
65
|
+
return logger.debug ?? logger.info ?? null;
|
|
66
|
+
case 'info':
|
|
67
|
+
return logger.info ?? null;
|
|
68
|
+
case 'warn':
|
|
69
|
+
return logger.warn ?? logger.info ?? null;
|
|
70
|
+
case 'error':
|
|
71
|
+
return logger.error ?? logger.warn ?? logger.info ?? null;
|
|
72
|
+
case 'fatal':
|
|
73
|
+
return logger.fatal ?? logger.error ?? logger.warn ?? logger.info ?? null;
|
|
74
|
+
default:
|
|
75
|
+
return logger.info ?? null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function toSpan(span: SentrySpanAdapter): SyncSpan {
|
|
80
|
+
return {
|
|
81
|
+
setAttribute(name, value) {
|
|
82
|
+
span.setAttribute?.(name, value);
|
|
83
|
+
},
|
|
84
|
+
setAttributes(attributes) {
|
|
85
|
+
if (span.setAttributes) {
|
|
86
|
+
span.setAttributes(attributes);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
91
|
+
span.setAttribute?.(key, value);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
setStatus(status) {
|
|
95
|
+
span.setStatus?.(status);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toLogAttributeValue(
|
|
101
|
+
value: unknown
|
|
102
|
+
): SyncTelemetryAttributeValue | undefined {
|
|
103
|
+
if (typeof value === 'string') return value;
|
|
104
|
+
if (typeof value === 'number') {
|
|
105
|
+
return Number.isFinite(value) ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
if (typeof value === 'boolean') return value;
|
|
108
|
+
if (typeof value === 'bigint') {
|
|
109
|
+
const asNumber = Number(value);
|
|
110
|
+
if (Number.isFinite(asNumber)) return asNumber;
|
|
111
|
+
return value.toString();
|
|
112
|
+
}
|
|
113
|
+
if (value instanceof Date) return value.toISOString();
|
|
114
|
+
if (value === null || value === undefined) return undefined;
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(value) || typeof value === 'object') {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.stringify(value);
|
|
119
|
+
} catch {
|
|
120
|
+
return String(value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return String(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function sanitizeLogAttributes(
|
|
128
|
+
attributes: Record<string, unknown>
|
|
129
|
+
): Record<string, SyncTelemetryAttributeValue> | null {
|
|
130
|
+
const sanitized: Record<string, SyncTelemetryAttributeValue> = {};
|
|
131
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
132
|
+
const normalized = toLogAttributeValue(value);
|
|
133
|
+
if (normalized !== undefined) {
|
|
134
|
+
sanitized[name] = normalized;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return Object.keys(sanitized).length > 0 ? sanitized : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createTracer(adapter: SentryTelemetryAdapter): SyncTracer {
|
|
142
|
+
return {
|
|
143
|
+
startSpan<T>(options: SyncSpanOptions, callback: (span: SyncSpan) => T): T {
|
|
144
|
+
if (!adapter.startSpan) return callback(noopSpan);
|
|
145
|
+
return adapter.startSpan(options, (span) => callback(toSpan(span)));
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function createMetrics(adapter: SentryTelemetryAdapter): SyncMetrics {
|
|
151
|
+
return {
|
|
152
|
+
count(name, value, options) {
|
|
153
|
+
adapter.metrics?.count?.(name, value ?? 1, options);
|
|
154
|
+
},
|
|
155
|
+
gauge(name, value, options) {
|
|
156
|
+
adapter.metrics?.gauge?.(name, value, options);
|
|
157
|
+
},
|
|
158
|
+
distribution(name, value, options) {
|
|
159
|
+
adapter.metrics?.distribution?.(name, value, options);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a Syncular telemetry adapter backed by Sentry primitives.
|
|
166
|
+
*/
|
|
167
|
+
export function createSentrySyncTelemetry(
|
|
168
|
+
adapter: SentryTelemetryAdapter
|
|
169
|
+
): SyncTelemetry {
|
|
170
|
+
return {
|
|
171
|
+
log(event) {
|
|
172
|
+
const level = resolveLogLevel(event);
|
|
173
|
+
const { event: message, ...attributes } = event;
|
|
174
|
+
const logMethod = resolveLogMethod(adapter.logger, level);
|
|
175
|
+
if (!logMethod) return;
|
|
176
|
+
const sanitizedAttributes = sanitizeLogAttributes(attributes);
|
|
177
|
+
if (!sanitizedAttributes) {
|
|
178
|
+
logMethod(message);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
logMethod(message, sanitizedAttributes);
|
|
182
|
+
},
|
|
183
|
+
tracer: createTracer(adapter),
|
|
184
|
+
metrics: createMetrics(adapter),
|
|
185
|
+
captureException(error, context) {
|
|
186
|
+
adapter.captureException?.(error);
|
|
187
|
+
if (!context) return;
|
|
188
|
+
const logMethod =
|
|
189
|
+
resolveLogMethod(adapter.logger, 'error') ??
|
|
190
|
+
resolveLogMethod(adapter.logger, 'info');
|
|
191
|
+
if (!logMethod) return;
|
|
192
|
+
const sanitizedContext = sanitizeLogAttributes(context);
|
|
193
|
+
if (!sanitizedContext) {
|
|
194
|
+
logMethod('sync.exception.context');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
logMethod('sync.exception.context', sanitizedContext);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|