@jaypie/logger 1.2.11 → 1.2.13

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.
@@ -12,10 +12,14 @@ declare class JaypieLogger {
12
12
  trace: Logger["trace"];
13
13
  var: Logger["var"];
14
14
  warn: Logger["warn"];
15
+ private _errorCount;
15
16
  private _logger;
16
17
  private _loggers;
17
18
  private _params;
19
+ private _report;
20
+ private _sessionActive;
18
21
  private _tags;
22
+ private _warnCount;
19
23
  private _withLoggers;
20
24
  constructor({ level, tags, }?: JaypieLoggerOptions);
21
25
  init(): void;
@@ -24,7 +28,10 @@ declare class JaypieLogger {
24
28
  lib?: string;
25
29
  tags?: Record<string, string>;
26
30
  }): JaypieLogger;
31
+ report(data: Record<string, unknown>): void;
32
+ setup(tags?: Record<string, unknown>): void;
27
33
  tag(tags: Record<string, unknown>): void;
34
+ teardown(): void;
28
35
  untag(key: unknown): void;
29
36
  with(key: unknown, value?: unknown): JaypieLogger;
30
37
  }
@@ -63,6 +63,224 @@ const DATADOG_TRANSPORT = {
63
63
  MAX_BATCH_SIZE: 100,
64
64
  };
65
65
 
66
+ //
67
+ // Key-based pipelines (match on var key name)
68
+ //
69
+ function isAxiosResponse(response) {
70
+ if (typeof response !== "object" || response === null) {
71
+ return false;
72
+ }
73
+ const r = response;
74
+ return !!(r &&
75
+ r.config &&
76
+ r.data &&
77
+ r.headers &&
78
+ r.request &&
79
+ r.status &&
80
+ r.statusText);
81
+ }
82
+ function filterAxiosResponse(response) {
83
+ if (!isAxiosResponse(response)) {
84
+ return response;
85
+ }
86
+ const r = response;
87
+ const newResponse = {
88
+ data: r.data,
89
+ headers: r.headers,
90
+ status: r.status,
91
+ statusText: r.statusText,
92
+ };
93
+ if (r.isAxiosError) {
94
+ newResponse.isAxiosError = r.isAxiosError;
95
+ }
96
+ return newResponse;
97
+ }
98
+ const axiosResponseVarPipeline = {
99
+ filter: filterAxiosResponse,
100
+ key: "response",
101
+ };
102
+ const pipelines = [axiosResponseVarPipeline];
103
+ // Fetch Response
104
+ function isFetchResponse(value) {
105
+ if (typeof value !== "object" || value === null) {
106
+ return false;
107
+ }
108
+ const r = value;
109
+ return !!(typeof r.ok === "boolean" &&
110
+ typeof r.status === "number" &&
111
+ typeof r.statusText === "string" &&
112
+ "headers" in r &&
113
+ "body" in r &&
114
+ typeof r.url === "string" &&
115
+ !("config" in r) &&
116
+ !("request" in r));
117
+ }
118
+ function headersToObject(headers) {
119
+ if (headers &&
120
+ typeof headers === "object" &&
121
+ typeof headers.entries === "function") {
122
+ const result = {};
123
+ for (const [key, value] of headers.entries()) {
124
+ result[key] = value;
125
+ }
126
+ return result;
127
+ }
128
+ return headers;
129
+ }
130
+ function filterFetchResponse(value) {
131
+ const r = value;
132
+ return {
133
+ headers: headersToObject(r.headers),
134
+ ok: r.ok,
135
+ redirected: r.redirected,
136
+ status: r.status,
137
+ statusText: r.statusText,
138
+ type: r.type,
139
+ url: r.url,
140
+ };
141
+ }
142
+ // Error
143
+ function isError(value) {
144
+ if (typeof value !== "object" || value === null) {
145
+ return false;
146
+ }
147
+ if (value instanceof Error) {
148
+ return true;
149
+ }
150
+ const i = value;
151
+ if (i.isProjectError) {
152
+ return true;
153
+ }
154
+ return false;
155
+ }
156
+ function filterError(value) {
157
+ const e = value;
158
+ const newItem = {
159
+ message: e.message,
160
+ name: e.name,
161
+ };
162
+ if (e.cause) {
163
+ newItem.cause = e.cause;
164
+ }
165
+ if (e.stack) {
166
+ newItem.stack = e.stack;
167
+ }
168
+ if (e.isProjectError) {
169
+ newItem.isProjectError = e.isProjectError;
170
+ newItem.title = e.title;
171
+ newItem.detail = e.detail;
172
+ newItem.status = e.status;
173
+ }
174
+ return newItem;
175
+ }
176
+ // Type filter registry (order matters — first match wins)
177
+ const typeFilters = [
178
+ { detect: isFetchResponse, filter: filterFetchResponse },
179
+ { detect: isError, filter: filterError },
180
+ ];
181
+ //
182
+ // Opaque object detection and generic extraction
183
+ //
184
+ function isOpaqueObject(value) {
185
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
186
+ return false;
187
+ }
188
+ // Plain objects are fine
189
+ if (value.constructor === Object || value.constructor === undefined) {
190
+ return false;
191
+ }
192
+ // If JSON.stringify produces "{}" the object has no own enumerable properties
193
+ // and will log as useless "[object Type]"
194
+ try {
195
+ const json = JSON.stringify(value);
196
+ return json === "{}" || json === "[]";
197
+ }
198
+ catch {
199
+ return true;
200
+ }
201
+ }
202
+ function extractOpaqueObject(value) {
203
+ const obj = value;
204
+ const result = {};
205
+ const ctorName = obj.constructor?.name;
206
+ if (ctorName && ctorName !== "Object") {
207
+ result._type = ctorName;
208
+ }
209
+ // If the object itself is map-like (Headers, URLSearchParams, FormData, etc.),
210
+ // convert its entries directly
211
+ const mapLike = obj;
212
+ if (typeof mapLike.entries === "function" &&
213
+ typeof mapLike.forEach === "function") {
214
+ try {
215
+ const entries = Object.fromEntries(mapLike.entries());
216
+ return { ...result, ...entries };
217
+ }
218
+ catch {
219
+ // Fall through to generic extraction
220
+ }
221
+ }
222
+ // Collect readable non-function properties from the prototype chain
223
+ let proto = obj;
224
+ while (proto && proto !== Object.prototype) {
225
+ for (const key of Object.getOwnPropertyNames(proto)) {
226
+ if (key === "constructor" || key in result) {
227
+ continue;
228
+ }
229
+ try {
230
+ const desc = Object.getOwnPropertyDescriptor(proto, key);
231
+ if (!desc)
232
+ continue;
233
+ // Read getters and value properties from the original object
234
+ const val = obj[key];
235
+ if (typeof val === "function" || typeof val === "symbol") {
236
+ continue;
237
+ }
238
+ // Skip streams and other non-serializable objects
239
+ if (val &&
240
+ typeof val === "object" &&
241
+ typeof val.pipe === "function") {
242
+ continue;
243
+ }
244
+ // Convert iterable map-like objects (Headers, URLSearchParams, etc.)
245
+ if (val &&
246
+ typeof val === "object" &&
247
+ typeof val.entries === "function" &&
248
+ typeof val.forEach === "function") {
249
+ result[key] = Object.fromEntries(val.entries());
250
+ continue;
251
+ }
252
+ result[key] = val;
253
+ }
254
+ catch {
255
+ // Property threw on access — skip
256
+ }
257
+ }
258
+ proto = Object.getPrototypeOf(proto);
259
+ }
260
+ return result;
261
+ }
262
+ //
263
+ // Public API
264
+ //
265
+ /**
266
+ * Filter a value by type, regardless of var key name.
267
+ * Tries known type filters first, then falls back to generic
268
+ * opaque object extraction for anything that would log as [object Type].
269
+ */
270
+ function filterByType(value) {
271
+ // Try known type filters
272
+ for (const tf of typeFilters) {
273
+ if (tf.detect(value)) {
274
+ return tf.filter(value);
275
+ }
276
+ }
277
+ // Generic fallback for opaque objects
278
+ if (isOpaqueObject(value)) {
279
+ return extractOpaqueObject(value);
280
+ }
281
+ return value;
282
+ }
283
+
66
284
  //
67
285
  //
68
286
  // Helper
@@ -501,7 +719,13 @@ class Logger {
501
719
  }
502
720
  if (format === FORMAT.JSON) {
503
721
  const messageKey = keys[0];
504
- const messageVal = msgObj[messageKey];
722
+ let messageVal = msgObj[messageKey];
723
+ for (const pipeline of pipelines) {
724
+ if (messageKey === pipeline.key) {
725
+ messageVal = pipeline.filter(messageVal);
726
+ }
727
+ }
728
+ messageVal = filterByType(messageVal);
505
729
  const json = {
506
730
  data: parse(messageVal),
507
731
  dataType: typeof messageVal,
@@ -623,81 +847,6 @@ function forceVar(key, value) {
623
847
  }
624
848
  }
625
849
 
626
- function isAxiosResponse(response) {
627
- if (typeof response !== "object" || response === null) {
628
- return false;
629
- }
630
- const r = response;
631
- return !!(r &&
632
- r.config &&
633
- r.data &&
634
- r.headers &&
635
- r.request &&
636
- r.status &&
637
- r.statusText);
638
- }
639
- function filterAxiosResponse(response) {
640
- if (!isAxiosResponse(response)) {
641
- return response;
642
- }
643
- const r = response;
644
- const newResponse = {
645
- data: r.data,
646
- headers: r.headers,
647
- status: r.status,
648
- statusText: r.statusText,
649
- };
650
- if (r.isAxiosError) {
651
- newResponse.isAxiosError = r.isAxiosError;
652
- }
653
- return newResponse;
654
- }
655
- const axiosResponseVarPipeline = {
656
- filter: filterAxiosResponse,
657
- key: "response",
658
- };
659
- function isError(item) {
660
- if (typeof item !== "object" || item === null) {
661
- return false;
662
- }
663
- if (item instanceof Error) {
664
- return true;
665
- }
666
- const i = item;
667
- if (i.isProjectError) {
668
- return true;
669
- }
670
- return false;
671
- }
672
- function filterErrorVar(item) {
673
- if (!isError(item)) {
674
- return item;
675
- }
676
- const e = item;
677
- const newItem = {
678
- message: e.message,
679
- name: e.name,
680
- };
681
- if (e.cause) {
682
- newItem.cause = e.cause;
683
- }
684
- if (e.stack) {
685
- newItem.stack = e.stack;
686
- }
687
- if (e.isProjectError) {
688
- newItem.isProjectError = e.isProjectError;
689
- newItem.title = e.title;
690
- newItem.detail = e.detail;
691
- newItem.status = e.status;
692
- }
693
- return newItem;
694
- }
695
- const errorVarPipeline = {
696
- filter: filterErrorVar,
697
- key: "error",
698
- };
699
- const pipelines = [axiosResponseVarPipeline, errorVarPipeline];
700
-
701
850
  function keyValueToArray(keyValue) {
702
851
  const key = Object.keys(keyValue)[0];
703
852
  return [key, keyValue[key]];
@@ -709,6 +858,7 @@ function logVar(key, value) {
709
858
  v = pipeline.filter(v);
710
859
  }
711
860
  }
861
+ v = filterByType(v);
712
862
  return { [k]: v };
713
863
  }
714
864
 
@@ -726,6 +876,10 @@ function envBoolean(key, { defaultValue }) {
726
876
  }
727
877
  class JaypieLogger {
728
878
  constructor({ level = process.env.LOG_LEVEL, tags = {}, } = {}) {
879
+ this._errorCount = 0;
880
+ this._report = {};
881
+ this._sessionActive = false;
882
+ this._warnCount = 0;
729
883
  this._params = { level, tags };
730
884
  this._loggers = [];
731
885
  this._tags = {};
@@ -740,16 +894,32 @@ class JaypieLogger {
740
894
  this._loggers = [this._logger];
741
895
  this.debug = ((...args) => this._logger.debug(...args));
742
896
  this.debug.var = (messageObject, messageValue) => this._logger.debug.var(messageObject, messageValue);
743
- this.error = ((...args) => this._logger.error(...args));
744
- this.error.var = (messageObject, messageValue) => this._logger.error.var(messageObject, messageValue);
897
+ this.error = ((...args) => {
898
+ if (this._sessionActive)
899
+ this._errorCount++;
900
+ this._logger.error(...args);
901
+ });
902
+ this.error.var = (messageObject, messageValue) => {
903
+ if (this._sessionActive)
904
+ this._errorCount++;
905
+ this._logger.error.var(messageObject, messageValue);
906
+ };
745
907
  this.fatal = ((...args) => this._logger.fatal(...args));
746
908
  this.fatal.var = (messageObject, messageValue) => this._logger.fatal.var(messageObject, messageValue);
747
909
  this.info = ((...args) => this._logger.info(...args));
748
910
  this.info.var = (messageObject, messageValue) => this._logger.info.var(messageObject, messageValue);
749
911
  this.trace = ((...args) => this._logger.trace(...args));
750
912
  this.trace.var = (messageObject, messageValue) => this._logger.trace.var(messageObject, messageValue);
751
- this.warn = ((...args) => this._logger.warn(...args));
752
- this.warn.var = (messageObject, messageValue) => this._logger.warn.var(messageObject, messageValue);
913
+ this.warn = ((...args) => {
914
+ if (this._sessionActive)
915
+ this._warnCount++;
916
+ this._logger.warn(...args);
917
+ });
918
+ this.warn.var = (messageObject, messageValue) => {
919
+ if (this._sessionActive)
920
+ this._warnCount++;
921
+ this._logger.warn.var(messageObject, messageValue);
922
+ };
753
923
  this.var = (messageObject, messageValue) => this._logger.var(logVar(messageObject, messageValue));
754
924
  }
755
925
  init() {
@@ -770,6 +940,11 @@ class JaypieLogger {
770
940
  });
771
941
  this._loggers = [this._logger];
772
942
  this._withLoggers = {};
943
+ // Reset session state
944
+ this._errorCount = 0;
945
+ this._report = {};
946
+ this._sessionActive = false;
947
+ this._warnCount = 0;
773
948
  const levels = [
774
949
  "debug",
775
950
  "error",
@@ -779,12 +954,38 @@ class JaypieLogger {
779
954
  "warn",
780
955
  ];
781
956
  levels.forEach((lvl) => {
782
- this[lvl] = ((...args) => {
783
- this._logger[lvl](...args);
784
- });
785
- this[lvl].var = (messageObject, messageValue) => {
786
- this._logger[lvl].var(messageObject, messageValue);
787
- };
957
+ if (lvl === "error") {
958
+ this.error = ((...args) => {
959
+ if (this._sessionActive)
960
+ this._errorCount++;
961
+ this._logger.error(...args);
962
+ });
963
+ this.error.var = (messageObject, messageValue) => {
964
+ if (this._sessionActive)
965
+ this._errorCount++;
966
+ this._logger.error.var(messageObject, messageValue);
967
+ };
968
+ }
969
+ else if (lvl === "warn") {
970
+ this.warn = ((...args) => {
971
+ if (this._sessionActive)
972
+ this._warnCount++;
973
+ this._logger.warn(...args);
974
+ });
975
+ this.warn.var = (messageObject, messageValue) => {
976
+ if (this._sessionActive)
977
+ this._warnCount++;
978
+ this._logger.warn.var(messageObject, messageValue);
979
+ };
980
+ }
981
+ else {
982
+ this[lvl] = ((...args) => {
983
+ this._logger[lvl](...args);
984
+ });
985
+ this[lvl].var = (messageObject, messageValue) => {
986
+ this._logger[lvl].var(messageObject, messageValue);
987
+ };
988
+ }
788
989
  });
789
990
  }
790
991
  lib({ level, lib, tags = {}, } = {}) {
@@ -810,12 +1011,55 @@ class JaypieLogger {
810
1011
  this._loggers.push(logger._logger);
811
1012
  return logger;
812
1013
  }
1014
+ report(data) {
1015
+ if (!this._sessionActive) {
1016
+ this.warn("[logger] report() called without active session");
1017
+ return;
1018
+ }
1019
+ for (const key of Object.keys(data)) {
1020
+ if (key in this._report) {
1021
+ this.warn(`[logger] Overwriting report key: ${key}`);
1022
+ }
1023
+ }
1024
+ Object.assign(this._report, data);
1025
+ }
1026
+ setup(tags) {
1027
+ if (this._sessionActive) {
1028
+ this.warn("[logger] setup() called while session already active");
1029
+ }
1030
+ this._errorCount = 0;
1031
+ this._report = {};
1032
+ this._sessionActive = true;
1033
+ this._warnCount = 0;
1034
+ if (tags) {
1035
+ this.tag(tags);
1036
+ }
1037
+ }
813
1038
  tag(tags) {
814
1039
  for (const logger of this._loggers) {
815
1040
  logger.tag(tags);
816
1041
  }
817
1042
  Object.assign(this._tags, tags);
818
1043
  }
1044
+ teardown() {
1045
+ if (!this._sessionActive) {
1046
+ return;
1047
+ }
1048
+ const finalReport = {
1049
+ ...this._report,
1050
+ log: {
1051
+ error: this._errorCount > 0,
1052
+ errors: this._errorCount,
1053
+ warn: this._warnCount > 0,
1054
+ warns: this._warnCount,
1055
+ },
1056
+ };
1057
+ this.info.var({ report: finalReport });
1058
+ this._errorCount = 0;
1059
+ this._report = {};
1060
+ this._sessionActive = false;
1061
+ this._warnCount = 0;
1062
+ }
819
1063
  untag(key) {
820
1064
  for (const logger of this._loggers) {
821
1065
  logger.untag(key);