@crimsonsunset/jsg-logger 1.0.8

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.
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Browser Console Formatter for CACP Logger
3
+ * Beautiful console output with emoji, colors, and context trees
4
+ * Supports file overrides, display toggles, and smart configuration
5
+ */
6
+
7
+ import {configManager} from '../config/config-manager.js';
8
+
9
+ /**
10
+ * Create browser console formatter for a specific component
11
+ * @param {string} componentName - Component identifier
12
+ * @param {Object} logStore - Optional log store for capturing logs
13
+ * @returns {Object} Stream-like object for Pino
14
+ */
15
+ export const createBrowserFormatter = (componentName, logStore = null) => {
16
+ return {
17
+ write: (data) => {
18
+ try {
19
+ const logData = typeof data === 'string' ? JSON.parse(data) : data;
20
+
21
+ // Get file context from stack trace if available
22
+ const filePath = getFilePathFromStack();
23
+ if (filePath) {
24
+ configManager.setCurrentFile(filePath);
25
+ }
26
+
27
+ // Get component config with file override support
28
+ const component = configManager.getComponentConfig(componentName, filePath);
29
+ const level = configManager.getLevelConfig(logData.level);
30
+ const displayConfig = configManager.getDisplayConfig(filePath);
31
+
32
+ // Check if this log should be displayed based on effective level
33
+ const effectiveLevel = configManager.getEffectiveLevel(componentName, filePath);
34
+ if (!shouldDisplay(logData.level, effectiveLevel)) {
35
+ return;
36
+ }
37
+
38
+ // Build the log parts
39
+ const logParts = [];
40
+ const logStyles = [];
41
+
42
+ // 1. Timestamp
43
+ if (displayConfig.timestamp) {
44
+ const timestamp = configManager.formatTimestamp(logData.time);
45
+ if (timestamp) {
46
+ logParts.push(`%c${timestamp}`);
47
+ logStyles.push('color: #888; font-family: monospace;');
48
+ }
49
+ }
50
+
51
+ // 2. Level emoji
52
+ if (displayConfig.emoji) {
53
+ const emoji = component.emoji || level.emoji;
54
+ logParts.push(`%c${emoji}`);
55
+ logStyles.push(`color: ${level.color}; font-size: 14px;`);
56
+ }
57
+
58
+ // 3. Component name
59
+ if (displayConfig.component) {
60
+ const componentName = component.name.toUpperCase().replace(/([a-z])([A-Z])/g, '$1-$2');
61
+ logParts.push(`%c[${componentName}]`);
62
+ logStyles.push(`color: ${component.color}; font-weight: bold;`);
63
+ }
64
+
65
+ // 4. Level name (optional)
66
+ if (displayConfig.level) {
67
+ logParts.push(`%c${level.name}`);
68
+ logStyles.push(`color: ${level.color}; font-weight: normal;`);
69
+ }
70
+
71
+ // 5. Message
72
+ if (displayConfig.message) {
73
+ const message = logData.msg || '';
74
+ logParts.push(`%c${message}`);
75
+ logStyles.push('color: inherit;');
76
+ }
77
+
78
+ // Combine and log the main message
79
+ if (logParts.length > 0) {
80
+ console.log(logParts.join(' '), ...logStyles);
81
+ }
82
+
83
+ // 6. JSON payload context (if enabled)
84
+ if (displayConfig.jsonPayload) {
85
+ const contextData = extractContextData(logData);
86
+ if (Object.keys(contextData).length > 0) {
87
+ displayContextData(contextData);
88
+ }
89
+ }
90
+
91
+ // 7. Stack trace (if enabled and present)
92
+ if (displayConfig.stackTrace && logData.err && logData.err.stack) {
93
+ console.error(' ╰─ Error Stack:');
94
+ console.error(logData.err.stack);
95
+ }
96
+
97
+ // Store log if logStore provided
98
+ if (logStore) {
99
+ logStore.add({
100
+ ...logData,
101
+ filePath,
102
+ effectiveLevel,
103
+ component: componentName,
104
+ displayConfig
105
+ });
106
+ }
107
+
108
+ } catch (error) {
109
+ // Fallback for malformed data
110
+ console.log(data);
111
+ if (logStore) {
112
+ logStore.add({
113
+ level: 50,
114
+ time: Date.now(),
115
+ msg: 'Logger formatting error',
116
+ error: error.message,
117
+ data
118
+ });
119
+ }
120
+ }
121
+ }
122
+ };
123
+ };
124
+
125
+ /**
126
+ * Check if a log should be displayed based on level hierarchy
127
+ * @param {number} logLevel - Log level number
128
+ * @param {string} effectiveLevel - Effective level string
129
+ * @returns {boolean} Whether log should be displayed
130
+ * @private
131
+ */
132
+ function shouldDisplay(logLevel, effectiveLevel) {
133
+ const levelMap = {
134
+ 'trace': 10, 'debug': 20, 'info': 30,
135
+ 'warn': 40, 'error': 50, 'fatal': 60
136
+ };
137
+
138
+ const minLevel = levelMap[effectiveLevel] || 30;
139
+ return logLevel >= minLevel;
140
+ }
141
+
142
+ /**
143
+ * Extract context data from log data (excluding standard fields)
144
+ * @param {Object} logData - Log data object
145
+ * @returns {Object} Context data
146
+ * @private
147
+ */
148
+ function extractContextData(logData) {
149
+ const excludeKeys = new Set(['level', 'time', 'msg', 'name', 'pid', 'hostname', 'v']);
150
+ const contextData = {};
151
+
152
+ Object.keys(logData).forEach(key => {
153
+ if (!excludeKeys.has(key)) {
154
+ contextData[key] = logData[key];
155
+ }
156
+ });
157
+
158
+ return contextData;
159
+ }
160
+
161
+ /**
162
+ * Display context data with tree-like structure
163
+ * @param {Object} contextData - Context data to display
164
+ * @private
165
+ */
166
+ function displayContextData(contextData) {
167
+ Object.entries(contextData).forEach(([key, value]) => {
168
+ if (typeof value === 'object' && value !== null) {
169
+ console.log(` ├─ %c${key}:`, 'color: #00C896; font-weight: bold;', value);
170
+ } else {
171
+ console.log(` ├─ %c${key}: %c${value}`, 'color: #00C896; font-weight: bold;', 'color: inherit;');
172
+ }
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Extract file path from error stack trace
178
+ * @returns {string|null} File path or null
179
+ * @private
180
+ */
181
+ function getFilePathFromStack() {
182
+ try {
183
+ const stack = new Error().stack;
184
+ if (!stack) return null;
185
+
186
+ const lines = stack.split('\n');
187
+
188
+ // Look for the first line that doesn't contain logger internals
189
+ for (let i = 1; i < lines.length; i++) {
190
+ const line = lines[i];
191
+
192
+ // Skip logger internal files
193
+ if (line.includes('logger') || line.includes('pino') || line.includes('browser-formatter')) {
194
+ continue;
195
+ }
196
+
197
+ // Extract file path from stack trace
198
+ const match = line.match(/at .+?\((.+?):\d+:\d+\)/);
199
+ if (match) {
200
+ let filePath = match[1];
201
+
202
+ // Clean up the file path
203
+ if (filePath.startsWith('file://')) {
204
+ filePath = filePath.replace('file://', '');
205
+ }
206
+
207
+ // Extract relative path from absolute path
208
+ const srcIndex = filePath.lastIndexOf('/src/');
209
+ if (srcIndex !== -1) {
210
+ return 'src/' + filePath.substring(srcIndex + 5);
211
+ }
212
+
213
+ // Return the last part of the path
214
+ const parts = filePath.split('/');
215
+ if (parts.length >= 2) {
216
+ return parts.slice(-2).join('/');
217
+ }
218
+
219
+ return parts[parts.length - 1];
220
+ }
221
+ }
222
+
223
+ return null;
224
+ } catch (error) {
225
+ return null;
226
+ }
227
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * CLI/Terminal Formatter for CACP Logger
3
+ * Uses pino-colada for beautiful terminal output with fallbacks
4
+ */
5
+
6
+ import { COMPONENT_SCHEME, LEVEL_SCHEME } from '../config/component-schemes.js';
7
+
8
+ /**
9
+ * Create CLI formatter using pino-colada or pino-pretty
10
+ * @returns {Object} Stream-like object for Pino
11
+ */
12
+ export const createCLIFormatter = () => {
13
+ try {
14
+ // Try to use pino-colada if available
15
+ const pinoColada = require('pino-colada');
16
+ return pinoColada();
17
+ } catch (error) {
18
+ // Fallback to pino-pretty if pino-colada not available
19
+ try {
20
+ const pinoPretty = require('pino-pretty');
21
+ return pinoPretty({
22
+ colorize: true,
23
+ translateTime: 'HH:MM:ss.l',
24
+ messageFormat: (log, messageKey, levelLabel) => {
25
+ const component = COMPONENT_SCHEME[log.name] || COMPONENT_SCHEME['cacp'];
26
+ const componentName = component.name.toUpperCase().replace(/([a-z])([A-Z])/g, '$1-$2');
27
+ const level = LEVEL_SCHEME[log.level] || LEVEL_SCHEME[30];
28
+ return `${level.emoji} [${componentName}] ${log[messageKey]}`;
29
+ },
30
+ customPrettifiers: {
31
+ level: () => '', // Hide level since we show it in messageFormat
32
+ time: (timestamp) => timestamp,
33
+ name: () => '' // Hide name since we show it in messageFormat
34
+ }
35
+ });
36
+ } catch (error) {
37
+ // Ultimate fallback - basic JSON
38
+ return {
39
+ write: (data) => console.log(data)
40
+ };
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Server/Production Formatter for CACP Logger
3
+ * Structured JSON output for production logging and log aggregation
4
+ */
5
+
6
+ /**
7
+ * Create server formatter (structured JSON)
8
+ * @returns {null} No custom formatter - uses Pino's default JSON output
9
+ */
10
+ export const createServerFormatter = () => {
11
+ // Server environments use default Pino JSON output
12
+ // This provides structured logs suitable for log aggregation systems
13
+ return null;
14
+ };
15
+
16
+ /**
17
+ * Server-specific configuration
18
+ * @returns {Object} Pino configuration for server environments
19
+ */
20
+ export const getServerConfig = () => {
21
+ return {
22
+ level: 'info', // More conservative logging in production
23
+ formatters: {
24
+ // Ensure consistent timestamp format
25
+ time: () => `,"time":"${new Date().toISOString()}"`,
26
+
27
+ // Add environment context
28
+ bindings: (bindings) => {
29
+ return {
30
+ pid: bindings.pid,
31
+ hostname: bindings.hostname,
32
+ environment: 'server'
33
+ };
34
+ }
35
+ },
36
+ // Redact sensitive information in production
37
+ redact: {
38
+ paths: ['password', 'token', 'key', 'secret'],
39
+ censor: '[REDACTED]'
40
+ }
41
+ };
42
+ };