@statly/observe 1.0.0 → 1.1.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 CHANGED
@@ -12,6 +12,8 @@ Error tracking and monitoring for JavaScript and TypeScript applications. Captur
12
12
  ## Features
13
13
 
14
14
  - Automatic error capturing with stack traces
15
+ - **Distributed Tracing**: Visualize function execution and call hierarchies
16
+ - **Performance Metrics**: Automated capture of latency and success rates
15
17
  - Breadcrumbs for debugging context
16
18
  - User context tracking
17
19
  - Release tracking
@@ -86,10 +88,45 @@ Statly.addBreadcrumb({
86
88
  level: 'info',
87
89
  });
88
90
 
89
- // Flush before exit (important for serverless)
91
+ # Flush before exit (important for serverless)
90
92
  await Statly.close();
91
93
  ```
92
94
 
95
+ ## Tracing & Performance
96
+
97
+ Statly Observe supports distributed tracing to help you visualize execution flow and measure backend performance.
98
+
99
+ ### Automatic Tracing
100
+
101
+ Use `Statly.trace()` to wrap functions. It works with both synchronous and asynchronous code:
102
+
103
+ ```typescript
104
+ import { Statly } from '@statly/observe';
105
+
106
+ const result = await Statly.trace('process_payment', async (span) => {
107
+ span.setTag('provider', 'stripe');
108
+
109
+ // Your logic here
110
+ const payment = await stripe.charges.create({...});
111
+
112
+ return payment;
113
+ });
114
+ ```
115
+
116
+ ### Manual Spans
117
+
118
+ For low-level control, you can start spans manually:
119
+
120
+ ```typescript
121
+ const span = Statly.startSpan('database_query');
122
+ try {
123
+ await db.query('...');
124
+ span.setTag('query_type', 'SELECT');
125
+ } finally {
126
+ span.finish(); // Reports to Statly
127
+ }
128
+ ```
129
+
93
130
  ## Framework Integrations
94
131
 
95
132
  ### Express
@@ -1,3 +1,7 @@
1
+ import {
2
+ TelemetryProvider
3
+ } from "./chunk-J5AHUFP2.mjs";
4
+
1
5
  // src/transport.ts
2
6
  var Transport = class {
3
7
  constructor(options) {
@@ -356,6 +360,7 @@ var StatlyClient = class {
356
360
  this.breadcrumbs = new BreadcrumbManager(this.options.maxBreadcrumbs);
357
361
  this.globalHandlers = new GlobalHandlers();
358
362
  this.consoleIntegration = new ConsoleIntegration();
363
+ TelemetryProvider.getInstance().setClient(this);
359
364
  }
360
365
  mergeOptions(options) {
361
366
  return {
@@ -441,6 +446,30 @@ var StatlyClient = class {
441
446
  });
442
447
  return this.sendEvent(event);
443
448
  }
449
+ /**
450
+ * Capture a completed span
451
+ */
452
+ captureSpan(span) {
453
+ const event = this.buildEvent({
454
+ message: `Span: ${span.name}`,
455
+ level: "span",
456
+ span: span.toDict()
457
+ });
458
+ return this.sendEvent(event);
459
+ }
460
+ /**
461
+ * Start a new tracing span
462
+ */
463
+ startSpan(name, tags) {
464
+ return TelemetryProvider.getInstance().startSpan(name, tags);
465
+ }
466
+ /**
467
+ * Execute a function within a trace span
468
+ */
469
+ async trace(name, operation, tags) {
470
+ const { trace: traceFn } = await import("./telemetry-CXHOTW3Y.mjs");
471
+ return traceFn(name, operation, tags);
472
+ }
444
473
  /**
445
474
  * Internal method to capture an error
446
475
  */
@@ -652,66 +681,81 @@ var StatlyClient = class {
652
681
  // src/integrations/nextjs.ts
653
682
  function withStatlyPagesApi(handler) {
654
683
  return async (req, res) => {
655
- Statly.addBreadcrumb({
656
- category: "http",
657
- message: `${req.method} ${req.url}`,
658
- level: "info",
659
- data: {
660
- method: req.method,
661
- url: req.url
662
- }
663
- });
664
- try {
665
- return await handler(req, res);
666
- } catch (error) {
667
- const context = {
668
- request: {
684
+ return Statly.trace(`${req.method} ${req.url}`, async (span) => {
685
+ span.setTag("component", "nextjs-pages-api");
686
+ span.setTag("http.method", req.method || "GET");
687
+ span.setTag("http.url", req.url || "unknown");
688
+ Statly.addBreadcrumb({
689
+ category: "http",
690
+ message: `${req.method} ${req.url}`,
691
+ level: "info",
692
+ data: {
669
693
  method: req.method,
670
- url: req.url,
671
- headers: sanitizeHeaders(req.headers),
672
- query: req.query
694
+ url: req.url
673
695
  }
674
- };
675
- Statly.captureException(error, context);
676
- throw error;
677
- }
696
+ });
697
+ try {
698
+ const result = await handler(req, res);
699
+ return result;
700
+ } catch (error) {
701
+ const context = {
702
+ request: {
703
+ method: req.method,
704
+ url: req.url,
705
+ headers: sanitizeHeaders(req.headers),
706
+ query: req.query
707
+ }
708
+ };
709
+ Statly.captureException(error, context);
710
+ throw error;
711
+ }
712
+ });
678
713
  };
679
714
  }
680
715
  function withStatly(handler) {
681
716
  const wrappedHandler = async (request, context) => {
682
- Statly.addBreadcrumb({
683
- category: "http",
684
- message: `${request.method} ${request.nextUrl?.pathname || request.url}`,
685
- level: "info",
686
- data: {
687
- method: request.method,
688
- url: request.nextUrl?.pathname || request.url
689
- }
690
- });
691
- try {
692
- return await handler(request, context);
693
- } catch (error) {
694
- const headers = {};
695
- request.headers.forEach((value, key) => {
696
- headers[key] = value;
697
- });
698
- const errorContext = {
699
- request: {
717
+ return Statly.trace(`${request.method} ${request.nextUrl?.pathname || request.url}`, async (span) => {
718
+ span.setTag("component", "nextjs-app-router");
719
+ span.setTag("http.method", request.method);
720
+ span.setTag("http.url", request.nextUrl?.pathname || request.url);
721
+ Statly.addBreadcrumb({
722
+ category: "http",
723
+ message: `${request.method} ${request.nextUrl?.pathname || request.url}`,
724
+ level: "info",
725
+ data: {
700
726
  method: request.method,
701
- url: request.nextUrl?.pathname || request.url,
702
- headers: sanitizeHeaders(headers),
703
- searchParams: request.nextUrl?.searchParams?.toString()
727
+ url: request.nextUrl?.pathname || request.url
704
728
  }
705
- };
706
- if (context?.params) {
707
- try {
708
- errorContext.params = await context.params;
709
- } catch {
729
+ });
730
+ try {
731
+ const result = await handler(request, context);
732
+ if (result instanceof Response) {
733
+ span.setTag("http.status_code", result.status.toString());
734
+ }
735
+ return result;
736
+ } catch (error) {
737
+ const headers = {};
738
+ request.headers.forEach((value, key) => {
739
+ headers[key] = value;
740
+ });
741
+ const errorContext = {
742
+ request: {
743
+ method: request.method,
744
+ url: request.nextUrl?.pathname || request.url,
745
+ headers: sanitizeHeaders(headers),
746
+ searchParams: request.nextUrl?.searchParams?.toString()
747
+ }
748
+ };
749
+ if (context?.params) {
750
+ try {
751
+ errorContext.params = await context.params;
752
+ } catch {
753
+ }
710
754
  }
755
+ Statly.captureException(error, errorContext);
756
+ throw error;
711
757
  }
712
- Statly.captureException(error, errorContext);
713
- throw error;
714
- }
758
+ });
715
759
  };
716
760
  return wrappedHandler;
717
761
  }
@@ -750,20 +794,24 @@ function withStatlyGetStaticProps(handler) {
750
794
  }
751
795
  function withStatlyServerAction(action, actionName) {
752
796
  return async (...args) => {
753
- Statly.addBreadcrumb({
754
- category: "action",
755
- message: `Server action: ${actionName || "unknown"}`,
756
- level: "info"
757
- });
758
- try {
759
- return await action(...args);
760
- } catch (error) {
761
- Statly.captureException(error, {
762
- source: "server-action",
763
- actionName
797
+ return Statly.trace(`Action: ${actionName || "unknown"}`, async (span) => {
798
+ span.setTag("component", "nextjs-server-action");
799
+ span.setTag("action.name", actionName || "unknown");
800
+ Statly.addBreadcrumb({
801
+ category: "action",
802
+ message: `Server action: ${actionName || "unknown"}`,
803
+ level: "info"
764
804
  });
765
- throw error;
766
- }
805
+ try {
806
+ return await action(...args);
807
+ } catch (error) {
808
+ Statly.captureException(error, {
809
+ source: "server-action",
810
+ actionName
811
+ });
812
+ throw error;
813
+ }
814
+ });
767
815
  };
768
816
  }
769
817
  function sanitizeHeaders(headers) {
@@ -976,6 +1024,20 @@ async function close() {
976
1024
  function getClient() {
977
1025
  return client;
978
1026
  }
1027
+ async function trace(name, operation, tags) {
1028
+ if (!client) {
1029
+ return operation(null);
1030
+ }
1031
+ return client.trace(name, operation, tags);
1032
+ }
1033
+ function startSpan(name, tags) {
1034
+ if (!client) return null;
1035
+ return client.startSpan(name, tags);
1036
+ }
1037
+ function captureSpan(span) {
1038
+ if (!client) return "";
1039
+ return client.captureSpan(span);
1040
+ }
979
1041
  var Statly = {
980
1042
  init,
981
1043
  captureException,
@@ -986,7 +1048,10 @@ var Statly = {
986
1048
  addBreadcrumb,
987
1049
  flush,
988
1050
  close,
989
- getClient
1051
+ getClient,
1052
+ trace,
1053
+ startSpan,
1054
+ captureSpan
990
1055
  };
991
1056
 
992
1057
  // src/integrations/express.ts
@@ -1110,5 +1175,8 @@ export {
1110
1175
  flush,
1111
1176
  close,
1112
1177
  getClient,
1178
+ trace,
1179
+ startSpan,
1180
+ captureSpan,
1113
1181
  Statly
1114
1182
  };
@@ -0,0 +1,135 @@
1
+ // src/span.ts
2
+ var Span = class {
3
+ constructor(name, context, tags) {
4
+ this._status = "ok" /* OK */;
5
+ this._tags = {};
6
+ this._metadata = {};
7
+ this._finished = false;
8
+ this.name = name;
9
+ this.context = context;
10
+ this.startTime = Date.now();
11
+ if (tags) this._tags = { ...tags };
12
+ }
13
+ /**
14
+ * Finish the span and calculate duration
15
+ */
16
+ finish(endTime) {
17
+ if (this._finished) return;
18
+ this._endTime = endTime || Date.now();
19
+ this._durationMs = this._endTime - this.startTime;
20
+ this._finished = true;
21
+ }
22
+ setTag(key, value) {
23
+ this._tags[key] = value;
24
+ return this;
25
+ }
26
+ setMetadata(key, value) {
27
+ this._metadata[key] = value;
28
+ return this;
29
+ }
30
+ setStatus(status) {
31
+ this._status = status;
32
+ return this;
33
+ }
34
+ get status() {
35
+ return this._status;
36
+ }
37
+ get tags() {
38
+ return { ...this._tags };
39
+ }
40
+ get durationMs() {
41
+ return this._durationMs;
42
+ }
43
+ toDict() {
44
+ return {
45
+ name: this.name,
46
+ traceId: this.context.traceId,
47
+ spanId: this.context.spanId,
48
+ parentId: this.context.parentId,
49
+ startTime: this.startTime,
50
+ endTime: this._endTime,
51
+ durationMs: this._durationMs,
52
+ status: this._status,
53
+ tags: this._tags,
54
+ metadata: this._metadata
55
+ };
56
+ }
57
+ };
58
+ var TraceContext = class {
59
+ static getActiveSpan() {
60
+ return this.currentSpan;
61
+ }
62
+ static setActiveSpan(span) {
63
+ this.currentSpan = span;
64
+ }
65
+ };
66
+ TraceContext.currentSpan = null;
67
+
68
+ // src/telemetry.ts
69
+ var TelemetryProvider = class _TelemetryProvider {
70
+ constructor() {
71
+ this.client = null;
72
+ }
73
+ static getInstance() {
74
+ if (!_TelemetryProvider.instance) {
75
+ _TelemetryProvider.instance = new _TelemetryProvider();
76
+ }
77
+ return _TelemetryProvider.instance;
78
+ }
79
+ setClient(client) {
80
+ this.client = client;
81
+ }
82
+ /**
83
+ * Start a new span
84
+ */
85
+ startSpan(name, tags) {
86
+ const parent = TraceContext.getActiveSpan();
87
+ const traceId = parent ? parent.context.traceId : this.generateId();
88
+ const parentId = parent ? parent.context.spanId : null;
89
+ const span = new Span(name, {
90
+ traceId,
91
+ spanId: this.generateId(),
92
+ parentId
93
+ }, tags);
94
+ TraceContext.setActiveSpan(span);
95
+ return span;
96
+ }
97
+ /**
98
+ * Finish and report a span
99
+ */
100
+ finishSpan(span) {
101
+ span.finish();
102
+ if (TraceContext.getActiveSpan() === span) {
103
+ TraceContext.setActiveSpan(null);
104
+ }
105
+ if (this.client) {
106
+ this.client.captureSpan(span);
107
+ }
108
+ }
109
+ generateId() {
110
+ return Math.random().toString(16).substring(2, 18);
111
+ }
112
+ };
113
+ async function trace(name, operation, tags) {
114
+ const provider = TelemetryProvider.getInstance();
115
+ const span = provider.startSpan(name, tags);
116
+ try {
117
+ const result = await operation(span);
118
+ return result;
119
+ } catch (error) {
120
+ span.setStatus("error" /* ERROR */);
121
+ span.setTag("error", "true");
122
+ if (error instanceof Error) {
123
+ span.setTag("exception.type", error.name);
124
+ span.setTag("exception.message", error.message);
125
+ }
126
+ throw error;
127
+ } finally {
128
+ provider.finishSpan(span);
129
+ }
130
+ }
131
+
132
+ export {
133
+ TelemetryProvider,
134
+ trace
135
+ };
package/dist/index.d.mts CHANGED
@@ -2,6 +2,55 @@ export { expressErrorHandler, requestHandler } from './integrations/express.mjs'
2
2
  export { captureNextJsError, withStatly, withStatlyGetServerSideProps, withStatlyGetStaticProps, withStatlyPagesApi, withStatlyServerAction } from './integrations/nextjs.mjs';
3
3
  export { createRequestCapture, statlyFastifyPlugin, statlyPlugin } from './integrations/fastify.mjs';
4
4
 
5
+ /**
6
+ * Span module for Statly Observe SDK
7
+ * Handles distributed tracing spans
8
+ */
9
+ declare enum SpanStatus {
10
+ OK = "ok",
11
+ ERROR = "error"
12
+ }
13
+ interface SpanContext {
14
+ traceId: string;
15
+ spanId: string;
16
+ parentId?: string | null;
17
+ }
18
+ interface SpanData {
19
+ name: string;
20
+ traceId: string;
21
+ spanId: string;
22
+ parentId?: string | null;
23
+ startTime: number;
24
+ endTime?: number;
25
+ durationMs?: number;
26
+ status: SpanStatus;
27
+ tags: Record<string, string>;
28
+ metadata: Record<string, unknown>;
29
+ }
30
+ declare class Span {
31
+ readonly name: string;
32
+ readonly context: SpanContext;
33
+ readonly startTime: number;
34
+ private _endTime?;
35
+ private _durationMs?;
36
+ private _status;
37
+ private _tags;
38
+ private _metadata;
39
+ private _finished;
40
+ constructor(name: string, context: SpanContext, tags?: Record<string, string>);
41
+ /**
42
+ * Finish the span and calculate duration
43
+ */
44
+ finish(endTime?: number): void;
45
+ setTag(key: string, value: string): this;
46
+ setMetadata(key: string, value: unknown): this;
47
+ setStatus(status: SpanStatus): this;
48
+ get status(): SpanStatus;
49
+ get tags(): Record<string, string>;
50
+ get durationMs(): number | undefined;
51
+ toDict(): SpanData;
52
+ }
53
+
5
54
  /**
6
55
  * Statly Observe SDK Types
7
56
  */
@@ -79,9 +128,10 @@ interface DeviceInfo {
79
128
  interface StatlyEvent {
80
129
  message: string;
81
130
  timestamp?: number;
82
- level?: 'debug' | 'info' | 'warning' | 'error' | 'fatal';
131
+ level?: 'debug' | 'info' | 'warning' | 'error' | 'fatal' | 'span';
83
132
  stack?: string;
84
133
  exception?: Exception;
134
+ span?: SpanData;
85
135
  environment?: string;
86
136
  release?: string;
87
137
  url?: string;
@@ -130,6 +180,18 @@ declare class StatlyClient {
130
180
  * Capture a message
131
181
  */
132
182
  captureMessage(message: string, level?: EventLevel): string;
183
+ /**
184
+ * Capture a completed span
185
+ */
186
+ captureSpan(span: Span): string;
187
+ /**
188
+ * Start a new tracing span
189
+ */
190
+ startSpan(name: string, tags?: Record<string, string>): Span;
191
+ /**
192
+ * Execute a function within a trace span
193
+ */
194
+ trace<T>(name: string, operation: (span: Span) => Promise<T> | T, tags?: Record<string, string>): Promise<T>;
133
195
  /**
134
196
  * Internal method to capture an error
135
197
  */
@@ -181,47 +243,6 @@ declare class StatlyClient {
181
243
  flush(): Promise<void>;
182
244
  }
183
245
 
184
- /**
185
- * Statly Observe SDK
186
- *
187
- * Error tracking and monitoring for JavaScript applications.
188
- *
189
- * @example
190
- * ```typescript
191
- * import { Statly } from '@statly/observe-sdk';
192
- *
193
- * Statly.init({
194
- * dsn: 'https://sk_live_xxx@statly.live/your-org',
195
- * release: '1.0.0',
196
- * environment: 'production',
197
- * });
198
- *
199
- * // Errors are captured automatically
200
- *
201
- * // Manual capture
202
- * try {
203
- * riskyOperation();
204
- * } catch (error) {
205
- * Statly.captureException(error);
206
- * }
207
- *
208
- * // Capture a message
209
- * Statly.captureMessage('Something happened', 'warning');
210
- *
211
- * // Set user context
212
- * Statly.setUser({
213
- * id: 'user-123',
214
- * email: 'user@example.com',
215
- * });
216
- *
217
- * // Add breadcrumb
218
- * Statly.addBreadcrumb({
219
- * category: 'auth',
220
- * message: 'User logged in',
221
- * });
222
- * ```
223
- */
224
-
225
246
  /**
226
247
  * Initialize the Statly SDK
227
248
  *
@@ -274,6 +295,18 @@ declare function close(): Promise<void>;
274
295
  * Get the current client instance
275
296
  */
276
297
  declare function getClient(): StatlyClient | null;
298
+ /**
299
+ * Execute a function within a trace span
300
+ */
301
+ declare function trace<T>(name: string, operation: (span: Span) => Promise<T> | T, tags?: Record<string, string>): Promise<T>;
302
+ /**
303
+ * Start a new tracing span
304
+ */
305
+ declare function startSpan(name: string, tags?: Record<string, string>): Span | null;
306
+ /**
307
+ * Capture a completed span
308
+ */
309
+ declare function captureSpan(span: Span): string;
277
310
  declare const Statly: {
278
311
  readonly init: typeof init;
279
312
  readonly captureException: typeof captureException;
@@ -285,6 +318,9 @@ declare const Statly: {
285
318
  readonly flush: typeof flush;
286
319
  readonly close: typeof close;
287
320
  readonly getClient: typeof getClient;
321
+ readonly trace: typeof trace;
322
+ readonly startSpan: typeof startSpan;
323
+ readonly captureSpan: typeof captureSpan;
288
324
  };
289
325
 
290
- export { type Breadcrumb, type BrowserInfo, type DeviceInfo, type EventLevel, type Exception, type OSInfo, type StackFrame, Statly, StatlyClient, type StatlyEvent, type StatlyOptions, type User, addBreadcrumb, captureException, captureMessage, close, flush, getClient, init, setTag, setTags, setUser };
326
+ export { type Breadcrumb, type BrowserInfo, type DeviceInfo, type EventLevel, type Exception, type OSInfo, type StackFrame, Statly, StatlyClient, type StatlyEvent, type StatlyOptions, type User, addBreadcrumb, captureException, captureMessage, captureSpan, close, flush, getClient, init, setTag, setTags, setUser, startSpan, trace };