tlog

package module
v0.25.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2024 License: MIT Imports: 30 Imported by: 10

README

Documentation Go workflow CircleCI codecov Go Report Card GitHub tag (latest SemVer)

tlog

At least it is a logger, but it is much more than that. It is an observability events system. Event is a log or tracing message, tracing span start or finish, metric value, or anything you need. Tons of work has been done to make it effective yet comfortable to use. The events are encoded in a machine-readable format to be processed in any way, instant or later. Events could be printed as logs, combined to build distributed traces, filtered and sent to an alerting service, processed and analyzed, and more.

tlog is a new way of instrumentation. Log once use smart.

Explore examples and extensions.

Status

The logging API is pretty solid. Now I'm working mostly on backend parts, web interface, integrations.

Quick Start

Logger

tlog.Printf("just like log.Printf")

tlog.Printw("but structured is", "much", "better")

type Req struct {
	Start time.Time
	Path  string
}

tlog.Printw("any value type is", "supported", &Req{Start: time.Now(), Path: "/resource/path"})

l := tlog.New(ioWriter)
l.Printw("yet another logger, seriously?")

Debug Topics Instead of Log Levels

No need to choose between tons of unrelated Debug logs and scant Info logs. Each event can be filtered precisely and filter can be changed at runtime.

tlog.SetVerbosity("rawdb,dump_request")

tlog.V("rawdb").Printw("make db query", "query", query) // V is inspired by glog.V

if tlog.If("dump_request") {
	// some heavy calculations could also be here
	tlog.Printw("full request data", "request", request)
}

if tlog.If("full_token") {
	tlog.Printw("db token", "token", token)
} else {
	tlog.Printw("db token", "token", token.ID)
}

Filtering is very flexible. You can select topics, functions, types, files, packages, topics in locations. You can select all in the file and then unselect some functions, etc.

Traces

Traces are vital if you have simultaneous requests or distributed request propagation. So they integrated into the logger to have the best experience.

func ServeRequest(req *Request) {
	span := tlog.Start("request_root", "client", req.RemoteAddr, "path", req.URL.Path)
	defer span.Finish()

	ctx := tlog.ContextWithSpan(req.Context(), span)

	doc, err := loadFromDB(ctx, req.URL.Path)
	// if err ...

	_ = doc
}

func loadFromDB(ctx context.Context, doc string) (err error) {
	parent := tlog.SpanFromContext(ctx)
	span := parent.V("dbops").Spawn("load_from_db", "doc", doc)
	defer func() {
		span.Finish("err", err) // record result error
	}()

	span.Printw("prepare query")
	// ...

	if dirtyPages > tooMuch {
		// record event to the local span or to the parent if the local was not selected
		span.Or(parent).Printw("too much of dirty pages", "durty_pages", dirtyPages,
			tlog.KeyLogLevel, tlog.Warn)
	}
}

Trace events are the same to log events, except they have IDs. You do not need to add the same data to trace attributes and write them to logs. It's the same!

Data Format

Events are just key-value associative arrays. All keys are optional, any can be added. Some keys have special meaning, like event timestamp or log level. But it's only a convention; representational parts primarily use it: console pretty text formatter moves time to the first column, for example.

The default format is a machine readable CBOR-like binary format. And the logger backend is just io.Writer. Text, JSON, Logfmt converters are provided. Any other can be implemented.

There is also a special compression format: as fast and efficient as snappy yet safe in a sense that each event (or batch write) emits single Write to the file (io.Writer actually).

Performance

Performance was in mind from the very beginning. The idea is to emit as many events as you want and not to pay for that by performance. In a typical efficient application CPU profile, the logger takes only 1-3% of CPU usage with no events economy. Almost all allocations were eliminated. That means less work is done, no garbage collector pressure, and lower memory usage.

Documentation

Index

Constants

View Source
const (
	Ldate = 1 << iota
	Ltime
	Lseconds
	Lmilliseconds
	Lmicroseconds
	Lshortfile
	Llongfile
	Ltypefunc // pkg.(*Type).Func
	Lfuncname // Func
	LUTC
	Lloglevel // log level

	LstdFlags = Ldate | Ltime
	LdetFlags = Ldate | Ltime | Lmicroseconds | Lshortfile | Lloglevel

	Lnone = 0
)
View Source
const (
	WireLabel = tlwire.SemanticTlogBase + iota
	WireID
	WireMessage
	WireEventKind
	WireLogLevel

	SemanticCommunityBase = tlwire.SemanticTlogBase + 10
)

Wire semantic tags.

View Source
const KeyAuto = ""

Variables

View Source
var (
	ResetColor = Color(0)

	DefaultColorScheme = ColorScheme{
		TimeColor:       Color(90),
		TimeChangeColor: Color(38, 5, 244, 1),
		FileColor:       Color(90),
		FuncColor:       Color(90),
		KeyColor:        Color(36),
		LevelColor: struct {
			Info  []byte
			Warn  []byte
			Error []byte
			Fatal []byte
			Debug []byte
		}{
			Info:  Color(90),
			Warn:  Color(31),
			Error: Color(31, 1),
			Fatal: Color(31, 1),
			Debug: Color(90),
		},
	}
)
View Source
var (
	Stdout = os.Stdout
	Stderr = os.Stderr
)
View Source
var (
	KeySpan      = "_s"
	KeyParent    = "_p"
	KeyTimestamp = "_t"
	KeyElapsed   = "_e"
	KeyCaller    = "_c"
	KeyMessage   = "_m"
	KeyEventKind = "_k"
	KeyLogLevel  = "_l"
	KeyRepeated  = "_r"
)

Predefined keys.

View Source
var AutoLabels = map[string]func() interface{}{
	"_hostname":   func() interface{} { return Hostname() },
	"_user":       func() interface{} { return User() },
	"_os":         func() interface{} { return runtime.GOOS },
	"_arch":       func() interface{} { return runtime.GOARCH },
	"_numcpu":     func() interface{} { return runtime.NumCPU() },
	"_gomaxprocs": func() interface{} { return runtime.GOMAXPROCS(0) },
	"_goversion":  func() interface{} { return runtime.Version },
	"_pid": func() interface{} {
		return os.Getpid()
	},
	"_timezone": func() interface{} {
		n, _ := time.Now().Zone()
		return n
	},
	"_execmd5":  func() interface{} { return ExecutableMD5() },
	"_execsha1": func() interface{} { return ExecutableSHA1() },
	"_execname": func() interface{} {
		return filepath.Base(os.Args[0])
	},
	"_randid": func() interface{} {
		return MathRandID().StringFull()
	},
}

AutoLabels is a list of automatically filled labels

_hostname - local hostname
_user - current user
_pid - process pid
_timezone - local timezone code (UTC, MSK)
_goversion - go version
_execmd5 - this binary md5 hash
_execsha1 - this binary sha1 hash
_execname - executable base name (project name)
_randid - random id. May be used to distinguish different runs.

Functions

func AppendKVs

func AppendKVs(e *tlwire.Encoder, b []byte, kvs []interface{}) []byte

func AppendLabels

func AppendLabels(e *tlwire.Encoder, b []byte, kvs []interface{}) []byte

func Color

func Color(c ...int) (r []byte)

func ContextWithSpan

func ContextWithSpan(ctx context.Context, s Span) context.Context

ContextWithSpan creates new context with Span ID context.Value. It returns the same context if id is zero.

func ExecutableMD5

func ExecutableMD5() string

ExecutableMD5 returns current process executable md5 hash. May be useful to find exact executable later.

func ExecutableSHA1

func ExecutableSHA1() string

ExecutableSHA1 returns current process executable sha1 hash. May be useful to find exact executable later.

func Hostname

func Hostname() string

Hostname returns hostname or err.Error().

func If

func If(topics string) bool

func IfDepth

func IfDepth(d int, topics string) bool

func LoggerSetCallers

func LoggerSetCallers(l *Logger, skip int, callers func(skip int, pc []uintptr) int)

func LoggerSetTimeNow

func LoggerSetTimeNow(l *Logger, now func() time.Time, nano func() int64)

func ParseLabels

func ParseLabels(s string) []interface{}

ParseLabels parses comma separated list of labels and fills them with values (See FillLabelsWithDefaults).

func Printf

func Printf(fmt string, args ...interface{})

func Printw

func Printw(msg string, kvs ...interface{})

func RandIDFromReader

func RandIDFromReader(read func(p []byte) (int, error)) func() ID

func SetLabels

func SetLabels(kvs ...interface{})

func SetVerbosity

func SetVerbosity(vfilter string)

func UUID

func UUID(read func(p []byte) (int, error)) func() ID

UUID creates ID generation function. read is a random Read method. Function panics on Read error. read must be safe for concurrent use.

It's got from github.com/google/uuid.

func User

func User() string

User returns current username or err.Error().

func Verbosity added in v0.22.1

func Verbosity() string

Types

type ColorScheme

type ColorScheme struct {
	TimeColor       []byte
	TimeChangeColor []byte // if different from TimeColor
	FileColor       []byte
	FuncColor       []byte
	MessageColor    []byte
	KeyColor        []byte
	ValColor        []byte
	LevelColor      struct {
		Info  []byte
		Warn  []byte
		Error []byte
		Fatal []byte
		Debug []byte
	}
}

type ConsoleWriter

type ConsoleWriter struct {
	io.Writer
	Flags int

	Colorize        bool
	PadEmptyMessage bool
	AllLabels       bool
	AllCallers      bool

	LevelWidth   int
	MessageWidth int
	IDWidth      int
	Shortfile    int
	Funcname     int
	MaxValPad    int

	TimeFormat     string
	TimeLocation   *time.Location
	DurationFormat string
	DurationDiv    time.Duration
	FloatFormat    string
	FloatChar      byte
	FloatPrecision int
	CallerFormat   string
	BytesFormat    string

	StringOnNewLineMinLen int

	PairSeparator string
	KVSeparator   string

	QuoteChars      string
	QuoteAnyValue   bool
	QuoteEmptyValue bool

	ColorScheme
	// contains filtered or unexported fields
}

func NewConsoleWriter

func NewConsoleWriter(w io.Writer, f int) *ConsoleWriter

func (*ConsoleWriter) AppendDuration

func (w *ConsoleWriter) AppendDuration(b []byte, d time.Duration) []byte

func (*ConsoleWriter) ConvertValue

func (w *ConsoleWriter) ConvertValue(b, p []byte, st, ff int) (_ []byte, i int)

func (*ConsoleWriter) Write

func (w *ConsoleWriter) Write(p []byte) (i int, err error)

type EventKind

type EventKind rune
const (
	EventSpanStart  EventKind = 's'
	EventSpanFinish EventKind = 'f'
	EventMetric     EventKind = 'm'
)

Event kinds.

func (EventKind) String

func (ek EventKind) String() string

func (EventKind) TlogAppend

func (ek EventKind) TlogAppend(b []byte) []byte

func (*EventKind) TlogParse

func (ek *EventKind) TlogParse(p []byte, i int) int

type FormatNext

type FormatNext string

type ID

type ID [16]byte

func IDFromBytes

func IDFromBytes(b []byte) (id ID, err error)

IDFromBytes decodes ID from bytes slice.

If byte slice is shorter than type length result is returned as is and ShortIDError as error value. You may use result if you expected short ID prefix.

func IDFromString

func IDFromString(s string) (id ID, err error)

IDFromString parses ID from string.

If parsed string is shorter than type length result is returned as is and ShortIDError as error value. You may use result if you expected short ID prefix (profuced by ID.String, for example).

func IDFromStringAsBytes

func IDFromStringAsBytes(s []byte) (id ID, err error)

IDFromStringAsBytes is the same as IDFromString. It avoids alloc in IDFromString(string(b)).

func MathRandID

func MathRandID() (id ID)

func MustID

func MustID(id ID, err error) ID

MustID wraps IDFrom* call and panics if error occurred.

func ShouldID

func ShouldID(id ID, err error) ID

ShouldID wraps IDFrom* call and skips error if any.

func (ID) Format

func (id ID) Format(s fmt.State, c rune)

Format is fmt.Formatter interface implementation. It supports width. '+' flag sets width to full ID length.

func (ID) FormatTo

func (id ID) FormatTo(b []byte, i int, f rune) int

FormatTo is alloc free Format alternative.

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

func (ID) String

func (id ID) String() string

String returns short string representation.

It's not supposed to be able to recover it back to the same value as it was.

func (ID) StringFull

func (id ID) StringFull() string

StringFull returns full id represented as string.

func (ID) StringUUID added in v0.23.1

func (id ID) StringUUID() string

func (ID) TlogAppend

func (id ID) TlogAppend(b []byte) []byte

func (*ID) TlogParse

func (id *ID) TlogParse(p []byte, i int) int

func (*ID) UnmarshalJSON

func (id *ID) UnmarshalJSON(b []byte) error

type LogLevel

type LogLevel int
const (
	Info LogLevel = iota
	Warn
	Error
	Fatal

	Debug LogLevel = -1
)

Log levels.

func (LogLevel) TlogAppend

func (l LogLevel) TlogAppend(b []byte) []byte

func (*LogLevel) TlogParse

func (l *LogLevel) TlogParse(p []byte, i int) int

type Logger

type Logger struct {
	io.Writer // protected by Mutex below

	tlwire.Encoder

	NewID func() ID `deep:"compare=pointer"` // must be threadsafe

	sync.Mutex
	// contains filtered or unexported fields
}

func New

func New(w io.Writer) *Logger

func V

func V(topics string) *Logger

func (*Logger) Copy

func (l *Logger) Copy(w io.Writer) *Logger

func (*Logger) Event

func (l *Logger) Event(kvs ...interface{}) (err error)

func (*Logger) IOWriter

func (l *Logger) IOWriter(d int) io.Writer

func (*Logger) If

func (l *Logger) If(topics string) bool

func (*Logger) IfDepth

func (l *Logger) IfDepth(d int, topics string) bool

func (*Logger) Labels

func (l *Logger) Labels() RawMessage

func (*Logger) NewMessage

func (l *Logger) NewMessage(d int, id ID, msg interface{}, kvs ...interface{})

func (*Logger) NewSpan

func (l *Logger) NewSpan(d int, par ID, name string, kvs ...interface{}) Span

func (*Logger) OK added in v0.23.0

func (l *Logger) OK() bool

func (*Logger) Or

func (l *Logger) Or(l2 *Logger) *Logger

func (*Logger) Printf

func (l *Logger) Printf(fmt string, args ...interface{})

func (*Logger) Printw

func (l *Logger) Printw(msg string, kvs ...interface{})

func (*Logger) Root

func (l *Logger) Root() Span

func (*Logger) SetLabels

func (l *Logger) SetLabels(kvs ...interface{})

func (*Logger) SetVerbosity

func (l *Logger) SetVerbosity(vfilter string)

func (*Logger) Start

func (l *Logger) Start(name string, kvs ...interface{}) Span

func (*Logger) V

func (l *Logger) V(topics string) *Logger

func (*Logger) Verbosity added in v0.22.1

func (l *Logger) Verbosity() string

func (*Logger) Write

func (l *Logger) Write(p []byte) (int, error)

type Modify

type Modify []byte

func NextIs

func NextIs(semantic int) Modify

type RawMessage

type RawMessage []byte

func RawTag

func RawTag(tag byte, sub int) RawMessage

func Special

func Special(value int) RawMessage

func (RawMessage) TlogAppend

func (r RawMessage) TlogAppend(b []byte) []byte

func (*RawMessage) TlogParse

func (r *RawMessage) TlogParse(p []byte, st int) (i int)

type ShortIDError

type ShortIDError struct {
	Bytes int // Bytes successfully parsed
}

ShortIDError is an ID parsing error.

func (ShortIDError) Error

func (e ShortIDError) Error() string

Error is an error interface implementation.

type Span

type Span struct {
	Logger    *Logger
	ID        ID
	StartedAt time.Time
}

func Root

func Root() Span

func SpanFromContext

func SpanFromContext(ctx context.Context) (s Span)

SpanFromContext loads saved by ContextWithSpan Span from Context. It returns valid empty (no-op) Span if none was found.

func SpawnFromContext

func SpawnFromContext(ctx context.Context, name string, kvs ...interface{}) Span

SpawnFromContext spawns new Span derived form Span or ID from Context. It returns empty (no-op) Span if no ID found.

func SpawnFromContextAndWrap

func SpawnFromContextAndWrap(ctx context.Context, name string, kvs ...interface{}) (Span, context.Context)

func SpawnFromContextOrStart

func SpawnFromContextOrStart(ctx context.Context, name string, kvs ...interface{}) Span

func Start

func Start(name string, kvs ...interface{}) Span

func (Span) Copy

func (s Span) Copy(w io.Writer) Span

func (Span) Event

func (s Span) Event(kvs ...interface{}) (err error)

func (Span) Finish

func (s Span) Finish(kvs ...interface{})

func (Span) IOWriter

func (s Span) IOWriter(d int) io.Writer

func (Span) If

func (s Span) If(topics string) bool

func (Span) IfDepth

func (s Span) IfDepth(d int, topics string) bool

func (Span) NewMessage

func (s Span) NewMessage(d int, msg interface{}, kvs ...interface{})

func (Span) OK added in v0.23.0

func (s Span) OK() bool

func (Span) Or

func (s Span) Or(s2 Span) Span

func (Span) Printf

func (s Span) Printf(fmt string, args ...interface{})

func (Span) Printw

func (s Span) Printw(msg string, kvs ...interface{})

func (Span) Spawn

func (s Span) Spawn(name string, kvs ...interface{}) Span

func (Span) V

func (s Span) V(topics string) Span

type Timestamp

type Timestamp int64

Directories

Path Synopsis
cmd
examples
raw
ext

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳