README ¶
API Pkg Go
Useful collection of reusable packages for Go
Log
: logging pkg that support write logs to multiple output target such asconsole
,file
(with logrotate),newrelic
andplatform log
at the same time.
Log
There are two main parts in log
which are Logger and Writer. frontend
is the API provided by Logger
interface and backend
is any pkg/lib that implement Logger
- Logger: Main actor that will decide where, how and whether it should write the logs or not based on the log level defined in each
Writer
. You may call this as thefrontend
, since you will and should only interact with the provided API fromLogger
interface. - Writer: Decide where the logs passed from
Logger
should be written to. Is it to terminal, file or whatever thisWriter
will decide that. We already provide pre-definedWriter
implementer namelyconsole
,file
,newrelic
,platform
.
For now, we can support two backend
which are zap & slog.
Use NewZapLogger
to use zap
as the logger backend or NewSlogLogger
to use slog
instead.
Getting Started
// set console/terminal writer to listen to all logs level DEBUG, INFO, WARNING, ERROR
// console writer, write logs to local terminal/console
cns := log.NewConsoleWriter(log.DebugLevel)
// use predefined zap as the backend
wr := log.NewZapLogger(cns)
// or use this if you want to use slog under the hood instead
// wr := logger.NewSlogLogger(cns)
// call Init before using any other API
wr.Init(3 * time.Second) // you may give longer or shorter timeout/deadline
// info level log message that include contextual data 'hello':'world'
wr.Inf("INFO message", log.String("hello", "world"))
// terminal: 2023-09-22T13:38:39.784+0700 INFO INFO message {"hello": "world"}
// json: {"level":"INFO","time":"2023-09-22T13:38:39.784+0700","msg":"INFO message","hello":"world"}
// debug level log message
wr.Dbg("DEBUG message")
// terminal: 2023-09-22T13:38:39.784+0700 DEBUG DEBUG message
// json: {"level":"DEBUG","time":"2023-09-22T13:38:39.784+0700","msg":"DEBUG message"}
// SHOULD be called before program exit to make sure any pending logs in buffer properly flushed by each Writer
wr.Flush(2 * time.Second) // you may give longer or shorter timeout/deadline
Contextual Data
// give contextual data that will be passed down to subsequent call
wr = wr.With(log.String("app_env", "local"))
wr.Wrn("warning log")
// terminal: 2023-09-22T13:38:39.784+0700 WARN warning log {"app_env": "local"}
// json: {"level":"WARN","time":"2023-09-22T13:38:39.784+0700","msg":"warning log","app_env":"local"}
wr = wr.With(log.Num("ram", 2)) // this will also accumulate previous contextual data
wr.Inf("look how many ram i have")
// terminal: 2023-09-22T13:38:39.784+0700 INFO look how many ram i have {"app_env": "local", "ram": 2}
// json: {"level":"INFO","time":"2023-09-22T13:38:39.784+0700","msg":"look how many ram i have","app_env":"local","ram":2}
Note: if the contextual data should change in each function call, then make sure to create new variable in each
.With()
call.
Example
func writeGoodLog(wr log.Logger) {
// create new variable instead of replacing the old wr variable
newWr := wr.With(
log.String("x-request-id", uuid.NewString()),
)
newWr.Inf("info message")
}
func writeBadLog(wr log.Logger) {
wr = wr.With(
log.String("x-request-id", uuid.NewString()),
)
wr.Inf("info message")
}
func main() {
// setup myLog that's type of log.Logger
myLog
writeBadLog(myLog)
// output: {"msg":"info message", "x-request-id": "c684f881-07a5-45e6-97fd-cb2af8ad7c4e"}
writeBadLog(myLog)
// output: {"msg":"info message", "x-request-id": "c684f881-07a5-45e6-97fd-cb2af8ad7c4e"}
// NOTE that each call on writeBadLog they will generate exactly same x-request-id, this is because the children
// and parent always affecting each other, if the children generate new x-request-id then the parent will also
// change their x-request-id and make that contextual data duplicate
writeGoodLog(myLog)
// output: {"msg":"info message", "x-request-id": "4014d36a-8f34-4b26-b91a-12480605033d"}
writeGoodLog(myLog)
// output: {"msg":"info message", "x-request-id": "2935ee81-a4a3-4586-b7f0-95d26473a5ac"}
// This time the x-request-id will always generate new string without affecting the log.Logger from param, because
// it always generates new log.Logger on each writeGoodLog() instead of replace and reusing log.Logger that come from param
}
Leveled Log
cns := log.NewConsoleWriter(log.ErrorLevel)
wr := log.NewZapLogger(cns)
wr.Init(3 * time.Second)
// won't print, less than error
wr.Dbg("DEBUG message")
// won't print, less than error
wr.Inf("INFO message")
// won't print, less than error
wr.Wrn("warning log")
// printed, higher or equal than error
wr.Err("oops!!")
// terminal: 2023-09-22T13:38:39.784+0700 ERROR oops!!
// json: {"level":"ERROR","time":"2023-09-22T13:38:39.784+0700","msg":"oops!!"}
// never forget to flush before exit
wr.Flush(1 * time.Second)
Log is prioritized in these order:
- Error
Err
: (Error) print only in log level Error - Warning
Wrn
: (Warning, Error) print in log level Warning, Error - Info
Inf
: (Info, Warning, Error) print in log level Info, Warning, Error - Debug
Dbg
: (Debug, Info, Warning, Error) print in all log level
Logger with Context
// put the logger wr to context with 'log.WithCtx'
ctx := log.WithCtx(context.Background(), wr)
printFromCtx(ctx) // pass logger contained context
func printFromCtx(ctx context.Context) {
// grab logger from context
wr := log.FromCtx(ctx)
// note that even if there is no logger inside context
// this won't cause any panic and just return nop-logger
// that will never write any logs
wr.Inf("my information")
// terminal: 2023-09-22T13:38:39.784+0700 INFO my information
// json: {"level":"INFO","time":"2023-09-22T13:38:39.784+0700","msg":"my information"}
}
Click to show internal directories.
Click to hide internal directories.