@logtrace/shared 0.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/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@logtrace/shared",
3
+ "version": "0.1.0",
4
+ "description": "Utilidades compartidas del ecosistema LogTrace (constantes, máscara de datos sensibles, helpers).",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./constants": "./src/constants.js",
10
+ "./utils": "./src/utils.js",
11
+ "./masker": "./src/masker.js"
12
+ },
13
+ "files": ["src"],
14
+ "engines": {
15
+ "node": ">=20.0.0"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/logtrace/tracker-cli.git",
20
+ "directory": "packages/shared"
21
+ },
22
+ "homepage": "https://logtrace.cloud",
23
+ "license": "MIT",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "test": "node --test test/**/*.test.js"
29
+ }
30
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @logtrace/shared — constants.js
3
+ *
4
+ * Single source of truth for paths, versions, and config shared
5
+ * between daemon and CLI.
6
+ *
7
+ * ENV overrides let dev run without root or /opt access.
8
+ * Dev: LOGTRACE_DIR=./tmp/logtrace SOCKET_PATH=./tmp/logtrace/daemon.sock
9
+ * Prod: defaults below (/opt/logtrace, /run/logtrace/daemon.sock)
10
+ */
11
+
12
+ export const LOGTRACE_DIR = process.env.LOGTRACE_DIR || '/opt/logtrace'
13
+ export const SOCKET_PATH = process.env.SOCKET_PATH || '/run/logtrace/daemon.sock'
14
+
15
+ export const VERSION = '0.1.0'
16
+ export const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'critical']
17
+
18
+ /** True when running in development mode — relaxes license checks, verbose logs */
19
+ export const IS_DEV = process.env.LOGTRACE_ENV === 'development'
20
+
21
+ /** Directories created by daemon on startup */
22
+ export const DAEMON_DIRS = [
23
+ `${LOGTRACE_DIR}/data`,
24
+ `${LOGTRACE_DIR}/backups`,
25
+ `${LOGTRACE_DIR}/logs`,
26
+ ]
27
+
28
+ /** SQLite database path */
29
+ export const DB_PATH = `${LOGTRACE_DIR}/data/logtrace.db`
30
+
31
+ /** License cache path */
32
+ export const LICENSE_CACHE_PATH = `${LOGTRACE_DIR}/.license-cache.json`
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './constants.js'
2
+ export * from './utils.js'
3
+ export * from './masker.js'
package/src/masker.js ADDED
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @logtrace/shared — masker.js
3
+ *
4
+ * maskLog(obj) strips sensitive data from a log object BEFORE it is stored
5
+ * in SQLite or forwarded to the central server.
6
+ *
7
+ * Patterns applied (in order):
8
+ * EMAIL — user@example.com → [EMAIL]
9
+ * CARD — 16-digit Luhn numbers → [CARD]
10
+ * JWT — eyJh...eyJh...sig → [JWT]
11
+ * AWS_KEY — AKIA[A-Z0-9]{16} → [AWS_KEY]
12
+ * PASSWORD — JSON field "password":"…" → "password":"[REDACTED]"
13
+ * SECRET — JSON field "secret":"…" → "secret":"[REDACTED]"
14
+ * TOKEN — JSON field "token":"…" → "token":"[REDACTED]"
15
+ *
16
+ * Only string values inside the object are modified.
17
+ * Non-string values (numbers, booleans, null) are left as-is.
18
+ * Nested objects are processed recursively.
19
+ *
20
+ * Custom patterns can be added at runtime via addPattern().
21
+ */
22
+
23
+ /** @type {Array<{regex: RegExp, replace: string}>} */
24
+ const PATTERNS = [
25
+ // Email addresses
26
+ {
27
+ regex: /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,
28
+ replace: '[EMAIL]',
29
+ },
30
+ // Credit/debit card numbers (Luhn-structured 16-digit, with optional separators)
31
+ {
32
+ regex: /\b(?:\d[ \-]?){13,15}\d\b/g,
33
+ replace: '[CARD]',
34
+ },
35
+ // JSON Web Tokens (3-part base64url)
36
+ {
37
+ regex: /eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/g,
38
+ replace: '[JWT]',
39
+ },
40
+ // AWS access key IDs
41
+ {
42
+ regex: /AKIA[0-9A-Z]{16}/g,
43
+ replace: '[AWS_KEY]',
44
+ },
45
+ // JSON field: "password":"<value>" (double or single quotes, any depth)
46
+ {
47
+ regex: /"password"\s*:\s*"[^"]*"/gi,
48
+ replace: '"password":"[REDACTED]"',
49
+ },
50
+ // JSON field: "secret":"<value>"
51
+ {
52
+ regex: /"secret"\s*:\s*"[^"]*"/gi,
53
+ replace: '"secret":"[REDACTED]"',
54
+ },
55
+ // JSON field: "token":"<value>" (catches api_token, access_token, etc.)
56
+ {
57
+ regex: /"[a-z_]*token[a-z_]*"\s*:\s*"[^"]*"/gi,
58
+ replace: (match) => match.replace(/"[^"]*"$/, '"[REDACTED]"'),
59
+ },
60
+ ]
61
+
62
+ /**
63
+ * Add a custom masking pattern at runtime.
64
+ * @param {RegExp} regex
65
+ * @param {string|Function} replace
66
+ */
67
+ export function addPattern(regex, replace) {
68
+ PATTERNS.push({ regex, replace })
69
+ }
70
+
71
+ /**
72
+ * Apply all masking patterns to a single string value.
73
+ * @param {string} str
74
+ * @returns {string}
75
+ */
76
+ function maskString(str) {
77
+ let result = str
78
+ for (const { regex, replace } of PATTERNS) {
79
+ // Reset lastIndex for global regexes to avoid stale state
80
+ if (regex.global) regex.lastIndex = 0
81
+ result = result.replace(regex, replace)
82
+ }
83
+ return result
84
+ }
85
+
86
+ /**
87
+ * Recursively mask all string values in an object or array.
88
+ * Returns a new object — the original is never mutated.
89
+ *
90
+ * @param {any} obj
91
+ * @returns {any}
92
+ */
93
+ export function maskLog(obj) {
94
+ if (obj === null || obj === undefined) return obj
95
+
96
+ if (typeof obj === 'string') {
97
+ return maskString(obj)
98
+ }
99
+
100
+ if (Array.isArray(obj)) {
101
+ return obj.map(maskLog)
102
+ }
103
+
104
+ if (typeof obj === 'object') {
105
+ const result = {}
106
+ for (const [key, value] of Object.entries(obj)) {
107
+ result[key] = maskLog(value)
108
+ }
109
+ return result
110
+ }
111
+
112
+ // number, boolean, bigint, symbol — return as-is
113
+ return obj
114
+ }
package/src/utils.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @logtrace/shared — utils.js
3
+ *
4
+ * Utility functions shared between daemon and CLI.
5
+ */
6
+
7
+ import { createHash } from 'node:crypto'
8
+
9
+ /**
10
+ * fingerprint(log) — deterministic hash for deduplication.
11
+ *
12
+ * Two log entries with the same level + service + message produce the same
13
+ * fingerprint, regardless of timestamp or meta. This lets the alert system
14
+ * deduplicate bursts without a DB query per log.
15
+ *
16
+ * @param {object} log - { level, service, message }
17
+ * @returns {string} 16-char hex fingerprint
18
+ */
19
+ export function fingerprint(log) {
20
+ const key = [
21
+ (log.level || '').toLowerCase(),
22
+ (log.service || '').toLowerCase(),
23
+ (log.message || '').slice(0, 200), // cap at 200 chars — ignore trailing noise
24
+ ].join('|')
25
+ return createHash('sha256').update(key).digest('hex').slice(0, 16)
26
+ }
27
+
28
+ /**
29
+ * formatDate(ts) — ISO timestamp → human-readable local string.
30
+ *
31
+ * @param {number|string|Date} ts
32
+ * @returns {string} e.g. "2026-06-09 14:32:01"
33
+ */
34
+ export function formatDate(ts) {
35
+ const d = ts instanceof Date ? ts : new Date(ts)
36
+ if (isNaN(d.getTime())) return 'invalid date'
37
+ const pad = (n) => String(n).padStart(2, '0')
38
+ return (
39
+ `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
40
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
41
+ )
42
+ }
43
+
44
+ /**
45
+ * truncate(str, n) — truncate a string to n chars, appending "…" if cut.
46
+ *
47
+ * @param {string} str
48
+ * @param {number} n - maximum length (including the ellipsis character)
49
+ * @returns {string}
50
+ */
51
+ export function truncate(str, n) {
52
+ if (typeof str !== 'string') return String(str)
53
+ if (str.length <= n) return str
54
+ return str.slice(0, n - 1) + '…'
55
+ }