package reggol
type BlockFn = func(string) string
type Block struct {
Text string
Fn BlockFn
}
func NewBlock(text string, fn BlockFn) Block {
return Block{Text: text, Fn: fn}
}
func (b *Block) Value() string {
if b.Fn != nil {
return (b.Fn)(b.Text)
}
return b.Text
}
type Blocks []Block
func (bb *Blocks) Add(value string) *Blocks {
return bb.AddBlock(Block{Text: value})
}
func (bb *Blocks) AddBlock(block Block) *Blocks {
*bb = append(*bb, block)
return bb
}
package reggol
import (
"fmt"
"gh.tarampamp.am/colors"
)
const (
ColorFgBlack colors.TextStyle = 1 << iota // Black text color
ColorFgRed // Red text color
ColorFgGreen // Green text color
ColorFgYellow // Yellow text color
ColorFgBlue // Blue text color
ColorFgMagenta // Magenta text color
ColorFgCyan // Cyan text color
ColorFgWhite // White text color
ColorFgDefault // Default text color
ColorFgBright // Bright text color, usage example: (FgRed | FgBright).Wrap("hello world")
ColorBgBlack // Black background color
ColorBgRed // Red background color
ColorBgGreen // Green background color
ColorBgYellow // Yellow background color
ColorBgBlue // Blue background color
ColorBgMagenta // Magenta background color
ColorBgCyan // Cyan background color
ColorBgWhite // White background color
ColorBgDefault // Default background color
ColorBgBright // Bright background color, usage example: (BgRed | BgBright).Wrap("hello world")
ColorBold // Bold text
ColorFaint // Faint text
ColorItalic // Italic text
ColorUnderline // Underline text
ColorBlinking // Blinking text
ColorReverse // Reverse text
ColorInvisible // Invisible text
ColorStrike // Strike text
ColorReset // Reset text style
)
func colorize(s interface{}, c colors.TextStyle, disabled bool) string {
if c == 0 {
disabled = true
}
str := fmt.Sprintf("%s", s)
if disabled {
return str
}
return c.Wrap(str)
}
func SetColor(s interface{}, c colors.TextStyle, disabled bool) string {
return colorize(s, c, disabled)
}
package reggol
import (
"bytes"
"io"
"os"
"sync"
)
var (
//nolint:gochecknoglobals
consoleBufPool = sync.Pool{
New: func() interface{} {
//nolint:gomnd
return bytes.NewBuffer(make([]byte, 0, 100))
},
}
)
type ConsoleWriter struct {
// Out is the output destination.
Out io.Writer
Trans Transformer
}
// NewConsoleWriter creates and initializes a new ConsoleWriter.
func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter {
w := ConsoleWriter{
Out: os.Stdout,
}
for _, opt := range options {
opt(&w)
}
if w.Trans == nil {
w.Trans = NewConsoleTransformer(false, ``)
}
// Fix color on Windows
// if w.Out == os.Stdout || w.Out == os.Stderr {
// w.Out = colorable.NewColorable(w.Out.(*os.File))
// }
return w
}
func (w ConsoleWriter) Transformer() Transformer {
return w.Trans
}
func (w ConsoleWriter) WithTransformer(trans Transformer) ConsoleWriter {
w.Trans = trans
return w
}
func (w ConsoleWriter) Write(p []byte) (n int, err error) {
//nolint:forcetypeassert
var buf = consoleBufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
consoleBufPool.Put(buf)
}()
buf.Write(p)
err = buf.WriteByte('\n')
if err != nil {
return n, err
}
_, err = buf.WriteTo(w.Out)
return len(p), err
}
func (w ConsoleWriter) Close() error {
if closer, ok := w.Out.(io.Closer); ok {
return closer.Close()
}
return nil
}
package reggol
var (
ErrorFieldName = `error`
TimestampFieldName = `ts`
LevelFieldName = `level`
MessageFieldName = `message`
ErrorMarshalFunc = func(err error) interface{} {
return err
}
ErrorHandler func(err error)
)
package reggol
import (
"errors"
"fmt"
"net"
"os"
"sync"
"time"
)
//nolint:gochecknoglobals
var eventPool = &sync.Pool{
New: func() interface{} {
return &Event{
data: EventData{
fields: make(Fields),
},
}
},
}
type EventData struct {
level Level
ts time.Time
fields Fields
blocks Blocks
err error
message string
}
type Event struct {
w LevelWriter
data EventData
doneFn func(msg string)
// ctx context.Context // Optional Go context for event
}
func newEvent(w LevelWriter, level Level) *Event {
//nolint:forcetypeassert
e := eventPool.Get().(*Event)
e.w = w
e.data.ts = time.Now()
e.data.level = level
e.data.err = nil
e.data.blocks = nil
clearMap(e.data.fields)
// e.data = newEventData(level)
return e
}
func newEventData(level Level) EventData {
return EventData{
level: level,
ts: time.Now(),
fields: make(Fields),
}
}
func (e *Event) write() (err error) {
if e == nil {
return nil
}
if e.data.level != Disabled {
if e.w != nil {
_, err = e.w.WriteLevel(e.data)
}
}
putEvent(e)
return
}
func (e *Event) Enabled() bool {
return e != nil && e.data.level != Disabled
}
func (e *Event) Discard() *Event {
if e == nil {
return e
}
e.data.level = Disabled
return nil
}
func (e *Event) Block(block Block) *Event {
if e == nil {
return e
}
e.data.blocks.AddBlock(block)
return e
}
func (e *Event) BlockText(msg string) *Event {
if e == nil {
return e
}
e.data.blocks.Add(msg)
return e
}
func (e *Event) Blocks(msgs ...string) *Event {
if e == nil {
return e
}
for _, msg := range msgs {
e.data.blocks.Add(msg)
}
return e
}
func (e *Event) Msg(msg string) {
if e == nil {
return
}
e.msg(msg)
}
func (e *Event) Msgf(format string, v ...interface{}) {
if e == nil {
return
}
e.msg(fmt.Sprintf(format, v...))
}
func (e *Event) msg(msg string) {
e.data.message = msg
if e.doneFn != nil {
defer e.doneFn(msg)
}
if err := e.write(); err != nil {
if ErrorHandler != nil {
ErrorHandler(err)
} else {
fmt.Fprintf(os.Stderr, "reggol: could not write event: %v\n", err)
}
}
}
func (e *Event) Push() {
if e == nil {
return
}
e.msg("")
}
//nolint:wsl
func putEvent(e *Event) {
// Proper usage of a sync.Pool requires each entry to have approximately
// the same memory cost. To obtain this property when the stored type
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
// to place back in the pool.
// todo
// See https://golang.org/issue/23199
// const maxSize = 1 << 16 // 64KiB
// if cap(e.buf) > maxSize {
// return
// }
eventPool.Put(e)
}
func (e *Event) addField(key string, val any) *Event {
if e == nil {
return e
}
e.data.fields.Add(key, val)
return e
}
// Str adds the field key with val as a string to the *Event context.
func (e *Event) Str(key, val string) *Event {
return e.addField(key, val)
}
func (e *Event) Int(key string, val int) *Event {
return e.addField(key, val)
}
func (e *Event) Bool(key string, val bool) *Event {
return e.addField(key, val)
}
func (e *Event) Bytes(key string, val []byte) *Event {
return e.addField(key, val)
}
func (e *Event) Time(key string, val time.Time) *Event {
return e.addField(key, val)
}
func (e *Event) Err(err error) *Event {
if e == nil {
return e
}
return e.AnErr(ErrorFieldName, err)
}
func (e *Event) IPAddr(key string, ip net.IP) *Event {
return e.addField(key, ip)
}
func (e *Event) AnErr(key string, err error) *Event {
if e == nil {
return e
}
switch m := ErrorMarshalFunc(err).(type) {
case nil:
return e
case error:
if m == nil || isNilValue(m) {
return e
} else {
// todo
e.data.err = m
return e
}
case string:
e.data.err = errors.New(m)
return e
default:
return e.Interface(key, m)
}
}
func (e *Event) Interface(key string, i interface{}) *Event {
if e == nil {
return e
}
// todo interface to JSON
return e
}
package reggol
type Fields map[string]any
func (f *Fields) Add(key string, value any) *Fields {
(*f)[key] = value
return f
}
package reggol
import "sync/atomic"
var (
ExitCode = 1
gLevel = new(int32)
)
// SetGlobalLevel sets the global override for log level. If this
// values is raised, all Loggers will use at least this value.
//
// To globally disable logs, set GlobalLevel to Disabled.
func SetGlobalLevel(l Level) {
atomic.StoreInt32(gLevel, int32(l))
}
// GlobalLevel returns the current global log level.
func GlobalLevel() Level {
return Level(atomic.LoadInt32(gLevel))
}
package reggol
import (
"errors"
"fmt"
"strconv"
"strings"
"gh.tarampamp.am/colors"
)
// Level defines log levels.
type Level int8
var (
// LevelTraceValue is the value used for the trace level field.
LevelTraceValue = "trace"
// LevelDebugValue is the value used for the debug level field.
LevelDebugValue = "debug"
// LevelInfoValue is the value used for the info level field.
LevelInfoValue = "info"
// LevelWarnValue is the value used for the warn level field.
LevelWarnValue = "warn"
// LevelErrorValue is the value used for the error level field.
LevelErrorValue = "error"
// LevelFatalValue is the value used for the fatal level field.
LevelFatalValue = "fatal"
// LevelPanicValue is the value used for the panic level field.
LevelPanicValue = "panic"
LevelFieldMarshalFunc = func(l Level) string {
return l.String()
}
// (colors.FgYellow | colors.Bold))
// LevelColors are used by ConsoleWriter's consoleDefaultFormatLevel to color
// log levels.
LevelColors = map[Level]colors.TextStyle{
TraceLevel: ColorFgBlue,
DebugLevel: 0,
InfoLevel: ColorFgGreen, // colorGreen,
WarnLevel: ColorFgYellow, // colorYellow,
ErrorLevel: ColorFgRed, // colorRed,
FatalLevel: ColorFgRed, // colorRed,
PanicLevel: ColorFgRed, // colorRed,
}
// FormattedLevels are used by ConsoleWriter's consoleDefaultFormatLevel
// for a short level name.
FormattedLevels = map[Level]string{
TraceLevel: "TRC",
DebugLevel: "DBG",
InfoLevel: "INF",
WarnLevel: "WRN",
ErrorLevel: "ERR",
FatalLevel: "FTL",
PanicLevel: "PNC",
}
)
const (
// DebugLevel defines debug log level.
DebugLevel Level = iota
// InfoLevel defines info log level.
InfoLevel
// WarnLevel defines warn log level.
WarnLevel
// ErrorLevel defines error log level.
ErrorLevel
// FatalLevel defines fatal log level.
FatalLevel
// PanicLevel defines panic log level.
PanicLevel
// NoLevel defines an absent log level.
NoLevel
// Disabled disables the logger.
Disabled
// TraceLevel defines trace log level.
TraceLevel Level = -1
// Values less than TraceLevel are handled as numbers.
)
func (l Level) String() string {
switch l {
case TraceLevel:
return LevelTraceValue
case DebugLevel:
return LevelDebugValue
case InfoLevel:
return LevelInfoValue
case WarnLevel:
return LevelWarnValue
case ErrorLevel:
return LevelErrorValue
case FatalLevel:
return LevelFatalValue
case PanicLevel:
return LevelPanicValue
case Disabled:
return "disabled"
case NoLevel:
return ""
}
return strconv.Itoa(int(l))
}
// ParseLevel converts a level string into a zerolog Level value.
// returns an error if the input string does not match known values.
func ParseLevel(levelStr string) (Level, error) {
switch {
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(TraceLevel)):
return TraceLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(DebugLevel)):
return DebugLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(InfoLevel)):
return InfoLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(WarnLevel)):
return WarnLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(ErrorLevel)):
return ErrorLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(FatalLevel)):
return FatalLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(PanicLevel)):
return PanicLevel, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(Disabled)):
return Disabled, nil
case strings.EqualFold(levelStr, LevelFieldMarshalFunc(NoLevel)):
return NoLevel, nil
}
i, err := strconv.Atoi(levelStr)
if err != nil {
return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr)
}
if i > 127 || i < -128 {
return NoLevel, fmt.Errorf("Out-Of-Bounds Level: '%d', defaulting to NoLevel", i)
}
return Level(i), nil
}
// UnmarshalText implements encoding.TextUnmarshaler to allow for easy reading from toml/yaml/json formats.
func (l *Level) UnmarshalText(text []byte) error {
if l == nil {
return errors.New("can't unmarshal a nil *Level")
}
var err error
*l, err = ParseLevel(string(text))
return err
}
// MarshalText implements encoding.TextMarshaler to allow for easy writing into toml/yaml/json formats.
func (l Level) MarshalText() ([]byte, error) {
return []byte(LevelFieldMarshalFunc(l)), nil
}
package reggol
import (
"fmt"
"io"
"os"
)
type Logger struct {
w LevelWriter
// context []byte
// ctx context.Context
level Level
}
func New(w io.Writer) Logger {
if w == nil {
w = io.Discard
}
lw, ok := w.(LevelWriter)
if !ok {
writer, ok := w.(TransformWriter)
if !ok {
writer = TransformWriterAdapter{w, NewTextTransformer(``)}
}
lw = LevelWriterAdapter{writer}
}
return Logger{w: lw, level: TraceLevel}
}
// Nop returns a disabled logger for which all operation are no-op.
func Nop() Logger {
return New(nil).Level(Disabled)
}
// Level creates a child logger with the minimum accepted level set to level.
func (l Logger) Level(lvl Level) Logger {
l.level = lvl
return l
}
// GetLevel returns the current Level of l.
func (l Logger) GetLevel() Level {
return l.level
}
func (l Logger) Write(p []byte) (n int, err error) {
n = len(p)
if n > 0 && p[n-1] == '\n' {
// Trim CR added by stdlog.
p = p[0 : n-1]
}
l.Log().Msg(string(p))
return
}
func (l *Logger) newEvent(level Level, doneFn func(string)) *Event {
enabled := l.should(level)
if !enabled {
if doneFn != nil {
doneFn("")
}
return nil
}
e := newEvent(l.w, level)
e.doneFn = doneFn
// e.ctx = l.ctx
// if l.context != nil && len(l.context) > 1 {
// e.buf = enc.AppendObjectData(e.buf, l.context)
// }
// if l.stack {
// e.Stack()
// }
return e
}
// should returns true if the log event should be logged.
func (l *Logger) should(lvl Level) bool {
if l.w == nil {
return false
}
if lvl < l.level || lvl < GlobalLevel() {
return false
}
return true
}
// //
func (l *Logger) Log() *Event {
return l.newEvent(NoLevel, nil)
}
func (l *Logger) Trace() *Event {
return l.newEvent(TraceLevel, nil)
}
func (l *Logger) Debug() *Event {
return l.newEvent(DebugLevel, nil)
}
func (l *Logger) Warn() *Event {
return l.newEvent(WarnLevel, nil)
}
func (l *Logger) Info() *Event {
return l.newEvent(InfoLevel, nil)
}
func (l *Logger) Error() *Event {
return l.newEvent(ErrorLevel, nil)
}
func (l *Logger) Err(err error) *Event {
if err != nil {
return l.Error().Err(err)
}
return l.Info()
}
func (l *Logger) Fatal() *Event {
return l.newEvent(FatalLevel, func(msg string) {
if closer, ok := l.w.(io.Closer); ok {
// Close the writer to flush any buffered message. Otherwise the message
// will be lost as os.Exit() terminates the program immediately.
closer.Close()
}
os.Exit(ExitCode)
})
}
func (l *Logger) Panic() *Event {
return l.newEvent(PanicLevel, func(msg string) { panic(msg) })
}
func (l *Logger) WithLevel(level Level) *Event {
switch level {
case TraceLevel:
return l.Trace()
case DebugLevel:
return l.Debug()
case InfoLevel:
return l.Info()
case WarnLevel:
return l.Warn()
case ErrorLevel:
return l.Error()
case FatalLevel:
return l.newEvent(FatalLevel, nil)
case PanicLevel:
return l.newEvent(PanicLevel, nil)
case NoLevel:
return l.Log()
case Disabled:
return nil
default:
return l.newEvent(level, nil)
}
}
func (l *Logger) Print(v ...interface{}) {
if e := l.Debug(); e.Enabled() {
e.Msg(fmt.Sprint(v...))
}
}
func (l *Logger) Printf(format string, v ...interface{}) {
if e := l.Debug(); e.Enabled() {
e.Msg(fmt.Sprintf(format, v...))
}
}
func (l *Logger) Println(v ...interface{}) {
if e := l.Debug(); e.Enabled() {
e.Msg(fmt.Sprintln(v...))
}
}
package log
import (
"fmt"
"os"
"github.com/efureev/reggol"
)
// Logger is the global logger.
//
//nolint:gochecknoglobals
var Logger = reggol.New(os.Stderr)
// Level creates a child logger with the minimum accepted level set to level.
func Level(level reggol.Level) reggol.Logger {
return Logger.Level(level)
}
// Err starts a new message with error level with err as a field if not nil or
// with info level if err is nil.
//
// You must call Msg on the returned event in order to send the event.
func Err(err error) *reggol.Event {
return Logger.Err(err)
}
// Trace starts a new message with trace level.
//
// You must call Msg on the returned event in order to send the event.
func Trace() *reggol.Event {
return Logger.Trace()
}
// Debug starts a new message with debug level.
//
// You must call Msg on the returned event in order to send the event.
func Debug() *reggol.Event {
return Logger.Debug()
}
// Info starts a new message with info level.
//
// You must call Msg on the returned event in order to send the event.
func Info() *reggol.Event {
return Logger.Info()
}
// Warn starts a new message with warn level.
//
// You must call Msg on the returned event in order to send the event.
func Warn() *reggol.Event {
return Logger.Warn()
}
// Error starts a new message with error level.
//
// You must call Msg on the returned event in order to send the event.
func Error() *reggol.Event {
return Logger.Error()
}
// Fatal starts a new message with fatal level. The os.Exit(1) function
// is called by the Msg method.
//
// You must call Msg on the returned event in order to send the event.
func Fatal() *reggol.Event {
return Logger.Fatal()
}
// Panic starts a new message with panic level. The message is also sent
// to the panic function.
//
// You must call Msg on the returned event in order to send the event.
func Panic() *reggol.Event {
return Logger.Panic()
}
// WithLevel starts a new message with level.
//
// You must call Msg on the returned event in order to send the event.
func WithLevel(level reggol.Level) *reggol.Event {
return Logger.WithLevel(level)
}
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
// zerolog.Disabled will still disable events produced by this method.
//
// You must call Msg on the returned event in order to send the event.
func Log() *reggol.Event {
return Logger.Log()
}
// Print sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Print.
func Print(v ...interface{}) {
Logger.Debug().Msg(fmt.Sprint(v...))
}
// Printf sends a log event using debug level and no extra field.
// Arguments are handled in the manner of fmt.Printf.
func Printf(format string, v ...interface{}) {
Logger.Debug().Msgf(format, v...)
}
package reggol
import (
"fmt"
"strings"
"time"
)
const (
defaultTimeFormat = time.Kitchen
TimeFieldFormat = time.RFC3339
)
type Formatter func(interface{}) string
type Transformer interface {
Transform(data EventData) []byte
}
// AbstractTransformer is a abstract transformer for plain text.
type AbstractTransformer struct {
displayTimestamp bool
displayLevel bool
fieldsDelimiter string
timeFormat string
FormatLevelFn Formatter
FormatTimestampFn Formatter
FormatFieldFn Formatter
FormatFieldNameFn Formatter
FormatFieldValueFn Formatter
FormatErrorFn Formatter
FormatMessageFn Formatter
BeforeTransformFn func(data EventData)
AfterTransformFn func(data EventData)
}
func (st *AbstractTransformer) HideLevel() {
st.displayLevel = false
}
func (st *AbstractTransformer) HideTimestamp() {
st.displayTimestamp = false
}
func (st AbstractTransformer) formatError(err error) string {
if st.FormatErrorFn != nil {
return (st.FormatErrorFn)(err)
}
return err.Error()
}
func (st AbstractTransformer) formatLevel(lvl Level) string {
if st.FormatLevelFn != nil {
return (st.FormatLevelFn)(lvl)
}
var l string
fl, ok := FormattedLevels[lvl]
if ok {
l = fl
} else {
l = "???"
}
return l
}
func (st AbstractTransformer) formatTimestamp(ts time.Time) string {
if st.FormatTimestampFn != nil {
return (st.FormatTimestampFn)(ts)
}
if st.timeFormat == "" {
st.timeFormat = defaultTimeFormat
}
return ts.Local().Format(st.timeFormat)
}
func (st AbstractTransformer) formatMessage(msg string) string {
if st.FormatMessageFn != nil {
return (st.FormatMessageFn)(msg)
}
return msg
}
func (st AbstractTransformer) formatBlocks(blocks Blocks) string {
list := make([]string, len(blocks))
for i, block := range blocks {
list[i] = block.Value()
}
return `blocks=[` + strings.Join(list, `, `) + `]`
}
func (st AbstractTransformer) formatField(name string, value any) string {
if st.FormatFieldFn != nil {
return (st.FormatFieldFn)([2]string{st.formatFieldName(name), st.formatFieldValue(value)})
}
return fmt.Sprintf(`%s=%s`, st.formatFieldName(name), st.formatFieldValue(value))
}
func (st AbstractTransformer) formatFieldName(name string) string {
if st.FormatFieldNameFn != nil {
return (st.FormatFieldNameFn)(name)
}
return name
}
func (st AbstractTransformer) formatFieldValue(i any) string {
if st.FormatFieldValueFn != nil {
return (st.FormatFieldValueFn)(i)
}
switch i.(type) {
case int, int8, int16, int32, int64:
return fmt.Sprintf("%d", i)
case string:
return fmt.Sprintf("%s", i)
default:
return fmt.Sprintf("%v", i)
}
}
package reggol
import (
"bytes"
"sort"
"strings"
"time"
"gh.tarampamp.am/colors"
)
// ConsoleTransformer is a transformer for console text.
type ConsoleTransformer struct {
AbstractTransformer
noColor bool
}
func NewConsoleTransformer(noColor bool, timeFormat string) ConsoleTransformer {
return ConsoleTransformer{
AbstractTransformer: AbstractTransformer{
timeFormat: timeFormat,
fieldsDelimiter: ` `,
displayTimestamp: true,
displayLevel: true,
},
noColor: noColor,
}
}
func (ct ConsoleTransformer) IsNoColor() bool {
return ct.noColor
}
func (ct ConsoleTransformer) formatTimestamp(ts time.Time) string {
val := ct.AbstractTransformer.formatTimestamp(ts)
return colorize(val, ColorFgBlack|ColorFgBright, ct.noColor)
}
func (ct ConsoleTransformer) formatLevel(lvl Level) string {
val := ct.AbstractTransformer.formatLevel(lvl)
return colorize(val, LevelColors[lvl]|colors.Bold, ct.noColor)
}
func (ct ConsoleTransformer) formatMessage(msg string) string {
return ct.AbstractTransformer.formatMessage(msg)
}
func (ct ConsoleTransformer) formatBlocks(blocks Blocks) string {
list := make([]string, len(blocks))
for i, block := range blocks {
list[i] = block.Value()
}
return strings.Join(list, ` `)
}
func (ct ConsoleTransformer) formatField(name string, value any) string {
return ct.AbstractTransformer.formatField(name, value)
}
func (ct ConsoleTransformer) formatError(err error) string {
val := ct.AbstractTransformer.formatError(err)
return colorize(val, LevelColors[ErrorLevel], ct.noColor)
}
func (ct ConsoleTransformer) Transform(data EventData) []byte {
if ct.AbstractTransformer.BeforeTransformFn != nil {
(ct.AbstractTransformer.BeforeTransformFn)(data)
}
//nolint:prealloc
var list []string
// timestamp
if ct.displayTimestamp {
list = append(list, ct.formatTimestamp(data.ts))
}
// level
if ct.displayLevel && data.level != NoLevel {
list = append(list, ct.formatLevel(data.level))
}
// blocks
if data.blocks != nil || len(data.blocks) > 0 {
list = append(list, ct.formatBlocks(data.blocks))
}
// Error
if data.err != nil {
list = append(list, ct.formatError(data.err))
} else if data.message != `` {
// Message
list = append(list, ct.formatMessage(data.message))
}
// fields
keys := make([]string, 0, len(data.fields))
for k := range data.fields {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
list = append(list, ct.formatField(k, data.fields[k]))
}
b := bytes.Buffer{}
lastIdx := len(list) - 1
for i, item := range list {
b.WriteString(item)
if i != lastIdx {
b.WriteString(ct.fieldsDelimiter)
}
}
if ct.AbstractTransformer.AfterTransformFn != nil {
(ct.AbstractTransformer.AfterTransformFn)(data)
}
return b.Bytes()
}
package reggol
import (
"bytes"
"fmt"
"sort"
)
// TextTransformer is a simple transformer for plain text.
type TextTransformer struct {
AbstractTransformer
}
func NewTextTransformer(timeFormat string) TextTransformer {
return TextTransformer{
AbstractTransformer{
timeFormat: timeFormat,
fieldsDelimiter: `, `,
displayTimestamp: true,
displayLevel: true,
},
}
}
func (tt TextTransformer) formatLevel(lvl Level) string {
return lvl.String()
}
func (tt TextTransformer) Transform(data EventData) []byte {
//nolint:prealloc
var list []string
// timestamp
if tt.displayTimestamp {
list = append(list, fmt.Sprintf(`%s=%s`, tt.formatFieldName(TimestampFieldName), tt.formatTimestamp(data.ts)))
}
// level
if tt.displayLevel {
list = append(list, fmt.Sprintf(`%s=%s`, tt.formatFieldName(LevelFieldName), tt.formatLevel(data.level)))
}
// Error
if data.err != nil {
list = append(list, fmt.Sprintf(`%s=%s`, tt.formatFieldName(ErrorFieldName), tt.formatError(data.err)))
} else if data.message != `` {
// Message
list = append(list, fmt.Sprintf(`%s=%s`, tt.formatFieldName(MessageFieldName), tt.formatMessage(data.message)))
}
// fields
keys := make([]string, 0, len(data.fields))
for k := range data.fields {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
list = append(list, fmt.Sprintf(`%s=%s`, tt.formatFieldName(k), tt.formatFieldValue(data.fields[k])))
}
b := bytes.Buffer{}
lastIdx := len(list) - 1
for i, item := range list {
b.WriteString(item)
if i != lastIdx {
b.WriteString(tt.fieldsDelimiter)
}
}
return b.Bytes()
}
package reggol
import "unsafe"
func isNilValue(i interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0
}
//go:build go1.21
package reggol
func clearMap(m Fields) {
clear(m)
}
package reggol
import (
"bytes"
"io"
)
type TransformWriter interface {
io.Writer
Transformer() Transformer
}
type TransformWriterAdapter struct {
io.Writer
Trans Transformer
}
func (lw TransformWriterAdapter) Transformer() Transformer {
return lw.Trans
}
func (lw TransformWriterAdapter) Write(p []byte) (n int, err error) {
buf := bytes.Buffer{}
buf.Write(p)
err = buf.WriteByte('\n')
if err != nil {
return n, err
}
return lw.Writer.Write(buf.Bytes())
}
type LevelWriter interface {
TransformWriter
WriteLevel(data EventData) (n int, err error)
}
// LevelWriterAdapter adapts an io.Writer to support the LevelWriter interface.
type LevelWriterAdapter struct {
TransformWriter
}
// WriteLevel simply writes everything to the adapted writer, ignoring the level.
func (lw LevelWriterAdapter) WriteLevel(data EventData) (n int, err error) {
return lw.Write(lw.Transformer().Transform(data))
}
// Call the underlying writer's Close method if it is an io.Closer. Otherwise does nothing.
func (lw LevelWriterAdapter) Close() error {
if closer, ok := lw.TransformWriter.(io.Closer); ok {
return closer.Close()
}
return nil
}