55 "os"
66 "time"
77
8+ "charm.land/lipgloss/v2"
89 "charm.land/log/v2"
910 "github.com/charmbracelet/colorprofile"
1011 "github.com/muesli/termenv"
@@ -193,14 +194,14 @@ func (l *StandardLogger) Infof(msg string, args ...any) {
193194 l .syncLoggerFormat ()
194195 switch l .mode {
195196 case Text :
196- l .PlainTextInfo (fmt . Sprintf (msg , args ... ))
197+ l .PlainTextInfo (safeSprintf (msg , args ... ))
197198 return
198199 case Hidden :
199200 return
200201 case JSON , Logfmt :
201- l .outHandler .Infof ( msg , args ... )
202+ l .outHandler .Info ( safeSprintf ( msg , args ... ) )
202203 if l .archiveHandler != nil {
203- l .archiveHandler .Infof ( msg , args ... )
204+ l .archiveHandler .Info ( safeSprintf ( msg , args ... ) )
204205 }
205206 }
206207}
@@ -209,14 +210,14 @@ func (l *StandardLogger) Noticef(msg string, args ...any) {
209210 l .syncLoggerFormat ()
210211 switch l .mode {
211212 case Text :
212- l .PlainTextNotice (fmt . Sprintf (msg , args ... ))
213+ l .PlainTextNotice (safeSprintf (msg , args ... ))
213214 return
214215 case Hidden :
215216 return
216217 case JSON , Logfmt :
217- l .outHandler .With ().Log (themes .LogNoticeLevel , msg , args ... )
218+ l .outHandler .With ().Log (themes .LogNoticeLevel , safeSprintf ( msg , args ... ) )
218219 if l .archiveHandler != nil {
219- l .archiveHandler .Errorf ( msg , args ... )
220+ l .archiveHandler .Error ( safeSprintf ( msg , args ... ) )
220221 }
221222 }
222223}
@@ -225,14 +226,14 @@ func (l *StandardLogger) Debugf(msg string, args ...any) {
225226 l .syncLoggerFormat ()
226227 switch l .mode {
227228 case Text :
228- l .PlainTextDebug (fmt . Sprintf (msg , args ... ))
229+ l .PlainTextDebug (safeSprintf (msg , args ... ))
229230 return
230231 case Hidden :
231232 return
232233 case JSON , Logfmt :
233- l .outHandler .Debugf ( msg , args ... )
234+ l .outHandler .Debug ( safeSprintf ( msg , args ... ) )
234235 if l .archiveHandler != nil {
235- l .archiveHandler .Debugf ( msg , args ... )
236+ l .archiveHandler .Debug ( safeSprintf ( msg , args ... ) )
236237 }
237238 }
238239}
@@ -251,14 +252,14 @@ func (l *StandardLogger) Errorf(msg string, args ...any) {
251252 l .syncLoggerFormat ()
252253 switch l .mode {
253254 case Text :
254- l .PlainTextError (fmt . Sprintf (msg , args ... ))
255+ l .PlainTextError (safeSprintf (msg , args ... ))
255256 return
256257 case Hidden :
257258 return
258259 case JSON , Logfmt :
259- l .outHandler .Errorf ( msg , args ... )
260+ l .outHandler .Error ( safeSprintf ( msg , args ... ) )
260261 if l .archiveHandler != nil {
261- l .archiveHandler .Errorf ( msg , args ... )
262+ l .archiveHandler .Error ( safeSprintf ( msg , args ... ) )
262263 }
263264 }
264265}
@@ -267,14 +268,14 @@ func (l *StandardLogger) Warnf(msg string, args ...any) {
267268 l .syncLoggerFormat ()
268269 switch l .mode {
269270 case Text :
270- l .PlainTextWarn (fmt . Sprintf (msg , args ... ))
271+ l .PlainTextWarn (safeSprintf (msg , args ... ))
271272 return
272273 case Hidden :
273274 return
274275 case JSON , Logfmt :
275- l .outHandler .Warnf ( msg , args ... )
276+ l .outHandler .Warn ( safeSprintf ( msg , args ... ) )
276277 if l .archiveHandler != nil {
277- l .archiveHandler .Warnf ( msg , args ... )
278+ l .archiveHandler .Warn ( safeSprintf ( msg , args ... ) )
278279 }
279280 }
280281}
@@ -285,18 +286,19 @@ func (l *StandardLogger) FatalErr(err error) {
285286
286287func (l * StandardLogger ) Fatalf (msg string , args ... any ) {
287288 l .syncLoggerFormat ()
289+ formatted := safeSprintf (msg , args ... )
288290 switch l .mode {
289291 case Text :
290- l .PlainTextError (fmt . Sprintf ( msg , args ... ) )
291- l .exitFunc (msg , args ... )
292+ l .PlainTextError (formatted )
293+ l .exitFunc (formatted )
292294 return
293295 case Hidden :
294296 return
295297 case JSON , Logfmt :
296298 if l .archiveHandler != nil {
297- l .archiveHandler .Errorf ( msg , args ... )
299+ l .archiveHandler .Error ( formatted )
298300 }
299- l .outHandler .Fatalf ( msg , args ... )
301+ l .outHandler .Fatal ( formatted )
300302 }
301303}
302304
@@ -450,6 +452,118 @@ func (l *StandardLogger) syncLoggerFormat() {
450452 }
451453}
452454
455+ // safeSprintf applies fmt.Sprintf only when args are provided.
456+ // When called with no args, it returns msg as-is, avoiding
457+ // misinterpretation of % characters in the message.
458+ func safeSprintf (msg string , args ... any ) string {
459+ if len (args ) == 0 {
460+ return msg
461+ }
462+ return fmt .Sprintf (msg , args ... )
463+ }
464+
453465func defaultExit (_ string , _ ... any ) {
454466 os .Exit (1 )
455467}
468+
469+ // --- TaskAwareLogger implementation ---
470+
471+ func (l * StandardLogger ) PrintWithTask (task * TaskContext , line string ) {
472+ if l .mode == Hidden {
473+ return
474+ }
475+ prefix := taskPrefix (task , l .theme .ColorPalette ())
476+ formatted := fmt .Sprintf ("%s %s" , prefix , line )
477+ _ , _ = fmt .Fprintln (l .outFile , formatted )
478+
479+ if l .archiveHandler != nil {
480+ l .archiveHandler .Info (line , "task" , task .Name )
481+ }
482+ }
483+
484+ func (l * StandardLogger ) PrintErrWithTask (task * TaskContext , line string ) {
485+ if l .mode == Hidden {
486+ return
487+ }
488+ prefix := taskPrefix (task , l .theme .ColorPalette ())
489+ errLine := l .theme .RenderError (line )
490+ formatted := fmt .Sprintf ("%s %s" , prefix , errLine )
491+ _ , _ = fmt .Fprintln (l .outFile , formatted )
492+
493+ if l .archiveHandler != nil {
494+ l .archiveHandler .Error (line , "task" , task .Name )
495+ }
496+ }
497+
498+ func (l * StandardLogger ) PrintTaskSummary (tasks []* TaskContext ) {
499+ if l .mode == Hidden || len (tasks ) == 0 {
500+ return
501+ }
502+
503+ _ , _ = fmt .Fprintln (l .outFile )
504+
505+ palette := l .theme .ColorPalette ()
506+ headerStyle := lipgloss .NewStyle ().Bold (true ).Foreground (lipgloss .Color (palette .Primary ))
507+ _ , _ = fmt .Fprintln (l .outFile , headerStyle .Render ("Task Summary" ))
508+
509+ maxLen := 0
510+ for _ , t := range tasks {
511+ if len (t .Name ) > maxLen {
512+ maxLen = len (t .Name )
513+ }
514+ }
515+
516+ for _ , t := range tasks {
517+ var statusColor string
518+ switch t .Status {
519+ case TaskSuccess :
520+ statusColor = palette .Success
521+ case TaskFailed :
522+ statusColor = palette .Error
523+ case TaskSkipped :
524+ statusColor = palette .Warning
525+ case TaskRunning :
526+ statusColor = palette .Info
527+ }
528+
529+ nameStyle := lipgloss .NewStyle ().Width (maxLen + 2 )
530+ statusStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color (statusColor )).Bold (true )
531+ durationStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color (palette .Gray ))
532+
533+ line := fmt .Sprintf (" %s %s %s" ,
534+ nameStyle .Render (t .Name ),
535+ statusStyle .Render (fmt .Sprintf ("%-4s" , t .Status .Icon ())),
536+ durationStyle .Render (t .Duration ().Truncate (time .Millisecond ).String ()),
537+ )
538+ _ , _ = fmt .Fprintln (l .outFile , line )
539+ }
540+ _ , _ = fmt .Fprintln (l .outFile )
541+
542+ if l .archiveHandler != nil {
543+ for _ , t := range tasks {
544+ l .archiveHandler .Info ("task_summary" ,
545+ "task" , t .Name ,
546+ "status" , t .Status .String (),
547+ "duration" , t .Duration ().String (),
548+ )
549+ }
550+ }
551+ }
552+
553+ func (l * StandardLogger ) BeginGroup (name string ) {
554+ if isCI () {
555+ _ , _ = fmt .Fprintf (l .outFile , "::group::%s\n " , name )
556+ } else {
557+ palette := l .theme .ColorPalette ()
558+ style := lipgloss .NewStyle ().
559+ Foreground (lipgloss .Color (palette .Secondary )).
560+ Bold (true )
561+ _ , _ = fmt .Fprintf (l .outFile , "\n %s\n " , style .Render ("--- " + name + " ---" ))
562+ }
563+ }
564+
565+ func (l * StandardLogger ) EndGroup () {
566+ if isCI () {
567+ _ , _ = fmt .Fprintln (l .outFile , "::endgroup::" )
568+ }
569+ }
0 commit comments