tlog

package module
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2021 License: MIT Imports: 28 Imported by: 0

README

Documentation Build Status CircleCI codecov GolangCI Go Report Card GitHub tag (latest SemVer)

tlog

Project is in process of rewriting.

TraceLog is a new way of instrumentation. Log once use everywhere: logs, distributed traces, metrics, analytics and more.

Explore examples and extensions.

Idea and most of the concepts were designated while working on distributed systems in Yuri Korzhenevsky R&D Center.

Contents

Status

It evolves as I use it. I still can change anything, but for now I'm quiet satisfied with most of details.

It's tested a bit but bugs are possible. Please report if find.

Benifits

It's fast. I mean I've made number of improvements that gave another 50ns gain after all the usual methods had been defeated. Almost all allocs are eliminated. Only few are remain: type to interface{} conversion in Attrs and those that with less than linear growth from number of events. That means no garbage collector pressure, no resources are spent on it. No dependency from GC cycle, it does not have to pay for alloc while gc works. Benchmarks are below.

It's powerful logger. Besides stdlib like logging there are structured logging, conditional logging, testing logging, Labels, Attributes, Callers info attachment.

It combines all the instrumentation systems at one. You do things once and use it reading logs, investigating traces, evaluating metrics, generating alerts. Everything is ready to work on distributed systems with no additional cost.

It's extendable from both sides. You can implement tlog.Writer and process messages generated by tlog.Logger. You can create a wrapper that will modify any aspect of logger. You can generate events and pass them to undelaying tlog.Writer directly. Almost everything used internally is exported to be helpful for you.

It's going to mimic of and/or integrate with existing systems: prometheus, OpenTelemetry, net/trace, Google Trace API, maybe jaeger backend, sentry...

It's going to have its own analysing tools taking advantage of a brand new events format.

It's open souce and free.

Logger

Logging as usual.

tlog.Printf("message: %v", "arguments")

Structured logging

tlog.Printw("message",
		"i", i,
		"path", pth)

Conditional logging

There is some kind of verbosity levels.

tlog.V("debug").Printf("DEBUG: conditional message")

if tlog.If("trace") {
    p := 1 + 2 // complex calculations here that will not be executed if log level is not high enough
    tlog.Printf("result: %v", p)
}

tlog.Printf("unconditional message") // prints anyway

Actually it's not verbosity levels but debug topics. Each conditional operation have some topics it belongs to. And you can configure Logger precisely, which topics at which locations are enabled at each moment (concurrent usage/update is supported).

func main() {
    // ...
	tlog.DefaultLogger = tlog.New(tlog.NewConsoleWriter(os.Stderr, tlog.LstdFlags))

	// change filter at any time.
	tlog.SetFilter(filter) // filter = "telemetry" or "Conn" or "Send=encrypt" or "file.go"
}

// path/to/module/and/file.go

func (t *Conn) Send(/*...*/) {
    // ...
    tlog.V("encrypt").Printf("tls encoding debug data")
    // ...
    tlog.V("telemetry,debug").Printf("telemetry ...")
    // ...
    if tlog.If("read,error") {
        // prepare and log message
    }
}

filtersFlag is a comma separated list of filters such as

# all messages with topics are enabled
telemetry
encryption
debug
trace

# all topics at specified location are enabled
path             # child packages are not enabled
path/*
path/to/file.go
file.go
package
(*Type)          # all Conn methods are enabled
Type             # short form
Type.Method      # one method
Method           # function or method of any object

# enable specific topics at specific location
package=encryption
Type=encryption+telemetry # multiple topics for location separated by '+'

List of filters is executed as chain of inclusion, exclusion and back inclusion of some locations.

path/*,!path/subpackage,path/subpackage/file.go,!funcInFile,!subpackage/file.go=debug+trace

What's happend:
* path/* - include whole subtree
* !path/subpackage - but exclude one of subpackages. Others: path/sub1/*, path/sub2/*, etc remain included.
* path/subpackage/file.go - but we interested in logs in file, so include it
* !funcInFile - except some function.
* !subpackage/file.go=debug+trace - and except topics `debug` and `trace` in file subpackage/file.go

In most cases it's enough to have only one filter, but if you need, you may have more with no performance loss.

By default all conditionals are disabled.

Logger object

Logger can be created as an object by tlog.New. All the same core functions are available at *tlog.Logger, package and tlog.Span.

l := tlog.New(...)

l.Printf("unconditional")
l.V("topic").Printf("conditional")

tr := l.Start("trace_name")
defer tr.Finish()

tr.Printf("trace info")

nil *tlog.Logger works perfectly fine as well as uninitialized tlog.Span. They both just do nothing but never panic.

type Service struct {
	logger *tlog.Logger
}

func NewService() *Service {
	s := &Service{}

	if needLogs {
		s.logger = tlog.New(...) // set if needed, leave nil if not
	}

	return s
}

func (s *Service) Operation() {
	// ...
	s.logger.Printf("some details") // use anyway without fear
}

Logging in tests

Log to *testing.T or *testing.B from your service code.

// using the Service defined above

func TestService(t *testing.T) {
	topics := "conn,rawbody" // get it from flags

	// if function crash messages from testing.T will not be printed
	// so set it to os.Stderr or buffer to print logs on your own
	// leave it nil to print to the test like by t.Logf
	var tostderr io.Writer

	tl := tlog.NewTestLogger(t, topics, tostderr)

	s := NewService()
	s.logger = tl

	r, err := s.PrepareOp()
	// if err != nil ...
	// assert r

	// instead of t.Logf()
	tl.Printf("dump: %v", r)

	// ...
}

Callers

Location in source code is recorded for each message you log (if you not disabled it). But you also may capture some location or stack trace.

l := loc.Caller(0) // 0 means current line
l = loc.Caller(2) // 2 frames higher
s := loc.Callers(2, 4) // skip 2 frames and record next 4

var globalVar loc.PC
l = loc.CallerOnce(0, &globalVar) // make heavy operation once; threadsafe

Then you may get function name, file name and file line for each frame.

funcName, fileName, fileLine := l.NameFileLine()
funcName, fileName, fileLine = s[2].NameFileLine()
tlog.Printf("called from here: %#v", l)
tlog.Printf("crashed\n%v", tlog.Callers(0, 10))

Writer

Default format is internal CBOR-like binary format. But converters are available.

Planned way is to log to a file (like normal loggers do) and to use separate agent to process data, send it to external services or serve requests as part of distributed storage.

ConsoleWriter

It supports the same flags as stdlib log plus some extra.

var w io.Writer = os.Stderr // could be any writer
tlog.DefaultLogger = tlog.New(tlog.NewConsoleWriter(w, tlog.LstdFlags | tlog.Lmilliseconds))

JSONWriter

Encodes logs in a compact way to analyze them later. It only needs io.Writer.

file, err := // ...
// if err ...
var w io.Writer = file // could be os.Stderr or net.Conn...
tlog.DefailtLogger = tlog.New(convert.NewJSON(w))

ProtoWriter

Ptotobuf encoding is compact and fast.

_ = convert.NewProto(w)

TeeWriter

You also may use several writers in the same time. It works similar to io.MultiWriter but writes to all writers regardless of errors.

cw := tlog.NewConsoleWriter(os.Stderr, tlog.LdetFlags)
jw := convert.NewJSON(file)
w := tlog.NewTeeWriter(cw, jw) // order is important. In that order messages will be passed to writers.
l := tlog.New(w)

The best writer ever

You can implement your own recoder.

There are more writers in tlog package, find them in docs.

Tracer

It's hard to overvalue tracing when it comes to many parallel requests and especially when it's distributed system. So tracing is here.

func Google(ctx context.Context, user, query string) (*Response, error) {
    tr := tlog.Start("search_request") // records frame (function name, file and line) and start time
    defer tr.Finish() // records duration

    tr.SetLabels(Labels{"user=" + user}) // attach to Span and each of it's messages.
        // In contrast with (*Logger).SetLabels it can be called at any point.
	// Even after all messages and metrics.

    for _, b := range backends {
        go func(){
            subctx := tlog.ContextWithID(ctx, tr.ID)

            res := b.Search(subctx, u, q)

            // handle res
        }()
    }

    var res Response

    // wait for and take results of backends

    // each message contains time, so you can measure each block between messages
    tr.Printw("backends responded", tlog.AInt("pages", len(res.Pages)))

    // ...

    tr.Printf("advertisments added")

    // return it in HTTP Header or somehow.
    // Later you can use it to find all related Spans and Messages.
    res.TraceID = tr.ID

    return res, nil
}

func (b *VideosBackend) Search(ctx context.Context, q string) ([]*Page, error) {
    tr := tlog.SpawnFromContext(ctx)
    defer tr.Finish()

    // ...
    tr.Printf("anything")
    
    // ...

    return res, nil
}

Traces may be used as metrics either. Analyzing timestamps of messages you can measure how much time has passed since one message to another.

Important thing you should remember: context.Context Values are not passed through the network (http.Request.WithContext for example). You must pass Span.ID manually. Should not be hard, there are helpers.

Analysing and visualising tool is going to be later.

Trace can also be used as net/trace.EventLog.

There is example middleware for gin to extract Span.ID and spawn new Span

Tracer + Logger

The best part is that you don't need to pass the same useful information to logs and to traces like when you use two separate systems, it's done for you!

tr := tlog.Start("trace_name")
defer tr.Finish()

tr.Printf("each time you print something to trace it appears in logs either")

tlog.Printf("but logs don't appear in traces")

Metrics

tlog.SetLabels(tlog.Labels{"global=label"})

tlog.RegisterMetric("fully_qualified_metric_name_with_units", tlog.MetricSummary, "help message that describes metric")

// This is metric either. It records span duration as Metric.
tr := tlog.Start("scope_name")
defer tr.Finish()

tr.Printw("account", "account_id", accid)

tr.Observe("fully_qualified_metric_name_with_units", 123.456)

This result in the following prometheus-like output

# HELP fully_qualified_metric_name_with_units help message that describes metric
# TYPE fully_qualified_metric_name_with_units summary
fully_qualified_metric_name_with_units{global="label",quantile="0.1"} 123.456
fully_qualified_metric_name_with_units{global="label",quantile="0.5"} 123.456
fully_qualified_metric_name_with_units{global="label",quantile="0.9"} 123.456
fully_qualified_metric_name_with_units{global="label",quantile="0.95"} 123.456
fully_qualified_metric_name_with_units{global="label",quantile="0.99"} 123.456
fully_qualified_metric_name_with_units{global="label",quantile="1"} 123.456
fully_qualified_metric_name_with_units_sum{global="label"} 123.456
fully_qualified_metric_name_with_units_count{global="label"} 1
# HELP tlog_span_duration_ms span context duration in milliseconds
# TYPE tlog_span_duration_ms summary
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="0.1"} 0.066248
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="0.5"} 0.066248
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="0.9"} 0.066248
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="0.95"} 0.066248
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="0.99"} 0.066248
tlog_span_duration_ms{global="label",func="main.main.func2",quantile="1"} 0.066248
tlog_span_duration_ms_sum{global="label",func="main.main.func2"} 0.066248
tlog_span_duration_ms_count{global="label",func="main.main.func2"} 1

Check out prometheus naming convention https://prometheus.io/docs/practices/naming/.

Distributed

Distributed tracing work almost the same as local logger.

Labels

First thing you sould set up is tlog.Labels. They are attached to each of the following Message, Span and Metric. You can find out later which machine and process produced each log event by these labels.

Resetting Labels replaces all of them not just given. Messages created before Labels was set are not annotated with them.

There are some predefined label names that can be filled for you.

tlog.DefaultLogger = tlog.New(...)

// full list is in tlog.AutoLabels
base := tlog.FillLabelsWithDefaults("_hostname", "_user", "_pid", "_execmd5", "_randid")

ls := append(Labels{"service=myservice"}, base...)

ls = append(ls, tlog.ParseLabels(*userLabelsFlag)...)

tlog.SetLabels(ls)

Span.ID

In a local code you may pass Span.ID in a context.Context as tlog.ContextWithID and derive from it as tlog.SpawnFromContext. But additional actions are required in case of remote procedure call. You need to send Span.ID with arguments as a string or []byte. There are helper functions for that: ID.FullString, tlog.IDFromString, ID[:], tlog.IDFromBytes.

Example for gin is here: ext/tlgin/gin.go

func server(w http.ResponseWriter, req *http.Request) {
    xtr := req.Header.Get("X-Traceid")
    trid, err := tlog.IDFromString(xtr)
    if err != nil {
        trid = tlog.ID{}	    
    }
    
    tr := tlog.SpawnOrStart(trid)
    defer tr.Finish()

    if err != nil && xtr != "" {
        tr.Printf("bad trace id: %v %v", xtr, err)
    }

    // ...
}

func client(ctx context.Context) {
    req := &http.Request{}

    if id := tlog.IDFromContext(ctx); id != (tlog.ID{}) {
        req.Header.Set("X-Traceid", id.FullString()) // ID.String returns short prefix. It's not enough to Swawn from it.
    }
    
    // ...
}

Performance

Allocs

Allocations are one of the worst enemies of performance. So I fighted each alloc and each byte and even hacked runtime (see unsafe.go). So you'll get much more than stdlib log gives you almost for the same price.

goos: linux
goarch: amd64
pkg: github.com/nikandfor/tlog

# logging
BenchmarkStdLogLogger/Std/SingleThread-8       	 3347139	       351 ns/op	      24 B/op	       2 allocs/op
BenchmarkStdLogLogger/Std/Parallel-8           	 2244493	       515 ns/op	      24 B/op	       2 allocs/op
BenchmarkStdLogLogger/Det/SingleThread-8       	  935287	      1239 ns/op	     240 B/op	       4 allocs/op
BenchmarkStdLogLogger/Det/Parallel-8           	 1000000	      1288 ns/op	     240 B/op	       4 allocs/op

BenchmarkTlogLogger/Std/SingleThread/Printf-8         	 4355400	       280 ns/op	       0 B/op	       0 allocs/op
BenchmarkTlogLogger/Std/SingleThread/Printw-8         	 4105479	       294 ns/op	       8 B/op	       1 allocs/op
BenchmarkTlogLogger/Std/Parallel/Printf-8             	 7600929	       155 ns/op	       0 B/op	       0 allocs/op
BenchmarkTlogLogger/Std/Parallel/Printw-8             	 7674375	       156 ns/op	       8 B/op	       1 allocs/op
BenchmarkTlogLogger/Det/SingleThread/Printf-8         	 1000000	      1029 ns/op	       0 B/op	       0 allocs/op
BenchmarkTlogLogger/Det/SingleThread/Printw-8         	  953114	      1129 ns/op	       8 B/op	       1 allocs/op
BenchmarkTlogLogger/Det/Parallel/Printf-8             	 3991116	       315 ns/op	       0 B/op	       0 allocs/op
BenchmarkTlogLogger/Det/Parallel/Printw-8             	 3677959	       335 ns/op	       8 B/op	       1 allocs/op

BenchmarkZapLogger/SingleThread-8             	  576332	      1875 ns/op	     344 B/op	       4 allocs/op
BenchmarkZapLogger/Parallel-8                 	 2139580	       574 ns/op	     344 B/op	       4 allocs/op

BenchmarkGlogLogger/SingleThread-8          	  912760	      1325 ns/op	     224 B/op	       3 allocs/op
BenchmarkGlogLogger/Parallel-8              	 1943516	       629 ns/op	     224 B/op	       3 allocs/op

BenchmarkLogrusLogger/SingleThread-8         	  386980	      2786 ns/op	     896 B/op	      19 allocs/op
BenchmarkLogrusLogger/Parallel-8             	  263313	      5347 ns/op	     897 B/op	      19 allocs/op

# trace with one message 
BenchmarkTlogTraces/ConsoleStd/SingleThread/StartPrintfFinish-8   	  648499	      1837 ns/op	        36.8 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkTlogTraces/ConsoleStd/Parallel/StartPrintfFinish-8       	 1615582	       718 ns/op	        36.5 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkTlogTraces/JSON/SingleThread/StartPrintfFinish-8         	  444662	      2440 ns/op	       250 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkTlogTraces/JSON/Parallel/StartPrintfFinish-8             	 1486056	       821 ns/op	       250 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkTlogTraces/Proto/SingleThread/StartPrintfFinish-8        	  469704	      2306 ns/op	       114 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkTlogTraces/Proto/Parallel/StartPrintfFinish-8            	 1578048	       763 ns/op	       113 disk_B/op	       0 B/op	       0 allocs/op

# writers
BenchmarkWriter/ConsoleDet/SingleThread/TracedMessage-8         	 5658282	       208 ns/op	        63.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/ConsoleDet/SingleThread/TracedMetric-8          	  626032	      1893 ns/op	       104 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/ConsoleDet/Parallel/TracedMessage-8             	 8096242	       148 ns/op	        63.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/ConsoleDet/Parallel/TracedMetric-8              	 1942116	       623 ns/op	       104 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/JSON/SingleThread/TracedMessage-8               	 9556735	       121 ns/op	        84.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/JSON/SingleThread/TracedMetric-8                	 4357563	       276 ns/op	        65.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/JSON/Parallel/TracedMessage-8                   	 6290318	       190 ns/op	        84.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/JSON/Parallel/TracedMetric-8                    	 4307060	       280 ns/op	        65.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/Proto/SingleThread/TracedMessage-8              	13024131	        85.1 ns/op	        49.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/Proto/SingleThread/TracedMetric-8               	 9758936	       128 ns/op	        32.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/Proto/Parallel/TracedMessage-8                  	 6444532	       187 ns/op	        49.0 disk_B/op	       0 B/op	       0 allocs/op
BenchmarkWriter/Proto/Parallel/TracedMetric-8                   	24649119	        44.0 ns/op	        32.0 disk_B/op	       0 B/op	       0 allocs/op

# Caller
BenchmarkLocationCaller-8         	 4326907	       265 ns/op	       0 B/op	       0 allocs/op
BenchmarkLocationNameFileLine-8   	 5736783	       207 ns/op	       0 B/op	       0 allocs/op

1 alloc in each line with Printw is int to interface{} conversion.

1 more alloc in most loggers is []interface{} allocation for variadic args. tlog is not the case because argumet doesn't leak and compiler optimiazation. 2 more allocs in LogLogger/Det benchmark is because of runtime.(*Frames).Next() - that's why I hacked it.

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 (
	MetricGauge   = "gauge"
	MetricCounter = "counter"
	MetricSummary = "summary"
)

Metric types

View Source
const (
	EventLabels     = EventType('L')
	EventSpanStart  = EventType('s')
	EventSpanFinish = EventType('f')
	EventValue      = EventType('v')
	EventMetricDesc = EventType('m')
)

Event Types

View Source
const (
	WireLabels = wire.SemanticExtBase + iota
	WireID
	WireMessage
	WireEventType

	WireLogLevel
)

Variables

View Source
var (
	Stdin  = os.Stdin
	Stdout = os.Stdout
	Stderr = os.Stderr
)

for you not to import os

View Source
var (
	KeyTime      = "t"
	KeySpan      = "s"
	KeyParent    = "p"
	KeyMessage   = "m"
	KeyElapsed   = "e"
	KeyCaller    = "c"
	KeyLabels    = "L"
	KeyEventType = "T"
	KeyLogLevel  = "l"
)

Predefined keys

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

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.
View Source
var KeyAuto = ""
View Source
var (
	ResetColor = Color(0)
)

Functions

func AppendKVs added in v0.11.0

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

func Color added in v0.10.0

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

func ContextWithID

func ContextWithID(ctx context.Context, id ID) context.Context

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

func ContextWithIDOrRandom added in v0.3.0

func ContextWithIDOrRandom(ctx context.Context, id ID) context.Context

ContextWithIDOrRandom creates new context with Span ID context.Value. If id is zero new random ID is generated.

func ContextWithLogger added in v0.10.0

func ContextWithLogger(ctx context.Context, l *Logger) context.Context

func ContextWithRandomID added in v0.3.0

func ContextWithRandomID(ctx context.Context) context.Context

ContextWithRandomID creates new context with random Span ID context.Value. May be useful to enable logging in submudules even if parent trace is not started.

func ContextWithSpan added in v0.3.0

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 added in v0.2.0

func ExecutableMD5() string

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

func ExecutableSHA1 added in v0.2.0

func ExecutableSHA1() string

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

func Filter added in v0.3.0

func Filter() string

Filter returns current verbosity filter of DefaultLogger.

func Hostname added in v0.2.0

func Hostname() string

Hostname returns hostname or err.Error().

func If added in v0.6.0

func If(tp string) bool

If does the same checks as V but only returns bool.

func Printf

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

func Printw added in v0.7.0

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

func RegisterMetric added in v0.7.0

func RegisterMetric(name, typ, help string, kvs ...interface{})

func SetFilter

func SetFilter(f string)

SetFilter sets filter to use in V.

Filter is a comma separated chain of rules. Each rule is applied to result of previous rule and adds or removes some locations. Rule started with '!' excludes matching locations.

Each rule is one of: topic (some word you used in V argument)

error
networking
send
encryption

location (directory, file, function) or

path/to/file.go
short_file.go
path/to/package - subpackages doesn't math
root/* - root package and all subpackages
github.com/nikandfor/tlog.Function
tlog.(*Type).Method
tlog.Type - all methods of type Type

topics in location

tlog.Span=timing
p2p/conn.go=read+write - multiple topics in location are separated by '+'

Example

module,!module/file.go,funcInFile

SetFilter can be called simultaneously with V.

func SetLabels

func SetLabels(ls Labels)

func TestSetTime added in v0.10.0

func TestSetTime(l *Logger, t func() time.Time, ts func() int64)

func UUID added in v0.9.0

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 added in v0.2.0

func User() string

User returns current username or err.Error().

Types

type ConsoleWriter

type ConsoleWriter struct {
	io.Writer
	Flags int

	Colorize        bool
	PadEmptyMessage bool

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

	TimeFormat     string
	DurationFormat string
	DurationDiv    time.Duration
	FloatFormat    string
	FloatChar      byte
	FloatPrecision int
	CallerFormat   string

	PairSeparator string
	KVSeparator   string

	QuoteChars      string
	QuoteAnyValue   bool
	QuoteEmptyValue bool

	TimeColor    []byte
	FileColor    []byte
	FuncColor    []byte
	MessageColor []byte
	KeyColor     []byte
	ValColor     []byte
	LevelColor   struct {
		Info  []byte
		Warn  []byte
		Error []byte
		Fatal []byte
		Debug []byte
	}
	// contains filtered or unexported fields
}

func NewConsoleWriter

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

func (*ConsoleWriter) Write added in v0.10.0

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

type CountableIODiscard added in v0.7.0

type CountableIODiscard struct {
	Bytes, Operations int64
}

CountableIODiscard discards data but counts operations and bytes. It's safe to use simultaneously (atimic operations are used).

func (*CountableIODiscard) ReportDisk added in v0.7.0

func (w *CountableIODiscard) ReportDisk(b *testing.B)

func (*CountableIODiscard) Write added in v0.7.0

func (w *CountableIODiscard) Write(p []byte) (int, error)

type EventType added in v0.10.0

type EventType rune

func (EventType) TlogAppend added in v0.11.0

func (et EventType) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*EventType) TlogParse added in v0.11.0

func (e *EventType) TlogParse(d *wire.Decoder, p []byte, i int) int

type Format added in v0.10.0

type Format struct {
	Fmt  string
	Args []interface{}
}

func (Format) TlogAppend added in v0.11.0

func (f Format) TlogAppend(e *wire.Encoder, b []byte) []byte

type FormatNext added in v0.10.0

type FormatNext string

type Hex added in v0.10.0

type Hex uint64

func (Hex) TlogAppend added in v0.11.0

func (x Hex) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*Hex) TlogParse added in v0.11.0

func (x *Hex) TlogParse(d *wire.Decoder, p []byte, i int) int

type ID

type ID [16]byte

func IDFromBytes added in v0.3.0

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 IDFromContext

func IDFromContext(ctx context.Context) ID

IDFromContext receives Span.ID or ID from Context. It returns zero if no ID found.

func IDFromString added in v0.3.0

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 added in v0.7.0

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

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

func MathRandID added in v0.9.0

func MathRandID() (id ID)

func MustID added in v0.7.0

func MustID(id ID, err error) ID

MustID wraps IDFrom* call and panics if error occurred.

func ShouldID added in v0.7.0

func ShouldID(id ID, err error) ID

ShouldID wraps IDFrom* call and skips error if any.

func (ID) Format added in v0.3.0

func (i 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 added in v0.3.0

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

FormatTo is alloc free Format alternative.

func (ID) String

func (i 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 added in v0.11.0

func (i ID) StringFull() string

StringFull returns full id represented as string.

func (ID) TlogAppend added in v0.11.0

func (id ID) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*ID) TlogParse added in v0.11.0

func (id *ID) TlogParse(d *wire.Decoder, p []byte, i int) int

type Labels

type Labels []string

Labels is a set of labels with optional values.

Global Labels are attached to all the following events (in an optimal way) until replaced. Span Labels are attached to Span and all it's events.

func FillLabelsWithDefaults

func FillLabelsWithDefaults(labels ...string) Labels

FillLabelsWithDefaults creates Labels and fills autolabels (See AutoLabels).

func ParseLabels

func ParseLabels(s string) Labels

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

func (Labels) Copy

func (ls Labels) Copy() Labels

Copy copies Labels including deleted thumbstones.

func (*Labels) Del

func (ls *Labels) Del(k string)

Del deletes label with key k.

func (Labels) Lookup added in v0.10.0

func (ls Labels) Lookup(k string) (string, bool)

Lookup gets k label value or "", false.

func (*Labels) Merge

func (ls *Labels) Merge(b Labels)

Merge merges two Labels sets.

func (*Labels) Set

func (ls *Labels) Set(k, v string)

Set sets k label value to v.

func (Labels) TlogAppend added in v0.11.0

func (ls Labels) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*Labels) TlogParse added in v0.11.0

func (ls *Labels) TlogParse(d *wire.Decoder, p []byte, i int) int

type LogLevel added in v0.10.0

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

	Debug LogLevel = -1
)

Log levels

func (LogLevel) TlogAppend added in v0.11.0

func (l LogLevel) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*LogLevel) TlogParse added in v0.11.0

func (l *LogLevel) TlogParse(d *wire.Decoder, p []byte, i int) int

type Logger

type Logger struct {
	io.Writer

	NewID func() ID // must be threadsafe

	NoTime   bool
	NoCaller bool

	sync.Mutex

	wire.Encoder
	// contains filtered or unexported fields
}

func LoggerFromContext added in v0.10.0

func LoggerFromContext(ctx context.Context) (l *Logger)

func LoggerOrDefaultFromContext added in v0.10.0

func LoggerOrDefaultFromContext(ctx context.Context) (l *Logger)

func New

func New(w io.Writer) *Logger

func NewTestLogger added in v0.5.0

func NewTestLogger(t testing.TB, v string, tostderr io.Writer) *Logger

NewTestLogger creates new logger with Writer destunation of testing.T (like t.Logf). v is verbosity topics. if tostderr is not nil than destination is changed to tostderr. Useful in case if test crashed and all log output is lost.

func V

func V(tp string) *Logger

V checks if topic tp is enabled and returns default Logger or nil.

It's OK to use nil Logger, it won't crash and won't emit any events to the Writer.

Multiple comma separated topics could be provided. Logger will be non-nil if at least one of these topics is enabled.

Usecases:

tlog.V("write").Printf("%d bytes written to address %v", n, addr)

if l := tlog.V("detailed"); l != nil {
    c := 1 + 2 // do complex computations here
    l.Printf("use result: %d")
}

func (*Logger) Event added in v0.10.0

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

func (*Logger) Filter added in v0.3.0

func (l *Logger) Filter() string

Filter returns current verbosity filter value.

See package.SetFilter description for details.

func (*Logger) IOWriter added in v0.8.0

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

func (*Logger) If added in v0.5.0

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

If checks if some of topics enabled.

func (*Logger) IfDepth added in v0.11.0

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

func (*Logger) NewMessage added in v0.11.0

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

func (*Logger) NewSpan added in v0.10.0

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

func (*Logger) Observe added in v0.6.0

func (l *Logger) Observe(name string, v interface{}, kvs ...interface{})

func (*Logger) Printf

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

func (*Logger) Printw added in v0.7.0

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

func (*Logger) RegisterMetric added in v0.7.0

func (l *Logger) RegisterMetric(name, typ, help string, kvs ...interface{})

func (*Logger) SetFilter

func (l *Logger) SetFilter(filters string)

SetFilter sets filter to use in V.

See package.SetFilter description for details.

func (*Logger) SetLabels added in v0.5.0

func (l *Logger) SetLabels(ls Labels)

func (*Logger) Spawn

func (l *Logger) Spawn(par ID, n string, kvs ...interface{}) Span

func (*Logger) SpawnOrStart added in v0.7.0

func (l *Logger) SpawnOrStart(par ID, n string, kvs ...interface{}) Span

func (*Logger) Start

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

func (*Logger) V

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

V checks if one of topics in tp is enabled and returns default Logger or nil.

It's OK to use nil Logger, it won't crash and won't emit any events to writer.

Multiple comma separated topics could be provided. Logger will be non-nil if at least one of these topics is enabled.

type Message

type Message string

func (Message) TlogAppend added in v0.11.0

func (m Message) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*Message) TlogParse added in v0.11.0

func (m *Message) TlogParse(d *wire.Decoder, p []byte, i int) int

type NopCloser added in v0.10.0

type NopCloser struct {
	io.Reader
	io.Writer
}

func (NopCloser) Close added in v0.10.0

func (NopCloser) Close() error

func (NopCloser) Fd added in v0.10.0

func (c NopCloser) Fd() uintptr

type RawMessage added in v0.11.0

type RawMessage []byte

func (RawMessage) TlogAppend added in v0.11.0

func (r RawMessage) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*RawMessage) TlogParse added in v0.11.0

func (r *RawMessage) TlogParse(d *wire.Decoder, p []byte, i int) int

type ReWriter added in v0.11.0

type ReWriter struct {
	Open func(io.Writer, error) (io.Writer, error)
	// contains filtered or unexported fields
}

func NewReWriter added in v0.11.0

func NewReWriter(open func(io.Writer, error) (io.Writer, error)) *ReWriter

func (*ReWriter) Close added in v0.11.0

func (w *ReWriter) Close() error

func (*ReWriter) Write added in v0.11.0

func (w *ReWriter) Write(p []byte) (n int, err error)

type ReadCloser added in v0.10.0

type ReadCloser struct {
	io.Reader
	io.Closer
}

type ShortIDError added in v0.10.0

type ShortIDError struct {
	N int
}

ShortIDError is an ID parsing error.

func (ShortIDError) Error added in v0.10.0

func (e ShortIDError) Error() string

Error is an error interface implementation.

type Span

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

func SpanFromContext added in v0.3.0

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

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

func Spawn

func Spawn(p ID, n string, kvs ...interface{}) Span

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 SpawnOrStartFromContext added in v0.10.0

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

SpawnFromContextOrStart loads saved by ContextWithSpan Span from Context. It starts new trace if no ID found.

func Start

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

func (Span) Event added in v0.10.0

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

func (Span) Finish

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

func (Span) IOWriter added in v0.8.0

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

func (Span) If added in v0.5.0

func (s Span) If(tp string) bool

If does the same checks as V but only returns bool.

func (Span) IfDepth added in v0.11.0

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

func (Span) NewMessage added in v0.11.0

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

func (Span) Observe added in v0.6.0

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

func (Span) Printf

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

func (Span) Printw added in v0.7.0

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

func (Span) Spawn added in v0.7.0

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

func (Span) SpawnOrStart added in v0.10.0

func (s Span) SpawnOrStart(n string, kvs ...interface{}) Span

func (Span) V

func (s Span) V(tp string) Span

V checks if one of topics in tp is enabled and returns the same Span or empty overwise.

It is safe to call any methods on empty Span.

Multiple comma separated topics could be provided. Span will be Valid if at least one of these topics is enabled.

type TeeWriter

type TeeWriter []io.Writer

func NewTeeWriter

func NewTeeWriter(ws ...io.Writer) (w TeeWriter)

func (TeeWriter) Append added in v0.10.0

func (w TeeWriter) Append(ws ...io.Writer) TeeWriter

func (TeeWriter) Close added in v0.10.0

func (w TeeWriter) Close() (err error)

func (TeeWriter) Write added in v0.10.0

func (w TeeWriter) Write(p []byte) (n int, err error)

type Timestamp added in v0.10.0

type Timestamp int64

func UnixNano added in v0.10.0

func UnixNano() Timestamp

func (Timestamp) TlogAppend added in v0.11.0

func (ts Timestamp) TlogAppend(e *wire.Encoder, b []byte) []byte

func (*Timestamp) TlogParse added in v0.11.0

func (ts *Timestamp) TlogParse(d *wire.Decoder, p []byte, i int) int

type WriteCloser added in v0.10.0

type WriteCloser struct {
	io.Writer
	io.Closer
}

Directories

Path Synopsis
cmd
examples
ext

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL