@@ -204,30 +204,29 @@ class Logger {
204204 ? console . log . bind ( console )
205205 : consoleFn ;
206206 const normalizedCallsite = this . normalizeCallsite ( callsite ) ;
207- const shouldGroupCallsite = this . shouldGroupCallsite ( normalizedCallsite ) ;
208- const outputFn = shouldGroupCallsite
207+ const shouldGroupDetails = this . shouldGroupDetails ( normalizedCallsite ) ;
208+ const outputFn = shouldGroupDetails
209209 ? console . groupCollapsed . bind ( console )
210210 : consoleFn ;
211211 const { root, detail } = this . splitModuleName ( module ) ;
212- const message = this . stringifyArgs ( args ) ;
212+ const consoleArgs = this . formatConsoleArgs ( args ) ;
213213 const suffix = this . formatSuffix ( detail , normalizedCallsite ) ;
214+ const suffixArgs = suffix ? [ suffix . trimStart ( ) ] : [ ] ;
214215 if ( this . config . useColors && typeof consoleFn === 'function' ) {
215216 const color = COLORS [ level ] || '#000000' ;
216- outputFn (
217- `%c ${ levelName . padEnd ( LEVEL_WIDTH , ' ' ) } %c ${ timestamp } %c %c ${ root } %c %c ${ message } %c${ suffix } ` ,
218- `background:${ color } ;color:#fff;font-weight:bold;` ,
219- `background:${ TIME_BG } ;color:#fff;font-weight:bold;` ,
220- `background:${ color } ;color:${ color } ;` ,
221- `background:${ TIME_BG } ;color:#fff;font-weight:bold;` ,
222- `background:${ color } ;color:${ color } ;` ,
223- `color:${ color } ;font-weight:bold;` ,
224- '' ,
225- ) ;
226- this . printCallsiteDetails ( detailFn , normalizedCallsite ) ;
217+ outputFn ( ...this . formatStyledConsoleArgs ( {
218+ levelName,
219+ timestamp,
220+ root,
221+ args : consoleArgs ,
222+ suffix : suffixArgs [ 0 ] || '' ,
223+ color
224+ } ) ) ;
225+ this . printExpandedDetails ( detailFn , normalizedCallsite ) ;
227226 return ;
228227 }
229- outputFn ( `${ levelName . padEnd ( LEVEL_WIDTH , ' ' ) } ${ timestamp } ${ root } ${ message } ${ suffix } ` ) ;
230- this . printCallsiteDetails ( detailFn , normalizedCallsite ) ;
228+ outputFn ( `${ levelName . padEnd ( LEVEL_WIDTH , ' ' ) } ${ timestamp } ${ root } ` , ... consoleArgs , ... suffixArgs ) ;
229+ this . printExpandedDetails ( detailFn , normalizedCallsite ) ;
231230 }
232231
233232 splitModuleName ( module ) {
@@ -266,6 +265,75 @@ class Logger {
266265 } ) . join ( ' ' ) ;
267266 }
268267
268+ formatConsoleArgs ( args = [ ] ) {
269+ return args . flatMap ( ( arg ) => this . formatConsoleArg ( arg ) ) ;
270+ }
271+
272+ formatConsoleArg ( arg ) {
273+ if ( typeof arg === 'string' ) {
274+ const payload = this . parseJsonPayloadFromString ( arg ) ;
275+ if ( payload ) {
276+ return payload . prefix ? [ payload . prefix , payload . value ] : [ payload . value ] ;
277+ }
278+ }
279+ return [ arg ] ;
280+ }
281+
282+ formatStyledConsoleArgs ( { levelName, timestamp, root, args = [ ] , suffix = '' , color } ) {
283+ const outputArgs = [
284+ `%c ${ levelName . padEnd ( LEVEL_WIDTH , ' ' ) } %c ${ timestamp } %c %c ${ root } %c %c ` ,
285+ `background:${ color } ;color:#fff;font-weight:bold;` ,
286+ `background:${ TIME_BG } ;color:#fff;font-weight:bold;` ,
287+ `background:${ color } ;color:${ color } ;` ,
288+ `background:${ TIME_BG } ;color:#fff;font-weight:bold;` ,
289+ `background:${ color } ;color:${ color } ;` ,
290+ `color:${ color } ;font-weight:bold;`
291+ ] ;
292+
293+ let format = outputArgs [ 0 ] ;
294+ let hasContent = false ;
295+ args . forEach ( ( arg ) => {
296+ if ( hasContent ) {
297+ format += ' ' ;
298+ }
299+ if ( this . isExpandableConsoleArg ( arg ) ) {
300+ format += '%o' ;
301+ outputArgs . push ( arg ) ;
302+ }
303+ else {
304+ format += this . escapeConsoleFormat ( this . stringifyConsolePrimitive ( arg ) ) ;
305+ }
306+ hasContent = true ;
307+ } ) ;
308+
309+ if ( suffix ) {
310+ if ( hasContent ) {
311+ format += ' ' ;
312+ }
313+ format += `%c${ this . escapeConsoleFormat ( suffix ) } ` ;
314+ outputArgs . push ( '' ) ;
315+ }
316+
317+ outputArgs [ 0 ] = format ;
318+ return outputArgs ;
319+ }
320+
321+ isExpandableConsoleArg ( arg ) {
322+ return ( typeof arg === 'object' && arg !== null && ! ( arg instanceof Error ) )
323+ || typeof arg === 'function' ;
324+ }
325+
326+ stringifyConsolePrimitive ( arg ) {
327+ if ( arg instanceof Error ) {
328+ return arg . stack || arg . message ;
329+ }
330+ return String ( arg ) ;
331+ }
332+
333+ escapeConsoleFormat ( value ) {
334+ return String ( value ) . replace ( / % / g, '%%' ) ;
335+ }
336+
269337 getCallsite ( ) {
270338 const stack = new Error ( ) . stack ;
271339 if ( ! stack ) {
@@ -353,21 +421,70 @@ class Logger {
353421 return filename . replace ( / \. (?: m j s | j s | t s x ? | j s x ) $ / i, '' ) || this . formatCallsite ( callsite ) ;
354422 }
355423
356- shouldGroupCallsite ( callsite ) {
424+ shouldGroupDetails ( callsite ) {
357425 return this . config . compactCallsite !== false
358426 && ! ! callsite ?. full
359427 && typeof console . groupCollapsed === 'function'
360428 && typeof console . groupEnd === 'function' ;
361429 }
362430
363- printCallsiteDetails ( consoleFn , callsite ) {
364- if ( ! this . shouldGroupCallsite ( callsite ) ) {
431+ printExpandedDetails ( consoleFn , callsite ) {
432+ if ( ! this . shouldGroupDetails ( callsite ) ) {
365433 return ;
366434 }
367- consoleFn ( `Source: ${ callsite . full } ` ) ;
435+ if ( callsite ?. full ) {
436+ consoleFn ( `Source: ${ callsite . full } ` ) ;
437+ }
368438 console . groupEnd ( ) ;
369439 }
370440
441+ parseJsonPayloadFromString ( value ) {
442+ const parsed = this . parseJsonLikeString ( value ) ;
443+ if ( parsed !== null ) {
444+ return {
445+ prefix : '' ,
446+ value : parsed
447+ } ;
448+ }
449+ const start = this . findJsonPayloadStart ( value ) ;
450+ if ( start <= 0 ) {
451+ return null ;
452+ }
453+ const payload = this . parseJsonLikeString ( value . slice ( start ) ) ;
454+ if ( payload === null ) {
455+ return null ;
456+ }
457+ return {
458+ prefix : value . slice ( 0 , start ) . trimEnd ( ) ,
459+ value : payload
460+ } ;
461+ }
462+
463+ findJsonPayloadStart ( value ) {
464+ const objectStart = value . indexOf ( '{' ) ;
465+ const arrayStart = value . indexOf ( '[' ) ;
466+ if ( objectStart < 0 ) {
467+ return arrayStart ;
468+ }
469+ if ( arrayStart < 0 ) {
470+ return objectStart ;
471+ }
472+ return Math . min ( objectStart , arrayStart ) ;
473+ }
474+
475+ parseJsonLikeString ( value ) {
476+ const trimmed = value . trim ( ) ;
477+ if ( ! trimmed || ! / ^ [ { [ ] / . test ( trimmed ) ) {
478+ return null ;
479+ }
480+ try {
481+ return JSON . parse ( trimmed ) ;
482+ }
483+ catch ( e ) {
484+ return null ;
485+ }
486+ }
487+
371488 formatSuffix ( detail , callsite , options = { } ) {
372489 const normalizedCallsite = this . normalizeCallsite ( callsite ) ;
373490 const compactCallsite = options . compactCallsite ?? this . config . compactCallsite !== false ;
0 commit comments