mynth-logger 2.1.6 → 2.1.7

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.
@@ -11,17 +11,17 @@ const ErrorType = type({
11
11
  message: "string",
12
12
  "stack?": "string",
13
13
  });
14
- const formatItem = (item) => {
14
+ const itemToString = (item) => {
15
15
  if (typeof item === "undefined")
16
16
  return "undefined";
17
17
  // Remove colors from strings
18
18
  if (typeof item === "string")
19
19
  // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping ANSI escape codes
20
- return config.redact(item.replace(/\x1b\[[0-9;]*m/g, ""));
20
+ return item.replace(/\x1b\[[0-9;]*m/g, "");
21
21
  // Check if this is an Error
22
22
  const error = ErrorType(item);
23
23
  if (!(error instanceof type.errors))
24
- return config.redact(error.stack || error.message);
24
+ return error.stack || error.message;
25
25
  const stringified = (() => {
26
26
  try {
27
27
  return stringify(item);
@@ -30,9 +30,7 @@ const formatItem = (item) => {
30
30
  return String(item);
31
31
  }
32
32
  })();
33
- return config.redact(stringified.replace(/^'|'$/g, ""));
33
+ return stringified.replace(/^'|'$/g, "");
34
34
  };
35
- const format = (items) => Array.from(items)
36
- .map((item) => formatItem(item))
37
- .join(" ");
35
+ const format = (items) => config.redact(Array.from(items).map(itemToString).join(" "));
38
36
  export { format, updateConfig };
@@ -1,3 +1,3 @@
1
- import type { RedactConfig } from "./redact.js";
1
+ import { type RedactConfig } from "./redact.js";
2
2
  declare const setupLogging: (config?: RedactConfig) => import("consola").ConsolaInstance;
3
3
  export { setupLogging };
@@ -1,9 +1,10 @@
1
1
  import { createConsola } from "consola";
2
2
  import { updateConfig } from "./format.js";
3
+ import { mergeRedactConfigs, parseEnvRedactConfig, } from "./redact.js";
3
4
  import DatadogReporter from "./reporters/datadog.js";
4
5
  import DiscordReporter from "./reporters/discord.js";
5
6
  const setupLogging = (config = {}) => {
6
- updateConfig(config);
7
+ updateConfig(mergeRedactConfigs(config, parseEnvRedactConfig()));
7
8
  const consola = createConsola({ fancy: true, level: 5 });
8
9
  // Set Discord reporter as first so it can remove
9
10
  // Discord-related config before other reporters process the
@@ -35,5 +35,7 @@ type RedactConfig = {
35
35
  mnemonic?: DetectorConfig;
36
36
  };
37
37
  declare const createRedact: (config: RedactConfig) => (text: string) => string;
38
- export { createRedact };
38
+ declare const mergeRedactConfigs: (a: RedactConfig, b: RedactConfig) => RedactConfig;
39
+ declare const parseEnvRedactConfig: () => RedactConfig;
40
+ export { createRedact, mergeRedactConfigs, parseEnvRedactConfig };
39
41
  export type { RedactConfig };
@@ -1,6 +1,7 @@
1
1
  import { DeepRedact } from "@hackylabs/deep-redact/index.ts";
2
2
  import { validateMnemonic } from "@scure/bip39";
3
3
  import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+ import { type } from "arktype";
4
5
  const replacement = "[REDACTED]";
5
6
  const sliceAround = (value, offset, matchLen, rule) => {
6
7
  const before = rule.before ?? 10;
@@ -130,4 +131,50 @@ const createRedact = (config) => {
130
131
  }
131
132
  };
132
133
  };
133
- export { createRedact };
134
+ const SerializableContextRuleType = type({
135
+ re: "string",
136
+ "flags?": "string",
137
+ "before?": "number",
138
+ "after?": "number",
139
+ }).pipe((rule) => ({
140
+ re: new RegExp(rule.re, rule.flags ?? ""),
141
+ before: rule.before,
142
+ after: rule.after,
143
+ }));
144
+ const SerializableDetectorConfigType = type({
145
+ "allow?": SerializableContextRuleType.array(),
146
+ });
147
+ const SerializableRedactConfigType = type({
148
+ "hex?": SerializableDetectorConfigType,
149
+ "base64?": SerializableDetectorConfigType,
150
+ "base64url?": SerializableDetectorConfigType,
151
+ "base58?": SerializableDetectorConfigType,
152
+ "mnemonic?": SerializableDetectorConfigType,
153
+ });
154
+ const mergeDetectorConfigs = (a, b) => {
155
+ if (!a && !b)
156
+ return undefined;
157
+ const allowA = a?.allow ?? [];
158
+ const allowB = b?.allow ?? [];
159
+ const merged = [...allowA, ...allowB];
160
+ return { allow: merged.length > 0 ? merged : undefined };
161
+ };
162
+ const mergeRedactConfigs = (a, b) => ({
163
+ hex: mergeDetectorConfigs(a.hex, b.hex),
164
+ base64: mergeDetectorConfigs(a.base64, b.base64),
165
+ base64url: mergeDetectorConfigs(a.base64url, b.base64url),
166
+ base58: mergeDetectorConfigs(a.base58, b.base58),
167
+ mnemonic: mergeDetectorConfigs(a.mnemonic, b.mnemonic),
168
+ });
169
+ const parseEnvRedactConfig = () => {
170
+ const raw = process.env.REDACT_CONFIG;
171
+ if (!raw)
172
+ return {};
173
+ const json = Buffer.from(raw, "base64").toString("utf8");
174
+ const parsed = JSON.parse(json);
175
+ const validated = SerializableRedactConfigType(parsed);
176
+ if (validated instanceof type.errors)
177
+ throw new Error(`REDACT_CONFIG validation failed: ${validated.summary}`);
178
+ return validated;
179
+ };
180
+ export { createRedact, mergeRedactConfigs, parseEnvRedactConfig };
@@ -2,7 +2,7 @@ import { beforeAll, describe, expect, it } from "vitest";
2
2
  import { format } from "../src/format.js";
3
3
  import { setupLogging } from "../src/index.js";
4
4
  beforeAll(() => {
5
- setupLogging();
5
+ setupLogging({ hex: { allow: [{ re: /\b(transfer)\b/i }] } });
6
6
  });
7
7
  describe("logging", () => {
8
8
  it("logs display to terminal", () => {
@@ -26,4 +26,11 @@ describe("logging", () => {
26
26
  expect(result).not.toContain(secret);
27
27
  expect(result).toContain("[REDACTED]");
28
28
  });
29
+ it("allows hex when context word is in a separate argument", () => {
30
+ const result = format([
31
+ "transfer",
32
+ "538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
33
+ ]);
34
+ expect(result).toBe("transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761");
35
+ });
29
36
  });
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createRedact } from "../src/redact.js";
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { createRedact, mergeRedactConfigs, parseEnvRedactConfig, } from "../src/redact.js";
3
3
  describe("redact", () => {
4
4
  it("redacts base64url", () => {
5
5
  const redact = createRedact({});
@@ -31,3 +31,75 @@ describe("redact", () => {
31
31
  expect(result).toBe(`This is an intent b3c1c51b70cd602cc9a5f76d3795b6eca27a89f884ba8977b604451333393530 but this is a private key: [REDACTED]`);
32
32
  });
33
33
  });
34
+ describe("mergeRedactConfigs", () => {
35
+ it("merges allow rules from both configs", () => {
36
+ const a = { hex: { allow: [{ re: /\b(event)\b/i }] } };
37
+ const b = { hex: { allow: [{ re: /\b(transfer)\b/i }] } };
38
+ const merged = mergeRedactConfigs(a, b);
39
+ const redact = createRedact(merged);
40
+ const eventResult = redact("Pushed event fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e.1771865100000");
41
+ expect(eventResult).toContain("fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e");
42
+ const transferResult = redact("transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761");
43
+ expect(transferResult).toContain("538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761");
44
+ });
45
+ it("handles empty configs", () => {
46
+ const merged = mergeRedactConfigs({}, {});
47
+ expect(merged).toEqual({
48
+ hex: undefined,
49
+ base64: undefined,
50
+ base64url: undefined,
51
+ base58: undefined,
52
+ mnemonic: undefined,
53
+ });
54
+ });
55
+ it("uses only a config when b is empty", () => {
56
+ const a = { hex: { allow: [{ re: /\b(event)\b/i }] } };
57
+ const merged = mergeRedactConfigs(a, {});
58
+ const redact = createRedact(merged);
59
+ const result = redact("Pushed event fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e.1771865100000");
60
+ expect(result).toContain("fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e");
61
+ });
62
+ });
63
+ describe("parseEnvRedactConfig", () => {
64
+ afterEach(() => {
65
+ delete process.env.REDACT_CONFIG;
66
+ });
67
+ it("returns empty config when REDACT_CONFIG is not set", () => {
68
+ delete process.env.REDACT_CONFIG;
69
+ const config = parseEnvRedactConfig();
70
+ expect(config).toEqual({});
71
+ });
72
+ it("parses base64-encoded JSON config", () => {
73
+ const serializable = {
74
+ hex: { allow: [{ re: "\\b(event)\\b", flags: "i" }] },
75
+ };
76
+ process.env.REDACT_CONFIG = Buffer.from(JSON.stringify(serializable)).toString("base64");
77
+ const config = parseEnvRedactConfig();
78
+ expect(config.hex?.allow).toHaveLength(1);
79
+ expect(config.hex?.allow?.[0].re).toBeInstanceOf(RegExp);
80
+ expect(config.hex?.allow?.[0].re.source).toBe("\\b(event)\\b");
81
+ expect(config.hex?.allow?.[0].re.flags).toContain("i");
82
+ });
83
+ it("throws on invalid base64", () => {
84
+ process.env.REDACT_CONFIG = "!!!not-valid-base64!!!";
85
+ expect(() => parseEnvRedactConfig()).toThrow();
86
+ });
87
+ it("throws on invalid JSON", () => {
88
+ process.env.REDACT_CONFIG = Buffer.from("{not json}").toString("base64");
89
+ expect(() => parseEnvRedactConfig()).toThrow();
90
+ });
91
+ it("throws when JSON structure fails arktype validation", () => {
92
+ process.env.REDACT_CONFIG = Buffer.from(JSON.stringify({ hex: { allow: [{ re: 123 }] } })).toString("base64");
93
+ expect(() => parseEnvRedactConfig()).toThrow();
94
+ });
95
+ it("applies env config allow rules when redacting", () => {
96
+ const serializable = {
97
+ hex: { allow: [{ re: "\\b(transfer)\\b", flags: "i" }] },
98
+ };
99
+ process.env.REDACT_CONFIG = Buffer.from(JSON.stringify(serializable)).toString("base64");
100
+ const envConfig = parseEnvRedactConfig();
101
+ const redact = createRedact(envConfig);
102
+ const result = redact("transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761");
103
+ expect(result).toBe("transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761");
104
+ });
105
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mynth-logger",
3
- "version": "2.1.6",
3
+ "version": "2.1.7",
4
4
  "description": "Package to format logs for mynth microservices.",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
package/src/format.ts CHANGED
@@ -15,18 +15,17 @@ const ErrorType = type({
15
15
  "stack?": "string",
16
16
  });
17
17
 
18
- const formatItem = (item: unknown): string => {
18
+ const itemToString = (item: unknown): string => {
19
19
  if (typeof item === "undefined") return "undefined";
20
20
 
21
21
  // Remove colors from strings
22
22
  if (typeof item === "string")
23
23
  // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping ANSI escape codes
24
- return config.redact(item.replace(/\x1b\[[0-9;]*m/g, ""));
24
+ return item.replace(/\x1b\[[0-9;]*m/g, "");
25
25
 
26
26
  // Check if this is an Error
27
27
  const error = ErrorType(item);
28
- if (!(error instanceof type.errors))
29
- return config.redact(error.stack || error.message);
28
+ if (!(error instanceof type.errors)) return error.stack || error.message;
30
29
 
31
30
  const stringified = (() => {
32
31
  try {
@@ -36,12 +35,10 @@ const formatItem = (item: unknown): string => {
36
35
  }
37
36
  })();
38
37
 
39
- return config.redact(stringified.replace(/^'|'$/g, ""));
38
+ return stringified.replace(/^'|'$/g, "");
40
39
  };
41
40
 
42
41
  const format = (items: unknown[]): string =>
43
- Array.from(items)
44
- .map((item) => formatItem(item))
45
- .join(" ");
42
+ config.redact(Array.from(items).map(itemToString).join(" "));
46
43
 
47
44
  export { format, updateConfig };
package/src/logging.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import { createConsola } from "consola";
2
2
  import { updateConfig } from "./format.js";
3
- import type { RedactConfig } from "./redact.js";
3
+ import {
4
+ mergeRedactConfigs,
5
+ parseEnvRedactConfig,
6
+ type RedactConfig,
7
+ } from "./redact.js";
4
8
  import DatadogReporter from "./reporters/datadog.js";
5
9
  import DiscordReporter from "./reporters/discord.js";
6
10
 
7
11
  const setupLogging = (config: RedactConfig = {}) => {
8
- updateConfig(config);
12
+ updateConfig(mergeRedactConfigs(config, parseEnvRedactConfig()));
9
13
  const consola = createConsola({ fancy: true, level: 5 });
10
14
 
11
15
  // Set Discord reporter as first so it can remove
package/src/redact.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DeepRedact } from "@hackylabs/deep-redact/index.ts";
2
2
  import { validateMnemonic } from "@scure/bip39";
3
3
  import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+ import { type } from "arktype";
4
5
 
5
6
  /**
6
7
  * Configurable redaction for strings that *look like secrets*:
@@ -239,5 +240,64 @@ const createRedact = (config: RedactConfig) => {
239
240
  };
240
241
  };
241
242
 
242
- export { createRedact };
243
+ const SerializableContextRuleType = type({
244
+ re: "string",
245
+ "flags?": "string",
246
+ "before?": "number",
247
+ "after?": "number",
248
+ }).pipe(
249
+ (rule): ContextRule => ({
250
+ re: new RegExp(rule.re, rule.flags ?? ""),
251
+ before: rule.before,
252
+ after: rule.after,
253
+ }),
254
+ );
255
+
256
+ const SerializableDetectorConfigType = type({
257
+ "allow?": SerializableContextRuleType.array(),
258
+ });
259
+
260
+ const SerializableRedactConfigType = type({
261
+ "hex?": SerializableDetectorConfigType,
262
+ "base64?": SerializableDetectorConfigType,
263
+ "base64url?": SerializableDetectorConfigType,
264
+ "base58?": SerializableDetectorConfigType,
265
+ "mnemonic?": SerializableDetectorConfigType,
266
+ });
267
+
268
+ const mergeDetectorConfigs = (
269
+ a?: DetectorConfig,
270
+ b?: DetectorConfig,
271
+ ): DetectorConfig | undefined => {
272
+ if (!a && !b) return undefined;
273
+ const allowA = a?.allow ?? [];
274
+ const allowB = b?.allow ?? [];
275
+ const merged = [...allowA, ...allowB];
276
+ return { allow: merged.length > 0 ? merged : undefined };
277
+ };
278
+
279
+ const mergeRedactConfigs = (
280
+ a: RedactConfig,
281
+ b: RedactConfig,
282
+ ): RedactConfig => ({
283
+ hex: mergeDetectorConfigs(a.hex, b.hex),
284
+ base64: mergeDetectorConfigs(a.base64, b.base64),
285
+ base64url: mergeDetectorConfigs(a.base64url, b.base64url),
286
+ base58: mergeDetectorConfigs(a.base58, b.base58),
287
+ mnemonic: mergeDetectorConfigs(a.mnemonic, b.mnemonic),
288
+ });
289
+
290
+ const parseEnvRedactConfig = (): RedactConfig => {
291
+ const raw = process.env.REDACT_CONFIG;
292
+ if (!raw) return {};
293
+
294
+ const json = Buffer.from(raw, "base64").toString("utf8");
295
+ const parsed: unknown = JSON.parse(json);
296
+ const validated = SerializableRedactConfigType(parsed);
297
+ if (validated instanceof type.errors)
298
+ throw new Error(`REDACT_CONFIG validation failed: ${validated.summary}`);
299
+ return validated;
300
+ };
301
+
302
+ export { createRedact, mergeRedactConfigs, parseEnvRedactConfig };
243
303
  export type { RedactConfig };
@@ -3,7 +3,7 @@ import { format } from "../src/format.js";
3
3
  import { setupLogging } from "../src/index.js";
4
4
 
5
5
  beforeAll(() => {
6
- setupLogging();
6
+ setupLogging({ hex: { allow: [{ re: /\b(transfer)\b/i }] } });
7
7
  });
8
8
 
9
9
  describe("logging", () => {
@@ -34,4 +34,14 @@ describe("logging", () => {
34
34
  expect(result).not.toContain(secret);
35
35
  expect(result).toContain("[REDACTED]");
36
36
  });
37
+
38
+ it("allows hex when context word is in a separate argument", () => {
39
+ const result = format([
40
+ "transfer",
41
+ "538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
42
+ ]);
43
+ expect(result).toBe(
44
+ "transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
45
+ );
46
+ });
37
47
  });
@@ -1,5 +1,9 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createRedact } from "../src/redact.js";
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import {
3
+ createRedact,
4
+ mergeRedactConfigs,
5
+ parseEnvRedactConfig,
6
+ } from "../src/redact.js";
3
7
 
4
8
  describe("redact", () => {
5
9
  it("redacts base64url", () => {
@@ -54,3 +58,111 @@ describe("redact", () => {
54
58
  );
55
59
  });
56
60
  });
61
+
62
+ describe("mergeRedactConfigs", () => {
63
+ it("merges allow rules from both configs", () => {
64
+ const a = { hex: { allow: [{ re: /\b(event)\b/i }] } };
65
+ const b = { hex: { allow: [{ re: /\b(transfer)\b/i }] } };
66
+ const merged = mergeRedactConfigs(a, b);
67
+ const redact = createRedact(merged);
68
+
69
+ const eventResult = redact(
70
+ "Pushed event fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e.1771865100000",
71
+ );
72
+ expect(eventResult).toContain(
73
+ "fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e",
74
+ );
75
+
76
+ const transferResult = redact(
77
+ "transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
78
+ );
79
+ expect(transferResult).toContain(
80
+ "538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
81
+ );
82
+ });
83
+
84
+ it("handles empty configs", () => {
85
+ const merged = mergeRedactConfigs({}, {});
86
+ expect(merged).toEqual({
87
+ hex: undefined,
88
+ base64: undefined,
89
+ base64url: undefined,
90
+ base58: undefined,
91
+ mnemonic: undefined,
92
+ });
93
+ });
94
+
95
+ it("uses only a config when b is empty", () => {
96
+ const a = { hex: { allow: [{ re: /\b(event)\b/i }] } };
97
+ const merged = mergeRedactConfigs(a, {});
98
+ const redact = createRedact(merged);
99
+ const result = redact(
100
+ "Pushed event fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e.1771865100000",
101
+ );
102
+ expect(result).toContain(
103
+ "fcc6533b59301096a973b8be3e6518f0cd13f73a9821de558cca77ac9b014d6e",
104
+ );
105
+ });
106
+ });
107
+
108
+ describe("parseEnvRedactConfig", () => {
109
+ afterEach(() => {
110
+ delete process.env.REDACT_CONFIG;
111
+ });
112
+
113
+ it("returns empty config when REDACT_CONFIG is not set", () => {
114
+ delete process.env.REDACT_CONFIG;
115
+ const config = parseEnvRedactConfig();
116
+ expect(config).toEqual({});
117
+ });
118
+
119
+ it("parses base64-encoded JSON config", () => {
120
+ const serializable = {
121
+ hex: { allow: [{ re: "\\b(event)\\b", flags: "i" }] },
122
+ };
123
+ process.env.REDACT_CONFIG = Buffer.from(
124
+ JSON.stringify(serializable),
125
+ ).toString("base64");
126
+ const config = parseEnvRedactConfig();
127
+
128
+ expect(config.hex?.allow).toHaveLength(1);
129
+ expect(config.hex?.allow?.[0].re).toBeInstanceOf(RegExp);
130
+ expect(config.hex?.allow?.[0].re.source).toBe("\\b(event)\\b");
131
+ expect(config.hex?.allow?.[0].re.flags).toContain("i");
132
+ });
133
+
134
+ it("throws on invalid base64", () => {
135
+ process.env.REDACT_CONFIG = "!!!not-valid-base64!!!";
136
+ expect(() => parseEnvRedactConfig()).toThrow();
137
+ });
138
+
139
+ it("throws on invalid JSON", () => {
140
+ process.env.REDACT_CONFIG = Buffer.from("{not json}").toString("base64");
141
+ expect(() => parseEnvRedactConfig()).toThrow();
142
+ });
143
+
144
+ it("throws when JSON structure fails arktype validation", () => {
145
+ process.env.REDACT_CONFIG = Buffer.from(
146
+ JSON.stringify({ hex: { allow: [{ re: 123 }] } }),
147
+ ).toString("base64");
148
+ expect(() => parseEnvRedactConfig()).toThrow();
149
+ });
150
+
151
+ it("applies env config allow rules when redacting", () => {
152
+ const serializable = {
153
+ hex: { allow: [{ re: "\\b(transfer)\\b", flags: "i" }] },
154
+ };
155
+ process.env.REDACT_CONFIG = Buffer.from(
156
+ JSON.stringify(serializable),
157
+ ).toString("base64");
158
+ const envConfig = parseEnvRedactConfig();
159
+
160
+ const redact = createRedact(envConfig);
161
+ const result = redact(
162
+ "transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
163
+ );
164
+ expect(result).toBe(
165
+ "transfer 538845bf2f418e0c7f3798d6bcb632273d46633545a5e261feceb7d378ed0761",
166
+ );
167
+ });
168
+ });