ylog

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: May 23, 2022 License: MIT Imports: 7 Imported by: 9

README

ylog

ylog is an alternative logger for golang that support propagated data into log.

Why data propagation?

In most case when building a backend system, we frequently need data that exist in all log to make debugging in production easier. For example, we want to add trace_id in every log that have same life-cycle (one API request-response), or we want to add journey_id an ID from mobile application to ensure that 2 API calls is come from one journey (i.e: when we want to list the restaurant, we call 2 API one is get the list of restaurant that open and second is restaurant's details (this common in microservice)).

Some other log libraries like logrus or zap support With or WithFields method that add field into all log written after the function is called. But, context for propagation data is more convenient in Golang.

How to use?

func main() {
    type RequestPropagationData struct {
        TraceID string `tracer:"trace_id"`
    }

    propagateData := RequestPropagationData{
        TraceID: "abc",
    }
    
    tracer, err := ylog.NewTracer(propagateData, ylog.WithTag("tracer"))
    if err != nil {
		panic(err)
    }
    
    ctx := ylog.Inject(context.Background(), tracer)

    core := zapcore.NewCore(
        zapcore.NewJSONEncoder(zapcore.EncoderConfig{
            TimeKey:        "ts",
            MessageKey:     "msg",
            EncodeDuration: zapcore.MillisDurationEncoder,
            EncodeTime:     zapcore.RFC3339NanoTimeEncoder,
            LineEnding:     zapcore.DefaultLineEnding,
            LevelKey:       "level",
            EncodeLevel:    zapcore.LowercaseLevelEncoder,
        }),
        zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout)), // pipe to multiple writer
        zapcore.DebugLevel,
    )
	
    zapLogger := zap.New(core)
    uniLogger := ylog.NewZap(zapLogger)
	
	// set to global logger
	ylog.SetGlobalLogger(uniLogger)
    ylog.Info(ctx, "info message")
    ylog.Error(ctx, "error message")
}

Above code will return log output similar like this

{"level":"info","ts":"2022-03-08T15:01:11.231938+07:00","msg":"info message","tag":"sys","tracer":{"trace_id":"abc"}}
{"level":"error","ts":"2022-03-08T15:01:11.23194+07:00","msg":"error message","tag":"sys","tracer":{"trace_id":"abc"}}

As you can see, the output always have tracer.trace_id data with exactly the same value. This value must always be generated in middleware as unique value, i.e: UUID.

If you want to get the data back, you can use:

var wantDataBack RequestPropagationData
tracerData := ylog.MustExtract(ctx)
err := ylog.UnmarshalTracer(tracerData, &wantDataBack)
if err != nil {
	panic(err)
}

fmt.Println(wantDataBack)

Variable wantDataBack now will contain the same values as when you inject it to context.

Limitation

  • Struct passed in ylog.NewTracer must only have field with simple type (string, number or boolean). This also means that Field with type map, struct, or interface won't be supported. This is because the tracer data must contain simple value only, i.e: tracer_id, journey_id, user_id. Complex nested data requires to be nested (or even recursive) loop that may reduce performance of your system.

Benchmark

go test -bench=. -benchmem ./...
goos: darwin
goarch: arm64
pkg: github.com/yusufsyaifudin/ylog
BenchmarkNewTracer-8     3994966               293.6 ns/op           536 B/op          7 allocs/op
BenchmarkNewZap-8         843906              1392 ns/op             857 B/op         11 allocs/op
PASS
ok      github.com/yusufsyaifudin/ylog  2.772s

Documentation

Index

Constants

View Source
const (
	TypeAccessLog = "access_log"
	TypeSys       = "sys"
)

Variables

This section is empty.

Functions

func Access

func Access(ctx context.Context, data AccessLogData)

func Debug

func Debug(ctx context.Context, msg string, fields ...KeyValue)

func Error

func Error(ctx context.Context, msg string, fields ...KeyValue)

func Info

func Info(ctx context.Context, msg string, fields ...KeyValue)

func Inject

func Inject(ctx context.Context, stuff *Tracer) context.Context

Inject Tracer object into context. As Go doc said: https://golang.org/pkg/context/#WithValue Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. https://blog.golang.org/context

func SetGlobalLogger

func SetGlobalLogger(log Logger)

func UnmarshalTracer

func UnmarshalTracer(tracer *Tracer, v interface{}) error

func Warn

func Warn(ctx context.Context, msg string, fields ...KeyValue)

Types

type AccessLogData

type AccessLogData struct {
	Path        string   `json:"path,omitempty"`
	Request     HTTPData `json:"request,omitempty"`
	Response    HTTPData `json:"response,omitempty"`
	Error       string   `json:"error,omitempty"`
	ElapsedTime int64    `json:"elapsed_time,omitempty"`
}

type HTTPData

type HTTPData struct {
	Header     map[string]string `json:"header,omitempty"`
	DataObject interface{}       `json:"data_object,omitempty"`
	DataString string            `json:"data_string,omitempty"`
}

type KeyValue

type KeyValue struct {
	// contains filtered or unexported fields
}

func KV

func KV(k string, v interface{}) KeyValue

type Logger

type Logger interface {
	Debug(ctx context.Context, msg string, fields ...KeyValue)
	Info(ctx context.Context, msg string, fields ...KeyValue)
	Warn(ctx context.Context, msg string, fields ...KeyValue)
	Error(ctx context.Context, msg string, fields ...KeyValue)
	Access(ctx context.Context, data AccessLogData)
}

type OptionsTracer

type OptionsTracer func(tracer *Tracer) error

func WithTag

func WithTag(tag string) OptionsTracer

type Tracer

type Tracer struct {
	// contains filtered or unexported fields
}

func Extract

func Extract(ctx context.Context) (*Tracer, bool)

Extract get Tracer information from context

func MustExtract

func MustExtract(ctx context.Context) *Tracer

MustExtract will extract Tracer without false condition. When Tracer is not exist, it will return empty Tracer instead of error.

func NewTracer

func NewTracer(v interface{}, opts ...OptionsTracer) (*Tracer, error)

type Zap

type Zap struct {
	// contains filtered or unexported fields
}

func NewZap

func NewZap(zapLogger *zap.Logger) *Zap

func (*Zap) Access

func (z *Zap) Access(ctx context.Context, data AccessLogData)

func (*Zap) Debug

func (z *Zap) Debug(ctx context.Context, msg string, fields ...KeyValue)

func (*Zap) Error

func (z *Zap) Error(ctx context.Context, msg string, fields ...KeyValue)

func (*Zap) Info

func (z *Zap) Info(ctx context.Context, msg string, fields ...KeyValue)

func (*Zap) Warn

func (z *Zap) Warn(ctx context.Context, msg string, fields ...KeyValue)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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