@oxog/log 1.0.0 → 1.0.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
@@ -214,13 +214,57 @@ const child = log.child({ component: 'Auth' });
214
214
  child.info('Checking token');
215
215
  ```
216
216
 
217
+ ## Transport Error Handling
218
+
219
+ ```typescript
220
+ // Listen for transport failures
221
+ log.on('error', ({ transport, error, entry }) => {
222
+ console.error(`Transport ${transport} failed:`, error.message);
223
+ // Optionally alert ops team or use fallback
224
+ });
225
+ ```
226
+
227
+ ## Sync vs Async Logging
228
+
229
+ ```typescript
230
+ const log = createLogger({
231
+ // fatal and error are sync by default (written before process exit)
232
+ sync: {
233
+ fatal: true, // default
234
+ error: true, // default
235
+ warn: false,
236
+ info: false,
237
+ debug: false,
238
+ trace: false,
239
+ }
240
+ });
241
+
242
+ // Fatal logs are written synchronously - safe before process.exit()
243
+ process.on('uncaughtException', (err) => {
244
+ log.fatal(err, 'Uncaught exception');
245
+ process.exit(1); // Log is guaranteed to be written
246
+ });
247
+ ```
248
+
249
+ ## Efficient Redaction
250
+
251
+ ```typescript
252
+ import { stringifyWithRedaction } from '@oxog/log';
253
+
254
+ // More efficient than redactFields + JSON.stringify for large objects
255
+ const json = stringifyWithRedaction(
256
+ largeObject,
257
+ ['password', 'token', 'headers.authorization']
258
+ );
259
+ ```
260
+
217
261
  ## Graceful Shutdown
218
262
 
219
263
  ```typescript
220
264
  // Flush buffered logs before shutdown
221
265
  await log.flush();
222
266
 
223
- // Cleanup and destroy
267
+ // Cleanup and destroy (also flushes buffer)
224
268
  await log.close();
225
269
  ```
226
270
 
package/dist/index.cjs CHANGED
@@ -272,7 +272,7 @@ function getCwd() {
272
272
 
273
273
  // src/utils/format.ts
274
274
  function formatJson(entry) {
275
- return JSON.stringify(entry, jsonReplacer);
275
+ return safeStringify(entry);
276
276
  }
277
277
  function jsonReplacer(_key, value) {
278
278
  if (typeof value === "bigint") {
@@ -436,27 +436,45 @@ function consoleTransport(options = {}) {
436
436
  } = options;
437
437
  const mergedColors = { ...DEFAULT_LEVEL_COLORS, ...levelColors };
438
438
  let pigment2;
439
+ function formatEntry(entry) {
440
+ if (colors && pigment2) {
441
+ return formatPretty(entry, pigment2, { timestamp, source: true });
442
+ } else if (colors) {
443
+ return formatWithAnsi(entry, mergedColors, timestamp);
444
+ } else {
445
+ return formatJson(entry);
446
+ }
447
+ }
439
448
  return {
440
449
  name: "console",
441
450
  write(entry) {
442
- let output;
443
451
  if (isBrowser()) {
444
452
  writeToBrowserConsole(entry, colors);
445
453
  return;
446
454
  }
447
- if (colors && pigment2) {
448
- output = formatPretty(entry, pigment2, { timestamp, source: true });
449
- } else if (colors) {
450
- output = formatWithAnsi(entry, mergedColors, timestamp);
451
- } else {
452
- output = formatJson(entry);
453
- }
455
+ const output = formatEntry(entry);
454
456
  if (entry.level >= 50) {
455
457
  process.stderr.write(output + "\n");
456
458
  } else {
457
459
  process.stdout.write(output + "\n");
458
460
  }
459
461
  },
462
+ writeSync(entry) {
463
+ if (isBrowser()) {
464
+ writeToBrowserConsole(entry, colors);
465
+ return;
466
+ }
467
+ const output = formatEntry(entry) + "\n";
468
+ const fd = entry.level >= 50 ? 2 : 1;
469
+ try {
470
+ if (entry.level >= 50) {
471
+ process.stderr.write(output);
472
+ } else {
473
+ process.stdout.write(output);
474
+ }
475
+ } catch {
476
+ }
477
+ },
460
478
  flush() {
461
479
  },
462
480
  close() {
@@ -779,6 +797,31 @@ function createLogger(options = {}) {
779
797
  kernel.use(plugin);
780
798
  }
781
799
  let closed = false;
800
+ function emitTransportError(transportName, error, entry) {
801
+ const err = error instanceof Error ? error : new Error(String(error));
802
+ emitter.emit("error", { transport: transportName, error: err, entry });
803
+ if (isNode()) {
804
+ process.stderr.write(`[LOG TRANSPORT ERROR] ${transportName}: ${err.message}
805
+ `);
806
+ }
807
+ }
808
+ function writeToTransportsSync(entry) {
809
+ if (closed) return;
810
+ for (const transport of activeTransports) {
811
+ try {
812
+ if (transport.writeSync) {
813
+ transport.writeSync(entry);
814
+ } else {
815
+ const result = transport.write(entry);
816
+ if (result instanceof Promise) {
817
+ result.catch((err) => emitTransportError(transport.name, err, entry));
818
+ }
819
+ }
820
+ } catch (err) {
821
+ emitTransportError(transport.name, err, entry);
822
+ }
823
+ }
824
+ }
782
825
  async function writeToTransports(entry) {
783
826
  if (closed) return;
784
827
  const promises = [];
@@ -786,14 +829,18 @@ function createLogger(options = {}) {
786
829
  try {
787
830
  const result = transport.write(entry);
788
831
  if (result instanceof Promise) {
789
- promises.push(result);
832
+ promises.push(
833
+ result.catch((err) => {
834
+ emitTransportError(transport.name, err, entry);
835
+ })
836
+ );
790
837
  }
791
838
  } catch (err) {
839
+ emitTransportError(transport.name, err, entry);
792
840
  }
793
841
  }
794
842
  if (promises.length > 0) {
795
- await Promise.all(promises).catch(() => {
796
- });
843
+ await Promise.all(promises);
797
844
  }
798
845
  }
799
846
  function createEntry(levelNum, levelName, msg, data, error) {
@@ -844,10 +891,10 @@ function createLogger(options = {}) {
844
891
  emitter.emit("log", entry);
845
892
  emitter.emit(`log:${levelName}`, entry);
846
893
  if (shouldSync(levelName)) {
847
- writeToTransports(entry).catch(() => {
848
- });
894
+ writeToTransportsSync(entry);
849
895
  } else {
850
- writeToTransports(entry).catch(() => {
896
+ writeToTransports(entry).catch((err) => {
897
+ emitTransportError("logger", err, entry);
851
898
  });
852
899
  }
853
900
  }
@@ -968,9 +1015,10 @@ function createLogger(options = {}) {
968
1015
  try {
969
1016
  const result = transport.flush();
970
1017
  if (result instanceof Promise) {
971
- promises.push(result);
1018
+ promises.push(result.catch((err) => emitTransportError(transport.name, err)));
972
1019
  }
973
- } catch {
1020
+ } catch (err) {
1021
+ emitTransportError(transport.name, err);
974
1022
  }
975
1023
  }
976
1024
  }
@@ -980,6 +1028,31 @@ function createLogger(options = {}) {
980
1028
  async close() {
981
1029
  if (closed) return;
982
1030
  closed = true;
1031
+ const ctx = kernel.getContext();
1032
+ if (ctx.flushTimerId) {
1033
+ clearInterval(ctx.flushTimerId);
1034
+ ctx.flushTimerId = void 0;
1035
+ }
1036
+ if (ctx.buffer && ctx.buffer.length > 0) {
1037
+ const bufferedEntries = ctx.buffer;
1038
+ ctx.buffer = [];
1039
+ for (const entry of bufferedEntries) {
1040
+ for (const transport of activeTransports) {
1041
+ try {
1042
+ if (transport.writeSync) {
1043
+ transport.writeSync(entry);
1044
+ } else {
1045
+ const result = transport.write(entry);
1046
+ if (result instanceof Promise) {
1047
+ await result.catch((err) => emitTransportError(transport.name, err, entry));
1048
+ }
1049
+ }
1050
+ } catch (err) {
1051
+ emitTransportError(transport.name, err, entry);
1052
+ }
1053
+ }
1054
+ }
1055
+ }
983
1056
  await logger.flush();
984
1057
  const promises = [];
985
1058
  for (const transport of activeTransports) {
@@ -987,9 +1060,10 @@ function createLogger(options = {}) {
987
1060
  try {
988
1061
  const result = transport.close();
989
1062
  if (result instanceof Promise) {
990
- promises.push(result);
1063
+ promises.push(result.catch((err) => emitTransportError(transport.name, err)));
991
1064
  }
992
- } catch {
1065
+ } catch (err) {
1066
+ emitTransportError(transport.name, err);
993
1067
  }
994
1068
  }
995
1069
  }
@@ -1145,6 +1219,36 @@ function fileTransport(options) {
1145
1219
  });
1146
1220
  });
1147
1221
  },
1222
+ writeSync(entry) {
1223
+ if (!fs) {
1224
+ try {
1225
+ const fsSync = require("fs");
1226
+ const pathSync = require("path");
1227
+ const dir = pathSync.dirname(filePath);
1228
+ if (!fsSync.existsSync(dir)) {
1229
+ fsSync.mkdirSync(dir, { recursive: true });
1230
+ }
1231
+ const line = formatJson(entry) + "\n";
1232
+ fsSync.appendFileSync(filePath, line, "utf8");
1233
+ currentSize += Buffer.byteLength(line, "utf8");
1234
+ } catch {
1235
+ process.stderr.write(formatJson(entry) + "\n");
1236
+ }
1237
+ return;
1238
+ }
1239
+ try {
1240
+ const line = formatJson(entry) + "\n";
1241
+ fs.appendFileSync(filePath, line, "utf8");
1242
+ currentSize += Buffer.byteLength(line, "utf8");
1243
+ } catch (err) {
1244
+ process.stderr.write(formatJson(entry) + "\n");
1245
+ throw new TransportError(
1246
+ `Failed to write sync to file: ${err instanceof Error ? err.message : String(err)}`,
1247
+ "file",
1248
+ err instanceof Error ? err : void 0
1249
+ );
1250
+ }
1251
+ },
1148
1252
  async flush() {
1149
1253
  if (writeStream && "flush" in writeStream) {
1150
1254
  return new Promise((resolve) => {
@@ -1794,7 +1898,9 @@ async function flushBuffer(ctx) {
1794
1898
  await Promise.all(
1795
1899
  entries.flatMap(
1796
1900
  (entry) => ctx.transports.map(
1797
- (transport) => Promise.resolve(transport.write(entry)).catch(() => {
1901
+ (transport) => Promise.resolve(transport.write(entry)).catch((err) => {
1902
+ const error = err instanceof Error ? err : new Error(String(err));
1903
+ ctx.emitter.emit("error", { transport: transport.name, error, entry });
1798
1904
  })
1799
1905
  )
1800
1906
  )