package sneklog import ( "fmt" "strings" "time" ) // Formatter // Format: %t - time, %N - name, %l - level, %L - level uppercase, %b - traceback, %B - full traceback, %m - message, // %M - method, %f - filename, %n - line number, %s - method signature, %p - full path // Example: "%t [%l] %N: %m (%f:%n %s)" // // TimeStampFormat: %Y - year, %m - month, %d - day, %H - hour, %M - minute, %S - second, // %z - timezone(i.e. 0300), %Z - timezone(i.e. 03:00) // Example: "%d.%m.%Y %H:%M:%S" // // TraceBackFormat: %M - method, %f - filename, %n - line number, %s - method signature, %p - full path type Formatter struct { Format string MessageSeparator string // JSONTraceBackPath // If true, in traceback will be path to file, otherwise only filename. JSONTraceBackPath bool TimeStampFormat string TraceBackFormat string TraceBackSeparator string ColorOutput bool ColorOnlyStdout bool } var DefaultTextFormatter = &Formatter{ Format: "%t %l %N: %m", MessageSeparator: " ", TimeStampFormat: RFC3339, TraceBackFormat: "%M (%f:%n %s)", TraceBackSeparator: "->", ColorOutput: true, ColorOnlyStdout: true, } var DefaultJsonFormatter = &Formatter{ Format: "%m", MessageSeparator: " ", TimeStampFormat: RFC3339, ColorOutput: false, } func NewFormatter() *Formatter { return &Formatter{ Format: DefaultTextFormatter.Format, MessageSeparator: DefaultTextFormatter.MessageSeparator, TimeStampFormat: DefaultTextFormatter.TimeStampFormat, TraceBackFormat: DefaultTextFormatter.TraceBackFormat, TraceBackSeparator: DefaultTextFormatter.TraceBackSeparator, ColorOutput: DefaultTextFormatter.ColorOutput, ColorOnlyStdout: DefaultTextFormatter.ColorOnlyStdout, } } func (f *Formatter) SetFormat(format string) *Formatter { f.Format = format return f } func (f *Formatter) SetMessageSeparator(separator string) *Formatter { f.MessageSeparator = separator return f } func (f *Formatter) SetTimeStampFormat(format string) *Formatter { f.TimeStampFormat = format return f } func (f *Formatter) SetTraceBackFormat(format string) *Formatter { f.TraceBackFormat = format return f } func (f *Formatter) SetTraceBackSeparator(separator string) *Formatter { f.TraceBackSeparator = separator return f } func (f *Formatter) SetColorOutput(color bool) *Formatter { f.ColorOutput = color return f } func (f *Formatter) SetColorOnlyStdout(only bool) *Formatter { f.ColorOnlyStdout = only return f } func (f *Formatter) FormatMessage(level LogLevel, prefix string, tb []*MethodTraceback, messages ...any) string { if f == nil { return fmt.Sprint(messages...) } output := f.Format output = strings.ReplaceAll(output, "%t", f.FormatTime(time.Now())) output = strings.ReplaceAll(output, "%N", prefix) output = strings.ReplaceAll(output, "%l", level.GetName()) output = strings.ReplaceAll(output, "%L", strings.ToUpper(level.GetName())) if len(tb) > 0 { formattedTraceback := f.FormatTraceback(tb[0]) output = strings.ReplaceAll(output, "%b", formattedTraceback) formattedTraceback = f.FormatTracebacks(tb) output = strings.ReplaceAll(output, "%B", formattedTraceback) } else { output = strings.ReplaceAll(output, "%b", "") output = strings.ReplaceAll(output, "%B", "") } if len(tb) > 0 { output = strings.ReplaceAll(output, "%M", tb[0].Method) output = strings.ReplaceAll(output, "%f", tb[0].Filename) output = strings.ReplaceAll(output, "%n", fmt.Sprintf("%d", tb[0].Line)) output = strings.ReplaceAll(output, "%s", tb[0].Signature) } else { output = strings.ReplaceAll(output, "%M", "") output = strings.ReplaceAll(output, "%f", "") output = strings.ReplaceAll(output, "%n", "") output = strings.ReplaceAll(output, "%s", "") } if f.JSONTraceBackPath && len(tb) > 0 { output = strings.ReplaceAll(output, "%p", tb[0].FullPath) } else { output = strings.ReplaceAll(output, "%p", "") } message := Map(messages, func(m any) string { return fmt.Sprint(m) }) output = strings.ReplaceAll(output, "%m", strings.Join(message, f.MessageSeparator)) return output } func (f *Formatter) ColorizeString(s string, level LogLevel) string { return NewColorStringBuilder(). AddFgColor(level.fg).AddBgColor(level.bg). AddText(s).AddReset().String() } func (f *Formatter) FormatTime(t time.Time) string { if f.TimeStampFormat == "" { return t.Format(time.RFC3339) } return t.Format(StrftimeToGo(f.TimeStampFormat)) } func (f *Formatter) FormatTraceback(traceback *MethodTraceback) string { formattedTraceback := f.TraceBackFormat if f.JSONTraceBackPath { formattedTraceback = strings.ReplaceAll(formattedTraceback, "%p", traceback.FullPath) } else { formattedTraceback = strings.ReplaceAll(formattedTraceback, "%p", "") } formattedTraceback = strings.ReplaceAll(formattedTraceback, "%M", traceback.Method) formattedTraceback = strings.ReplaceAll(formattedTraceback, "%f", traceback.Filename) formattedTraceback = strings.ReplaceAll(formattedTraceback, "%n", fmt.Sprintf("%d", traceback.Line)) formattedTraceback = strings.ReplaceAll(formattedTraceback, "%s", traceback.Signature) return formattedTraceback } func (f *Formatter) FormatTracebacks(traceback []*MethodTraceback) string { var formattedTraceback []string for _, frame := range traceback { formattedTraceback = append(formattedTraceback, f.FormatTraceback(frame)) } return strings.Join(formattedTraceback, f.TraceBackSeparator) }