@statly/observe 1.1.0 → 1.2.1

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,7 @@ Error tracking and monitoring for JavaScript and TypeScript applications. Captur
12
12
  ## Features
13
13
 
14
14
  - Automatic error capturing with stack traces
15
+ - **Structured Logging**: Production-grade logging with automatic scrubbing
15
16
  - **Distributed Tracing**: Visualize function execution and call hierarchies
16
17
  - **Performance Metrics**: Automated capture of latency and success rates
17
18
  - Breadcrumbs for debugging context
@@ -127,6 +128,109 @@ try {
127
128
  }
128
129
  ```
129
130
 
131
+ ## Structured Logging
132
+
133
+ The Logger class provides production-grade structured logging with automatic secret scrubbing, session management, and batching.
134
+
135
+ ### Quick Start
136
+
137
+ ```typescript
138
+ import { Logger } from '@statly/observe';
139
+
140
+ const logger = Logger.create({
141
+ dsn: 'https://sk_live_xxx@statly.live/your-org',
142
+ environment: 'production',
143
+ loggerName: 'api-server',
144
+ });
145
+
146
+ // Log at different levels
147
+ logger.trace('Entering function', { args: [1, 2, 3] });
148
+ logger.debug('Processing request', { requestId: 'req_123' });
149
+ logger.info('User logged in', { userId: 'user_123' });
150
+ logger.warn('Rate limit approaching', { current: 95, limit: 100 });
151
+ logger.error('Payment failed', { orderId: 'ord_456', error: 'Card declined' });
152
+ logger.fatal('Database connection lost', { host: 'db.example.com' });
153
+ logger.audit('User role changed', { userId: 'user_123', newRole: 'admin' });
154
+
155
+ // Always close before exit
156
+ await logger.close();
157
+ ```
158
+
159
+ ### Child Loggers
160
+
161
+ Create child loggers with inherited context:
162
+
163
+ ```typescript
164
+ const requestLogger = logger.child({
165
+ context: { requestId: 'req_123' },
166
+ loggerName: 'request-handler',
167
+ });
168
+
169
+ requestLogger.info('Processing request'); // Includes requestId automatically
170
+ ```
171
+
172
+ ### User Context
173
+
174
+ Associate logs with users:
175
+
176
+ ```typescript
177
+ logger.setUser({
178
+ id: 'user_123',
179
+ email: 'jane@example.com',
180
+ name: 'Jane Doe',
181
+ });
182
+ ```
183
+
184
+ ### Secret Scrubbing
185
+
186
+ The logger automatically scrubs sensitive data (API keys, passwords, credit cards, etc.). Add custom patterns:
187
+
188
+ ```typescript
189
+ const logger = Logger.create({
190
+ dsn: '...',
191
+ scrubPatterns: [
192
+ /my-custom-secret-[a-z0-9]+/gi,
193
+ /internal-token-\d+/g,
194
+ ],
195
+ });
196
+ ```
197
+
198
+ ### Sample Rates
199
+
200
+ Control log volume with per-level sampling:
201
+
202
+ ```typescript
203
+ const logger = Logger.create({
204
+ dsn: '...',
205
+ sampleRates: {
206
+ trace: 0.01, // 1% of trace logs
207
+ debug: 0.1, // 10% of debug logs
208
+ info: 0.5, // 50% of info logs
209
+ warn: 1.0, // 100% of warnings
210
+ error: 1.0, // 100% of errors
211
+ fatal: 1.0, // 100% of fatal
212
+ },
213
+ });
214
+ ```
215
+
216
+ ### Logger Options
217
+
218
+ | Option | Type | Default | Description |
219
+ |--------|------|---------|-------------|
220
+ | `dsn` | `string` | required | Your project's Data Source Name |
221
+ | `environment` | `string` | `undefined` | Environment name |
222
+ | `release` | `string` | `undefined` | Release/version identifier |
223
+ | `loggerName` | `string` | `undefined` | Logger name for filtering |
224
+ | `sessionId` | `string` | auto-generated | Session ID for grouping logs |
225
+ | `user` | `object` | `undefined` | User context |
226
+ | `defaultContext` | `object` | `{}` | Default context for all logs |
227
+ | `minLevel` | `LogLevel` | `'trace'` | Minimum level to log |
228
+ | `sampleRates` | `object` | all 1.0 | Per-level sample rates |
229
+ | `scrubPatterns` | `RegExp[]` | `[]` | Additional patterns to scrub |
230
+ | `batchSize` | `number` | `50` | Batch size before flush |
231
+ | `flushInterval` | `number` | `5000` | Flush interval in ms |
232
+ | `maxQueueSize` | `number` | `1000` | Max queue size |
233
+
130
234
  ## Framework Integrations
131
235
 
132
236
  ### Express
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  TelemetryProvider
3
- } from "./chunk-J5AHUFP2.mjs";
3
+ } from "./chunk-NKQPBSKX.js";
4
4
 
5
5
  // src/transport.ts
6
6
  var Transport = class {
@@ -62,10 +62,10 @@ var Transport = class {
62
62
  this.queue = [];
63
63
  try {
64
64
  await this.sendBatch(events);
65
- } catch (error) {
65
+ } catch (error2) {
66
66
  this.queue = [...events, ...this.queue].slice(0, this.maxQueueSize);
67
67
  if (this.debug) {
68
- console.error("[Statly] Failed to send events:", error);
68
+ console.error("[Statly] Failed to send events:", error2);
69
69
  }
70
70
  } finally {
71
71
  this.isSending = false;
@@ -105,13 +105,13 @@ var Transport = class {
105
105
  console.log(`[Statly] Sent ${events.length} event(s)`);
106
106
  }
107
107
  return { success: true, status: response.status };
108
- } catch (error) {
108
+ } catch (error2) {
109
109
  if (this.debug) {
110
- console.error("[Statly] Network error:", error);
110
+ console.error("[Statly] Network error:", error2);
111
111
  }
112
112
  return {
113
113
  success: false,
114
- error: error instanceof Error ? error.message : "Network error"
114
+ error: error2 instanceof Error ? error2.message : "Network error"
115
115
  };
116
116
  }
117
117
  }
@@ -178,16 +178,16 @@ var GlobalHandlers = class {
178
178
  if (!this.errorCallback) {
179
179
  return;
180
180
  }
181
- let error;
181
+ let error2;
182
182
  if (event.reason instanceof Error) {
183
- error = event.reason;
183
+ error2 = event.reason;
184
184
  } else if (typeof event.reason === "string") {
185
- error = new Error(event.reason);
185
+ error2 = new Error(event.reason);
186
186
  } else {
187
- error = new Error("Unhandled Promise Rejection");
188
- error.reason = event.reason;
187
+ error2 = new Error("Unhandled Promise Rejection");
188
+ error2.reason = event.reason;
189
189
  }
190
- this.errorCallback(error, {
190
+ this.errorCallback(error2, {
191
191
  mechanism: { type: "onunhandledrejection", handled: false }
192
192
  });
193
193
  };
@@ -230,13 +230,13 @@ var GlobalHandlers = class {
230
230
  }
231
231
  installOnError() {
232
232
  this.originalOnError = window.onerror;
233
- window.onerror = (message, source, lineno, colno, error) => {
233
+ window.onerror = (message, source, lineno, colno, error2) => {
234
234
  if (this.originalOnError) {
235
- this.originalOnError.call(window, message, source, lineno, colno, error);
235
+ this.originalOnError.call(window, message, source, lineno, colno, error2);
236
236
  }
237
237
  if (this.errorCallback) {
238
- const errorObj = error || new Error(String(message));
239
- if (!error && source) {
238
+ const errorObj = error2 || new Error(String(message));
239
+ if (!error2 && source) {
240
240
  errorObj.filename = source;
241
241
  errorObj.lineno = lineno;
242
242
  errorObj.colno = colno;
@@ -300,13 +300,12 @@ var ConsoleIntegration = class {
300
300
  return;
301
301
  }
302
302
  this.originalMethods[level] = originalMethod;
303
- const self = this;
304
- console[level] = function(...args) {
305
- if (self.callback) {
306
- self.callback({
303
+ console[level] = (...args) => {
304
+ if (this.callback) {
305
+ this.callback({
307
306
  category: "console",
308
- message: self.formatArgs(args),
309
- level: self.mapLevel(level),
307
+ message: this.formatArgs(args),
308
+ level: this.mapLevel(level),
310
309
  data: args.length > 1 ? { arguments: args } : void 0
311
310
  });
312
311
  }
@@ -400,8 +399,8 @@ var StatlyClient = class {
400
399
  }
401
400
  this.initialized = true;
402
401
  if (this.options.autoCapture) {
403
- this.globalHandlers.install((error, context) => {
404
- this.captureError(error, context);
402
+ this.globalHandlers.install((error2, context) => {
403
+ this.captureError(error2, context);
405
404
  });
406
405
  }
407
406
  if (this.options.captureConsole) {
@@ -424,15 +423,15 @@ var StatlyClient = class {
424
423
  /**
425
424
  * Capture an exception/error
426
425
  */
427
- captureException(error, context) {
426
+ captureException(error2, context) {
428
427
  let errorObj;
429
- if (error instanceof Error) {
430
- errorObj = error;
431
- } else if (typeof error === "string") {
432
- errorObj = new Error(error);
428
+ if (error2 instanceof Error) {
429
+ errorObj = error2;
430
+ } else if (typeof error2 === "string") {
431
+ errorObj = new Error(error2);
433
432
  } else {
434
433
  errorObj = new Error("Unknown error");
435
- errorObj.originalError = error;
434
+ errorObj.originalError = error2;
436
435
  }
437
436
  return this.captureError(errorObj, context);
438
437
  }
@@ -467,24 +466,24 @@ var StatlyClient = class {
467
466
  * Execute a function within a trace span
468
467
  */
469
468
  async trace(name, operation, tags) {
470
- const { trace: traceFn } = await import("./telemetry-CXHOTW3Y.mjs");
469
+ const { trace: traceFn } = await import("./telemetry-JOMOMZ25.js");
471
470
  return traceFn(name, operation, tags);
472
471
  }
473
472
  /**
474
473
  * Internal method to capture an error
475
474
  */
476
- captureError(error, context) {
475
+ captureError(error2, context) {
477
476
  if (Math.random() > this.options.sampleRate) {
478
477
  return "";
479
478
  }
480
479
  const event = this.buildEvent({
481
- message: error.message,
480
+ message: error2.message,
482
481
  level: "error",
483
- stack: error.stack,
482
+ stack: error2.stack,
484
483
  exception: {
485
- type: error.name,
486
- value: error.message,
487
- stacktrace: this.parseStackTrace(error.stack)
484
+ type: error2.name,
485
+ value: error2.message,
486
+ stacktrace: this.parseStackTrace(error2.stack)
488
487
  },
489
488
  extra: context
490
489
  });
@@ -697,7 +696,7 @@ function withStatlyPagesApi(handler) {
697
696
  try {
698
697
  const result = await handler(req, res);
699
698
  return result;
700
- } catch (error) {
699
+ } catch (error2) {
701
700
  const context = {
702
701
  request: {
703
702
  method: req.method,
@@ -706,8 +705,8 @@ function withStatlyPagesApi(handler) {
706
705
  query: req.query
707
706
  }
708
707
  };
709
- Statly.captureException(error, context);
710
- throw error;
708
+ Statly.captureException(error2, context);
709
+ throw error2;
711
710
  }
712
711
  });
713
712
  };
@@ -733,7 +732,7 @@ function withStatly(handler) {
733
732
  span.setTag("http.status_code", result.status.toString());
734
733
  }
735
734
  return result;
736
- } catch (error) {
735
+ } catch (error2) {
737
736
  const headers = {};
738
737
  request.headers.forEach((value, key) => {
739
738
  headers[key] = value;
@@ -752,17 +751,17 @@ function withStatly(handler) {
752
751
  } catch {
753
752
  }
754
753
  }
755
- Statly.captureException(error, errorContext);
756
- throw error;
754
+ Statly.captureException(error2, errorContext);
755
+ throw error2;
757
756
  }
758
757
  });
759
758
  };
760
759
  return wrappedHandler;
761
760
  }
762
- function captureNextJsError(error, context) {
763
- return Statly.captureException(error, {
761
+ function captureNextJsError(error2, context) {
762
+ return Statly.captureException(error2, {
764
763
  ...context,
765
- digest: error.digest,
764
+ digest: error2.digest,
766
765
  source: "nextjs-error-boundary"
767
766
  });
768
767
  }
@@ -770,12 +769,12 @@ function withStatlyGetServerSideProps(handler) {
770
769
  return async (context) => {
771
770
  try {
772
771
  return await handler(context);
773
- } catch (error) {
774
- Statly.captureException(error, {
772
+ } catch (error2) {
773
+ Statly.captureException(error2, {
775
774
  source: "getServerSideProps",
776
775
  url: context.req?.url || context.resolvedUrl
777
776
  });
778
- throw error;
777
+ throw error2;
779
778
  }
780
779
  };
781
780
  }
@@ -783,12 +782,12 @@ function withStatlyGetStaticProps(handler) {
783
782
  return async (context) => {
784
783
  try {
785
784
  return await handler(context);
786
- } catch (error) {
787
- Statly.captureException(error, {
785
+ } catch (error2) {
786
+ Statly.captureException(error2, {
788
787
  source: "getStaticProps",
789
788
  params: context.params
790
789
  });
791
- throw error;
790
+ throw error2;
792
791
  }
793
792
  };
794
793
  }
@@ -804,12 +803,12 @@ function withStatlyServerAction(action, actionName) {
804
803
  });
805
804
  try {
806
805
  return await action(...args);
807
- } catch (error) {
808
- Statly.captureException(error, {
806
+ } catch (error2) {
807
+ Statly.captureException(error2, {
809
808
  source: "server-action",
810
809
  actionName
811
810
  });
812
- throw error;
811
+ throw error2;
813
812
  }
814
813
  });
815
814
  };
@@ -864,16 +863,16 @@ function statlyFastifyPlugin(fastify, options, done) {
864
863
  });
865
864
  hookDone();
866
865
  });
867
- fastify.setErrorHandler((error, request, reply) => {
868
- const statusCode = error.statusCode || 500;
866
+ fastify.setErrorHandler((error2, request, reply) => {
867
+ const statusCode = error2.statusCode || 500;
869
868
  if (skipStatusCodes.includes(statusCode)) {
870
- throw error;
869
+ throw error2;
871
870
  }
872
- if (!captureValidationErrors && error.validation) {
873
- throw error;
871
+ if (!captureValidationErrors && error2.validation) {
872
+ throw error2;
874
873
  }
875
- if (shouldCapture && !shouldCapture(error)) {
876
- throw error;
874
+ if (shouldCapture && !shouldCapture(error2)) {
875
+ throw error2;
877
876
  }
878
877
  const context = {
879
878
  request: {
@@ -886,27 +885,27 @@ function statlyFastifyPlugin(fastify, options, done) {
886
885
  params: request.params
887
886
  },
888
887
  error: {
889
- statusCode: error.statusCode,
890
- code: error.code
888
+ statusCode: error2.statusCode,
889
+ code: error2.code
891
890
  }
892
891
  };
893
892
  if (request.ip) {
894
893
  context.ip = request.ip;
895
894
  }
896
- if (error.validation) {
897
- context.validation = error.validation;
895
+ if (error2.validation) {
896
+ context.validation = error2.validation;
898
897
  }
899
898
  Statly.setTag("http.method", request.method);
900
899
  Statly.setTag("http.url", request.routerPath || request.url);
901
900
  Statly.setTag("http.status_code", String(statusCode));
902
- Statly.captureException(error, context);
903
- throw error;
901
+ Statly.captureException(error2, context);
902
+ throw error2;
904
903
  });
905
904
  done();
906
905
  }
907
906
  var statlyPlugin = statlyFastifyPlugin;
908
907
  function createRequestCapture(request) {
909
- return (error, additionalContext) => {
908
+ return (error2, additionalContext) => {
910
909
  const context = {
911
910
  request: {
912
911
  id: request.id,
@@ -916,7 +915,7 @@ function createRequestCapture(request) {
916
915
  },
917
916
  ...additionalContext
918
917
  };
919
- return Statly.captureException(error, context);
918
+ return Statly.captureException(error2, context);
920
919
  };
921
920
  }
922
921
  function sanitizeHeaders2(headers) {
@@ -966,12 +965,12 @@ function init(options) {
966
965
  client = new StatlyClient(finalOptions);
967
966
  client.init();
968
967
  }
969
- function captureException(error, context) {
968
+ function captureException(error2, context) {
970
969
  if (!client) {
971
970
  console.warn("[Statly] SDK not initialized. Call Statly.init() first.");
972
971
  return "";
973
972
  }
974
- return client.captureException(error, context);
973
+ return client.captureException(error2, context);
975
974
  }
976
975
  function captureMessage(message, level = "info") {
977
976
  if (!client) {
@@ -1024,7 +1023,7 @@ async function close() {
1024
1023
  function getClient() {
1025
1024
  return client;
1026
1025
  }
1027
- async function trace(name, operation, tags) {
1026
+ async function trace2(name, operation, tags) {
1028
1027
  if (!client) {
1029
1028
  return operation(null);
1030
1029
  }
@@ -1049,7 +1048,7 @@ var Statly = {
1049
1048
  flush,
1050
1049
  close,
1051
1050
  getClient,
1052
- trace,
1051
+ trace: trace2,
1053
1052
  startSpan,
1054
1053
  captureSpan
1055
1054
  };
@@ -1093,8 +1092,8 @@ function requestHandler() {
1093
1092
  }
1094
1093
  function expressErrorHandler(options = {}) {
1095
1094
  return (err, req, res, next) => {
1096
- const error = err instanceof Error ? err : new Error(String(err));
1097
- if (options.shouldHandleError && !options.shouldHandleError(error)) {
1095
+ const error2 = err instanceof Error ? err : new Error(String(err));
1096
+ if (options.shouldHandleError && !options.shouldHandleError(error2)) {
1098
1097
  return next(err);
1099
1098
  }
1100
1099
  const context = {
@@ -1118,7 +1117,7 @@ function expressErrorHandler(options = {}) {
1118
1117
  if (req.statlyContext?.transactionName) {
1119
1118
  Statly.setTag("transaction", req.statlyContext.transactionName);
1120
1119
  }
1121
- Statly.captureException(error, context);
1120
+ Statly.captureException(error2, context);
1122
1121
  next(err);
1123
1122
  };
1124
1123
  }
@@ -1175,7 +1174,7 @@ export {
1175
1174
  flush,
1176
1175
  close,
1177
1176
  getClient,
1178
- trace,
1177
+ trace2 as trace,
1179
1178
  startSpan,
1180
1179
  captureSpan,
1181
1180
  Statly