247 lines
4.9 KiB
Go
247 lines
4.9 KiB
Go
|
package logie
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
printFmt = "%s%s %s\n"
|
||
|
escape byte = 0x1B
|
||
|
csi byte = '['
|
||
|
separator byte = ';'
|
||
|
csiEnd byte = 'm'
|
||
|
fgColor byte = '3'
|
||
|
bgColor byte = '4'
|
||
|
reset byte = '0'
|
||
|
)
|
||
|
|
||
|
type Maybe[T any] struct {
|
||
|
exists bool
|
||
|
value T
|
||
|
}
|
||
|
|
||
|
func Exists[T any](t T) Maybe[T] {
|
||
|
return Maybe[T]{
|
||
|
exists: true,
|
||
|
value: t,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Empty[T any]() Maybe[T] {
|
||
|
return Maybe[T]{}
|
||
|
}
|
||
|
|
||
|
func (m Maybe[T]) Exists() bool {
|
||
|
return m.exists
|
||
|
}
|
||
|
|
||
|
func (m Maybe[T]) Value() T {
|
||
|
return m.value
|
||
|
}
|
||
|
|
||
|
type Color uint8
|
||
|
|
||
|
const (
|
||
|
ColorBlack = '0'
|
||
|
ColorRed = '1'
|
||
|
ColorGreen = '2'
|
||
|
ColorYellow = '3'
|
||
|
ColorBlue = '4'
|
||
|
ColorMagenta = '5'
|
||
|
ColorCyan = '6'
|
||
|
ColorWhite = '7'
|
||
|
)
|
||
|
|
||
|
type ColorSet struct {
|
||
|
fg Maybe[Color]
|
||
|
bg Maybe[Color]
|
||
|
font Maybe[Font]
|
||
|
}
|
||
|
|
||
|
func Colors() ColorSet {
|
||
|
return ColorSet{}
|
||
|
}
|
||
|
|
||
|
func (c ColorSet) Foreground(fg Color) ColorSet {
|
||
|
c.fg = Exists(fg)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c ColorSet) Background(bg Color) ColorSet {
|
||
|
c.bg = Exists(bg)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c ColorSet) Font(f Font) ColorSet {
|
||
|
c.font = Exists(f)
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func (c ColorSet) String(v string) string {
|
||
|
var hasFg, hasBg, hasFont = c.fg.Exists(), c.bg.Exists(), c.font.Exists()
|
||
|
if !hasFg && !hasBg && !hasFont {
|
||
|
return v
|
||
|
}
|
||
|
// Build format
|
||
|
formatBytes := []byte{escape, csi}
|
||
|
if hasFg {
|
||
|
formatBytes = append(formatBytes, fgColor, byte(c.fg.Value()))
|
||
|
}
|
||
|
if hasBg {
|
||
|
if len(formatBytes) > 2 {
|
||
|
formatBytes = append(formatBytes, separator)
|
||
|
}
|
||
|
formatBytes = append(formatBytes, bgColor, byte(c.bg.Value()))
|
||
|
}
|
||
|
if hasFont {
|
||
|
if len(formatBytes) > 2 {
|
||
|
formatBytes = append(formatBytes, separator)
|
||
|
}
|
||
|
formatBytes = append(formatBytes, byte(c.font.Value()))
|
||
|
}
|
||
|
formatBytes = append(formatBytes, csiEnd)
|
||
|
formatBytes = append(append(formatBytes, []byte(v)...), escape, csi, reset, csiEnd)
|
||
|
return string(formatBytes)
|
||
|
}
|
||
|
|
||
|
type Font uint8
|
||
|
|
||
|
const (
|
||
|
FontBold = '1'
|
||
|
FontFaint = '2'
|
||
|
FontUnderlined = '4'
|
||
|
FontSlowBlink = '5'
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
info = Colors().Font(FontFaint).String("[INFO]")
|
||
|
warn = Colors().Background(ColorYellow).String("[WARN]")
|
||
|
error_ = Colors().Foreground(ColorWhite).Background(ColorRed).Font(FontBold).String("[ERROR]")
|
||
|
fatal = Colors().Foreground(ColorRed).Background(ColorYellow).Font(FontBold).String("[FATAL]")
|
||
|
)
|
||
|
|
||
|
type Log struct {
|
||
|
output *os.File
|
||
|
timeFmt string
|
||
|
color map[int]ColorSet
|
||
|
timeColor ColorSet
|
||
|
}
|
||
|
|
||
|
func New() Log {
|
||
|
return Log{
|
||
|
output: os.Stdout,
|
||
|
timeFmt: time.RFC822,
|
||
|
timeColor: Colors().Font(FontFaint),
|
||
|
color: map[int]ColorSet{},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (l Log) Clone() Log {
|
||
|
colorClone := map[int]ColorSet{}
|
||
|
for k, cs := range l.color {
|
||
|
colorClone[k] = cs
|
||
|
}
|
||
|
l.color = colorClone
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
// ColorBracket returns a Log with the coloring information for the bracket index.
|
||
|
//
|
||
|
// The way coloring works in Logie is that you can only color the parts of your
|
||
|
// message that are within closed brackets `[]` in its format string
|
||
|
// (therefore non-f calls cannot color). The [index] being passed in is a zero-indexed
|
||
|
// index of which bracket set to color.
|
||
|
func (l Log) ColorBracket(index int, set ColorSet) Log {
|
||
|
l = l.Clone()
|
||
|
l.color[index] = set
|
||
|
|
||
|
return l
|
||
|
}
|
||
|
|
||
|
func (l Log) time() string {
|
||
|
return l.timeColor.String("[" + time.Now().Format(l.timeFmt) + "]")
|
||
|
}
|
||
|
|
||
|
func (l Log) Logf(level, format string, args ...any) {
|
||
|
line := fmt.Sprintf(colorize(format, l.color), args...)
|
||
|
fmt.Fprintf(l.output, printFmt, l.time(), level, line)
|
||
|
}
|
||
|
|
||
|
func colorize(format string, colors map[int]ColorSet) string {
|
||
|
var (
|
||
|
parts = []string{}
|
||
|
bracketParts = []int{}
|
||
|
last int
|
||
|
)
|
||
|
outer:
|
||
|
for index := 0; index < len(format); index++ {
|
||
|
if format[index] == '[' {
|
||
|
parts = append(parts, format[last:index])
|
||
|
|
||
|
for inner := index + 1; index < len(format); inner++ {
|
||
|
if format[inner] == ']' {
|
||
|
current := format[index : inner+1]
|
||
|
parts = append(parts, current)
|
||
|
bracketParts = append(bracketParts, len(parts)-1)
|
||
|
last = inner + 1
|
||
|
continue outer
|
||
|
}
|
||
|
}
|
||
|
|
||
|
parts = append(parts, format[index:])
|
||
|
}
|
||
|
}
|
||
|
parts = append(parts, format[last:])
|
||
|
|
||
|
for bracketIndex, color := range colors {
|
||
|
if bracketIndex >= len(bracketParts) {
|
||
|
continue
|
||
|
}
|
||
|
parts[bracketParts[bracketIndex]] = color.String(parts[bracketParts[bracketIndex]])
|
||
|
}
|
||
|
|
||
|
return strings.Join(parts, "")
|
||
|
}
|
||
|
|
||
|
func (l Log) Log(level string, args ...any) {
|
||
|
line := fmt.Sprint(args...)
|
||
|
fmt.Fprintf(l.output, printFmt, l.time(), level, line)
|
||
|
}
|
||
|
|
||
|
func (l Log) Infof(format string, args ...any) {
|
||
|
l.Logf(info, format, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Info(args ...any) {
|
||
|
l.Log(info, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Warnf(format string, args ...any) {
|
||
|
l.Logf(warn, format, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Warn(args ...any) {
|
||
|
l.Log(warn, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Errorf(format string, args ...any) {
|
||
|
l.Logf(error_, format, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Error(args ...any) {
|
||
|
l.Log(error_, args...)
|
||
|
}
|
||
|
|
||
|
func (l Log) Fatalf(format string, args ...any) {
|
||
|
l.Logf(fatal, format, args...)
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
func (l Log) Fatal(args ...any) {
|
||
|
l.Log(fatal, args...)
|
||
|
os.Exit(1)
|
||
|
}
|