https://github.com/dataphos/lib-logger
Common Logging Library
https://github.com/dataphos/lib-logger
go library logging
Last synced: about 1 month ago
JSON representation
Common Logging Library
- Host: GitHub
- URL: https://github.com/dataphos/lib-logger
- Owner: dataphos
- License: apache-2.0
- Created: 2024-10-02T13:09:50.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-10-11T09:21:18.000Z (over 1 year ago)
- Last Synced: 2025-01-19T13:15:34.530Z (over 1 year ago)
- Topics: go, library, logging
- Language: Go
- Homepage:
- Size: 71.3 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# lib-logger
*Common Logging Library*
This repository contains the `Log` interface and `standardlogger` implementation
that logs to stdout.
## Import package
```golang
import (
"github.com/dataphos/lib-logger/logger"
"github.com/dataphos/lib-logger/standardlogger"
)
```
## How To Use
### Labels
To use the logger you first have to define Labels.
Labels are a map-like object containing key-value string pairs sent with every log entry.
Labels creation:
```golang
// only *strings* are allowed as keys and values
labels := logger.Labels{"product": "Persistor", "clientId": "client0", "license": "enterprise"}
// logger.L is an alias of logger.Labels
// this should be used in the Add method (see below)
labels := logger.L{"product": "Persistor", "clientId": "client0", "license": "enterprise"}
```
This creates `"product"="Persistor", "clientId"="client0", "license"="enterprise"` key-value pairs.
Labels also allow you to add and delete keys:
```golang
labels := logger.Labels{"product": "Persistor", "clientId": "client0", "license": "enterprise"}
labels.Del("clientId", "license")
labels.Add(logger.L{"product": "SchemaRegistry", "cluster": "C1"}) // overrides product and adds cluster
```
When defining multiple loggers it is useful to have one master `Labels` object
and many children that add additional information:
```golang
mainLabels := logger.Labels{"product": "Persistor", "clientId": "client0"}
// subscriber component
// create an independent copy of the mainLabels and add component=subscriber key-value pair
subscriberLabels := mainLabels.Clone()
subscriberLabels.Add(logger.L{"component": "subscriber"})
// publisher component
// chaining is allowed
publisherLabels := mainLabels.Clone().Add(logger.L{"component": "publisher"})
```
**Note:** without a call to `Clone()`, child `Labels` is not independent of
the parent `Labels`, and calling `Add()` or `Del()` will modify the original `Labels`
instance.
### How To Log
Create an instance of the `standardlogger` and pass `Labels` instance as
the parameter:
```golang
log := standardlogger.New(labels)
```
To log a message, use logging functions:
```golang
// ======================================
// LOGGING FUNCTIONS WITHOUT EXTRA FIELDS
// ======================================
log.Info("Info")
log.Warn("Warning")
// Error, Fatal, Panic require error code
log.Error("Error", 0)
log.Fatal("Error", 0)
log.Panic("Panic", 0)
// ======================================
// LOGGING FUNCTIONS WITH EXTRA FIELDS
// SHORT SYNTAX
// logger.F is an alias of logger.Fields
// ======================================
log.Infow("Info", logger.F{"userId": 55, "objId": 64})
log.Warnw("Warn", logger.F{"objId": 43})
log.Errorw("Error", 0, logger.F{"objId": 43})
log.Fatalw("Fatal", 0, logger.F{"userId": 55})
log.Panicw("Panic", 0, logger.F{"reqId": 22})
```
### How To Pass a `log` Object
To pass a `log`, use the `logger.Log` interface:
```golang
func giveMeTheLog(log logger.Log) {
log.Info("Log passed")
}
```
### How To Log Panics
To properly log panics, make a deferred call to `log.PanicLogger()` at the
beginning of every goroutine. Possible deferred recovery function should be
deferred **before** the PanicLogger.
The main goroutine begins at `func main()` so
that is where you should make a deferred call to `log.PanicLogger()`.
```golang
func main() {
defer func() {
if r:=recover(); r!=nil {
// recover panicking goroutine
}
}()
labels := logger.Labels{"product": "Persistor"}
log := standardlogger.New(labels)
defer log.PanicLogger()
// panic can be called directly
panic("PANIC!")
// it can also be called with the log.Panicw method.
// this allows you to set error code and
// additional information
log.Panicw("Panic", 1000, logger.F{"reqId": 22})
// goroutine
go func() {
routineLabels := labels.Clone().
Add(logger.L{"component": "routine", "remove": "me", "and": "me"}).
Del("remove", "and")
routineLog := standardlogger.New(routineLabels)
defer routineLog.PanicLogger()
// code...
}()
}
```
### How To Log With Log Level
Create an instance of the `standardlogger`, pass `Lables` instance and to set desired log level use `WithLogLevel(level)`
You can set level to `logger.LevelInfo`, `logger.LevelWarn`, `logger.LevelError`, `logger.LevelPanic` and `logger.LevelFatal`. Logs of a lower level won't be printed to stdout.
Here is an example of how to do this:
```golang
logger := standardlogger.New(labels, standardlogger.WithLogLevel(logger.LevelInfo))
```
You can log a message using logging functions as shown before.
# Testing
Standard logger has a `NewForTesting` constructor that keeps logged records in memory:
```golang
log, logs := NewForTesting(logger.Labels{"product": "Persistor"})
```
where `log` is an instance of the `standardlogger` and `logs` is an instance of
[zaptest observer](go.uber.org/zap/zaptest/observer)'s `ObservedLogs`.
`ObservedLogs` holds all logged records as a list of `LoggedEntry` instances.
They in turn hold the underlying `Entry` and the `Context`:
```golang
type LoggedEntry struct {
zapcore.Entry
Context []zapcore.Field
}
```
Important fields in the `zapcore.Entry` are `Message` and `Level`.
The following example shows basic usage of the `NewForTesting` in a unit test:
```golang
func TestNewForTesting(t *testing.T) {
log, logs := NewForTesting(logger.Labels{})
log.Info("Info")
expectedMsg := "Info"
entry := logs.All()[0]
if entry.Message != expectedMsg {
t.Errorf("Wrong message, want %s.", expectedMsg)
}
}
```
This example demonstrates how to test when logging with extra fields:
```golang
func TestNewForTesting_WithLabelsOnWarn(t *testing.T) {
log, logs := NewForTesting(
logger.Labels{
"product": "Persistor",
"license": "enterprise",
})
log.Warn("Warn")
expectedFields := []zap.Field{
zap.String("product", "Persistor"),
zap.String("license", "enterprise"),
}
expectedMsg := "Warn"
entry := logs.All()[0]
if entry.Message != expectedMsg {
t.Errorf("Wrong message, want %s.", expectedMsg)
}
for _, field := range expectedFields {
found := false
for _, v := range entry.Context {
if field.Equals(v) {
found = true
break
}
}
if !found {
t.Errorf("Field not found, want %v.", field)
}
}
}
```
More examples can be found in `standardlogger/standardlogger_test.go`.
## Full Example
Full working example can be found at `example/main.go`
## Important
Standard logger is a thin wrapper around Uber `zap` logging library: https://github.com/uber-go/zap.
### Sampling
Sampling is **enabled** to protect from a flood of errors.
Logs are **dropped intentionally** by zap when sampling is enabled.
Sampling will cause **repeated logs within a second** to be sampled.
Read more at: https://github.com/uber-go/zap/blob/master/FAQ.md#why-are-some-of-my-logs-missing
## Creating a New Logger
To create a new logger, implement the `Log` interface that can be
found in the `logger` package.