mirror of https://github.com/ethereum/go-ethereum
all: replace log15 with slog (#28187)
This PR replaces Geth's logger package (a fork of [log15](https://github.com/inconshreveable/log15)) with an implementation using slog, a logging library included as part of the Go standard library as of Go1.21. Main changes are as follows: * removes any log handlers that were unused in the Geth codebase. * Json, logfmt, and terminal formatters are now slog handlers. * Verbosity level constants are changed to match slog constant values. Internal translation is done to make this opaque to the user and backwards compatible with existing `--verbosity` and `--vmodule` options. * `--log.backtraceat` and `--log.debug` are removed. The external-facing API is largely the same as the existing Geth logger. Logger method signatures remain unchanged. A small semantic difference is that a `Handler` can only be set once per `Logger` and not changed dynamically. This just means that a new logger must be instantiated every time the handler of the root logger is changed. ---- For users of the `go-ethereum/log` module. If you were using this module for your own project, you will need to change the initialization. If you previously did ```golang log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) ``` You now instead need to do ```golang log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) ``` See more about reasoning here: https://github.com/ethereum/go-ethereum/issues/28558#issuecomment-1820606613pull/28630/head
parent
61b844f2b2
commit
28e7371701
@ -1,49 +1,51 @@ |
|||||||
{"111,222,333,444,555,678,999":"111222333444555678999","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464383209+01:00"} |
{"t":"2023-11-22T15:42:00.407963+08:00","lvl":"info","msg":"big.Int","111,222,333,444,555,678,999":"111222333444555678999"} |
||||||
{"-111,222,333,444,555,678,999":"-111222333444555678999","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.46455928+01:00"} |
{"t":"2023-11-22T15:42:00.408084+08:00","lvl":"info","msg":"-big.Int","-111,222,333,444,555,678,999":"-111222333444555678999"} |
||||||
{"11,122,233,344,455,567,899,900":"11122233344455567899900","lvl":"info","msg":"big.Int","t":"2023-11-09T08:33:19.464582073+01:00"} |
{"t":"2023-11-22T15:42:00.408092+08:00","lvl":"info","msg":"big.Int","11,122,233,344,455,567,899,900":"11122233344455567899900"} |
||||||
{"-11,122,233,344,455,567,899,900":"-11122233344455567899900","lvl":"info","msg":"-big.Int","t":"2023-11-09T08:33:19.464594846+01:00"} |
{"t":"2023-11-22T15:42:00.408097+08:00","lvl":"info","msg":"-big.Int","-11,122,233,344,455,567,899,900":"-11122233344455567899900"} |
||||||
{"111,222,333,444,555,678,999":"0x607851afc94ca2517","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464607873+01:00"} |
{"t":"2023-11-22T15:42:00.408127+08:00","lvl":"info","msg":"uint256","111,222,333,444,555,678,999":"111222333444555678999"} |
||||||
{"11,122,233,344,455,567,899,900":"0x25aeffe8aaa1ef67cfc","lvl":"info","msg":"uint256","t":"2023-11-09T08:33:19.464694639+01:00"} |
{"t":"2023-11-22T15:42:00.408133+08:00","lvl":"info","msg":"uint256","11,122,233,344,455,567,899,900":"11122233344455567899900"} |
||||||
{"1,000,000":1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464708835+01:00"} |
{"t":"2023-11-22T15:42:00.408137+08:00","lvl":"info","msg":"int64","1,000,000":1000000} |
||||||
{"-1,000,000":-1000000,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464725054+01:00"} |
{"t":"2023-11-22T15:42:00.408145+08:00","lvl":"info","msg":"int64","-1,000,000":-1000000} |
||||||
{"9,223,372,036,854,775,807":9223372036854775807,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464735773+01:00"} |
{"t":"2023-11-22T15:42:00.408149+08:00","lvl":"info","msg":"int64","9,223,372,036,854,775,807":9223372036854775807} |
||||||
{"-9,223,372,036,854,775,808":-9223372036854775808,"lvl":"info","msg":"int64","t":"2023-11-09T08:33:19.464744532+01:00"} |
{"t":"2023-11-22T15:42:00.408153+08:00","lvl":"info","msg":"int64","-9,223,372,036,854,775,808":-9223372036854775808} |
||||||
{"1,000,000":1000000,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464752807+01:00"} |
{"t":"2023-11-22T15:42:00.408156+08:00","lvl":"info","msg":"uint64","1,000,000":1000000} |
||||||
{"18,446,744,073,709,551,615":18446744073709551615,"lvl":"info","msg":"uint64","t":"2023-11-09T08:33:19.464779296+01:00"} |
{"t":"2023-11-22T15:42:00.40816+08:00","lvl":"info","msg":"uint64","18,446,744,073,709,551,615":18446744073709551615} |
||||||
{"key":"special \r\n\t chars","lvl":"info","msg":"Special chars in value","t":"2023-11-09T08:33:19.464794181+01:00"} |
{"t":"2023-11-22T15:42:00.408164+08:00","lvl":"info","msg":"Special chars in value","key":"special \r\n\t chars"} |
||||||
{"lvl":"info","msg":"Special chars in key","special \n\t chars":"value","t":"2023-11-09T08:33:19.464827197+01:00"} |
{"t":"2023-11-22T15:42:00.408167+08:00","lvl":"info","msg":"Special chars in key","special \n\t chars":"value"} |
||||||
{"lvl":"info","msg":"nospace","nospace":"nospace","t":"2023-11-09T08:33:19.464841118+01:00"} |
{"t":"2023-11-22T15:42:00.408171+08:00","lvl":"info","msg":"nospace","nospace":"nospace"} |
||||||
{"lvl":"info","msg":"with space","t":"2023-11-09T08:33:19.464862818+01:00","with nospace":"with nospace"} |
{"t":"2023-11-22T15:42:00.408174+08:00","lvl":"info","msg":"with space","with nospace":"with nospace"} |
||||||
{"key":"\u001b[1G\u001b[K\u001b[1A","lvl":"info","msg":"Bash escapes in value","t":"2023-11-09T08:33:19.464876802+01:00"} |
{"t":"2023-11-22T15:42:00.408178+08:00","lvl":"info","msg":"Bash escapes in value","key":"\u001b[1G\u001b[K\u001b[1A"} |
||||||
{"\u001b[1G\u001b[K\u001b[1A":"value","lvl":"info","msg":"Bash escapes in key","t":"2023-11-09T08:33:19.464885416+01:00"} |
{"t":"2023-11-22T15:42:00.408182+08:00","lvl":"info","msg":"Bash escapes in key","\u001b[1G\u001b[K\u001b[1A":"value"} |
||||||
{"key":"value","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","t":"2023-11-09T08:33:19.464906946+01:00"} |
{"t":"2023-11-22T15:42:00.408186+08:00","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","key":"value"} |
||||||
{"\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m[","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","t":"2023-11-09T08:33:19.464921455+01:00"} |
{"t":"2023-11-22T15:42:00.408194+08:00","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m["} |
||||||
{"2562047h47m16.854s":"2562047h47m16.854s","lvl":"info","msg":"Custom Stringer value","t":"2023-11-09T08:33:19.464943893+01:00"} |
{"t":"2023-11-22T15:42:00.408197+08:00","lvl":"info","msg":"an error message with quotes","error":"this is an 'error'"} |
||||||
{"key":"lazy value","lvl":"info","msg":"Lazy evaluation of value","t":"2023-11-09T08:33:19.465013552+01:00"} |
{"t":"2023-11-22T15:42:00.408202+08:00","lvl":"info","msg":"Custom Stringer value","2562047h47m16.854s":"2562047h47m16.854s"} |
||||||
{"lvl":"info","msg":"A message with wonky 💩 characters","t":"2023-11-09T08:33:19.465069437+01:00"} |
{"t":"2023-11-22T15:42:00.408208+08:00","lvl":"info","msg":"a custom stringer that emits quoted text","output":"output with 'quotes'"} |
||||||
{"lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩","t":"2023-11-09T08:33:19.465083053+01:00"} |
{"t":"2023-11-22T15:42:00.408215+08:00","lvl":"info","msg":"Lazy evaluation of value","key":"lazy value"} |
||||||
{"lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above","t":"2023-11-09T08:33:19.465104289+01:00"} |
{"t":"2023-11-22T15:42:00.408219+08:00","lvl":"info","msg":"A message with wonky 💩 characters"} |
||||||
{"false":"false","lvl":"info","msg":"boolean","t":"2023-11-09T08:33:19.465117185+01:00","true":"true"} |
{"t":"2023-11-22T15:42:00.408222+08:00","lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"} |
||||||
{"foo":"beta","lvl":"info","msg":"repeated-key 1","t":"2023-11-09T08:33:19.465143425+01:00"} |
{"t":"2023-11-22T15:42:00.408226+08:00","lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"} |
||||||
{"lvl":"info","msg":"repeated-key 2","t":"2023-11-09T08:33:19.465156323+01:00","xx":"longer"} |
{"t":"2023-11-22T15:42:00.408229+08:00","lvl":"info","msg":"boolean","true":true,"false":false} |
||||||
{"lvl":"info","msg":"log at level info","t":"2023-11-09T08:33:19.465193158+01:00"} |
{"t":"2023-11-22T15:42:00.408234+08:00","lvl":"info","msg":"repeated-key 1","foo":"alpha","foo":"beta"} |
||||||
{"lvl":"warn","msg":"log at level warn","t":"2023-11-09T08:33:19.465228964+01:00"} |
{"t":"2023-11-22T15:42:00.408237+08:00","lvl":"info","msg":"repeated-key 2","xx":"short","xx":"longer"} |
||||||
{"lvl":"eror","msg":"log at level error","t":"2023-11-09T08:33:19.465240352+01:00"} |
{"t":"2023-11-22T15:42:00.408241+08:00","lvl":"info","msg":"log at level info"} |
||||||
{"a":"aligned left","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465247226+01:00"} |
{"t":"2023-11-22T15:42:00.408244+08:00","lvl":"warn","msg":"log at level warn"} |
||||||
{"a":1,"bar":"a long message","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465269028+01:00"} |
{"t":"2023-11-22T15:42:00.408247+08:00","lvl":"eror","msg":"log at level error"} |
||||||
{"a":"aligned right","bar":"short","lvl":"info","msg":"test","t":"2023-11-09T08:33:19.465313611+01:00"} |
{"t":"2023-11-22T15:42:00.408251+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned left"} |
||||||
{"lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns","t":"2023-11-09T08:33:19.465328188+01:00"} |
{"t":"2023-11-22T15:42:00.408254+08:00","lvl":"info","msg":"test","bar":"a long message","a":1} |
||||||
{"gas":1123123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"info","msg":"Inserted known block","number":1012,"other":"first","t":"2023-11-09T08:33:19.465350507+01:00","txs":200} |
{"t":"2023-11-22T15:42:00.408258+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned right"} |
||||||
{"gas":1123,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","lvl":"info","msg":"Inserted new block","number":1,"other":"second","t":"2023-11-09T08:33:19.465387952+01:00","txs":2} |
{"t":"2023-11-22T15:42:00.408261+08:00","lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns"} |
||||||
{"gas":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","lvl":"info","msg":"Inserted known block","number":99,"other":"third","t":"2023-11-09T08:33:19.465406687+01:00","txs":10} |
{"t":"2023-11-22T15:42:00.408275+08:00","lvl":"info","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":1123123,"other":"first"} |
||||||
{"gas":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","lvl":"warn","msg":"Inserted known block","number":1012,"other":"fourth","t":"2023-11-09T08:33:19.465433025+01:00","txs":200} |
{"t":"2023-11-22T15:42:00.408281+08:00","lvl":"info","msg":"Inserted new block","number":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","txs":2,"gas":1123,"other":"second"} |
||||||
{"\u003cnil\u003e":"\u003cnil\u003e","lvl":"info","msg":"(*big.Int)(nil)","t":"2023-11-09T08:33:19.465450283+01:00"} |
{"t":"2023-11-22T15:42:00.408287+08:00","lvl":"info","msg":"Inserted known block","number":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","txs":10,"gas":1,"other":"third"} |
||||||
{"\u003cnil\u003e":"nil","lvl":"info","msg":"(*uint256.Int)(nil)","t":"2023-11-09T08:33:19.465472953+01:00"} |
{"t":"2023-11-22T15:42:00.408296+08:00","lvl":"warn","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":99,"other":"fourth"} |
||||||
{"lvl":"info","msg":"(fmt.Stringer)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465538633+01:00"} |
{"t":"2023-11-22T15:42:00.4083+08:00","lvl":"info","msg":"(*big.Int)(nil)","<nil>":"<nil>"} |
||||||
{"lvl":"info","msg":"nil-concrete-stringer","res":"nil","t":"2023-11-09T08:33:19.465552355+01:00"} |
{"t":"2023-11-22T15:42:00.408303+08:00","lvl":"info","msg":"(*uint256.Int)(nil)","<nil>":"<nil>"} |
||||||
{"lvl":"info","msg":"error(nil) ","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465601029+01:00"} |
{"t":"2023-11-22T15:42:00.408311+08:00","lvl":"info","msg":"(fmt.Stringer)(nil)","res":null} |
||||||
{"lvl":"info","msg":"nil-concrete-error","res":"","t":"2023-11-09T08:33:19.46561622+01:00"} |
{"t":"2023-11-22T15:42:00.408318+08:00","lvl":"info","msg":"nil-concrete-stringer","res":"<nil>"} |
||||||
{"lvl":"info","msg":"nil-custom-struct","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465638888+01:00"} |
{"t":"2023-11-22T15:42:00.408322+08:00","lvl":"info","msg":"error(nil) ","res":null} |
||||||
{"lvl":"info","msg":"raw nil","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465673664+01:00"} |
{"t":"2023-11-22T15:42:00.408326+08:00","lvl":"info","msg":"nil-concrete-error","res":""} |
||||||
{"lvl":"info","msg":"(*uint64)(nil)","res":"\u003cnil\u003e","t":"2023-11-09T08:33:19.465700264+01:00"} |
{"t":"2023-11-22T15:42:00.408334+08:00","lvl":"info","msg":"nil-custom-struct","res":null} |
||||||
{"level":"level","lvl":"lvl","msg":"msg","t":"t","time":"time"} |
{"t":"2023-11-22T15:42:00.40835+08:00","lvl":"info","msg":"raw nil","res":null} |
||||||
|
{"t":"2023-11-22T15:42:00.408354+08:00","lvl":"info","msg":"(*uint64)(nil)","res":null} |
||||||
|
{"t":"2023-11-22T15:42:00.408361+08:00","lvl":"info","msg":"Using keys 't', 'lvl', 'time', 'level' and 'msg'","t":"t","time":"time","lvl":"lvl","level":"level","msg":"msg"} |
||||||
|
@ -1,51 +1,51 @@ |
|||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111222333444555678999 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111222333444555678999 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11122233344455567899900 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11122233344455567899900 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111222333444555678999 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11122233344455567899900 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 1,000,000=1,000,000 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 1,000,000=1000000 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -1,000,000=-1,000,000 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -1,000,000=-1000000 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 9,223,372,036,854,775,807=9223372036854775807 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9223372036854775808 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 1,000,000=1,000,000 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 1,000,000=1000000 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18446744073709551615 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in value" key="special \r\n\t chars" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in value" key="special \r\n\t chars" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Special chars in key" "special \n\t chars"=value |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in key" "special \n\t chars"=value |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nospace nospace=nospace |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nospace nospace=nospace |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="with space" "with nospace"="with nospace" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="with space" "with nospace"="with nospace" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="an error message with quotes" error="this is an 'error'" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="an error message with quotes" error="this is an 'error'" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Lazy evaluation of value" key="lazy value" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Lazy evaluation of value" key="lazy value" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A message with wonky 💩 characters" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A message with wonky 💩 characters" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=boolean true=true false=false |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=boolean true=true false=false |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="repeated-key 2" xx=short xx=longer |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 2" xx=short xx=longer |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="log at level info" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="log at level info" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="log at level warn" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="log at level warn" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=eror msg="log at level error" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=eror msg="log at level error" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned left" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned left" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar="a long message" a=1 |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar="a long message" a=1 |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=test bar=short a="aligned right" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned right" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns" |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1,123,123 other=first |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1123123 other=first |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*big.Int)(nil) <nil>=<nil> |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*big.Int)(nil) <nil>=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint256.Int)(nil) <nil>=<nil> |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint256.Int)(nil) <nil>=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(fmt.Stringer)(nil) res=nil |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(fmt.Stringer)(nil) res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-stringer res=nil |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-stringer res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="error(nil) " res=nil |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="error(nil) " res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-concrete-error res= |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-error res="" |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=nil-custom-struct res=<nil> |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-custom-struct res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="raw nil" res=nil |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="raw nil" res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg=(*uint64)(nil) res=<nil> |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint64)(nil) res=<nil> |
||||||
t=xxxxxxxxxxxxxxxxxxxxxxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg |
t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg |
||||||
|
@ -1,52 +1,52 @@ |
|||||||
INFO [XX-XX|XX:XX:XX.XXX] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
INFO [xx-xx|xx:xx:xx.xxx] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999 |
INFO [xx-xx|xx:xx:xx.xxx] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
INFO [xx-xx|xx:xx:xx.xxx] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900 |
INFO [xx-xx|xx:xx:xx.xxx] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
INFO [xx-xx|xx:xx:xx.xxx] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
INFO [xx-xx|xx:xx:xx.xxx] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 1,000,000=1,000,000 |
INFO [xx-xx|xx:xx:xx.xxx] int64 1,000,000=1,000,000 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 -1,000,000=-1,000,000 |
INFO [xx-xx|xx:xx:xx.xxx] int64 -1,000,000=-1,000,000 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807 |
INFO [xx-xx|xx:xx:xx.xxx] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808 |
INFO [xx-xx|xx:xx:xx.xxx] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint64 1,000,000=1,000,000 |
INFO [xx-xx|xx:xx:xx.xxx] uint64 1,000,000=1,000,000 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615 |
INFO [xx-xx|xx:xx:xx.xxx] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Special chars in value key="special \r\n\t chars" |
INFO [xx-xx|xx:xx:xx.xxx] Special chars in value key="special \r\n\t chars" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Special chars in key "special \n\t chars"=value |
INFO [xx-xx|xx:xx:xx.xxx] Special chars in key "special \n\t chars"=value |
||||||
INFO [XX-XX|XX:XX:XX.XXX] nospace nospace=nospace |
INFO [xx-xx|xx:xx:xx.xxx] nospace nospace=nospace |
||||||
INFO [XX-XX|XX:XX:XX.XXX] with space "with nospace"="with nospace" |
INFO [xx-xx|xx:xx:xx.xxx] with space "with nospace"="with nospace" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A" |
INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value |
INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value |
||||||
INFO [XX-XX|XX:XX:XX.XXX] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value |
INFO [xx-xx|xx:xx:xx.xxx] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value |
||||||
INFO [XX-XX|XX:XX:XX.XXX] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" |
INFO [xx-xx|xx:xx:xx.xxx] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] an error message with quotes error="this is an 'error'" |
INFO [xx-xx|xx:xx:xx.xxx] an error message with quotes error="this is an 'error'" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s |
INFO [xx-xx|xx:xx:xx.xxx] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s |
||||||
INFO [XX-XX|XX:XX:XX.XXX] a custom stringer that emits quoted text output="output with 'quotes'" |
INFO [xx-xx|xx:xx:xx.xxx] a custom stringer that emits quoted text output="output with 'quotes'" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Lazy evaluation of value key="lazy value" |
INFO [xx-xx|xx:xx:xx.xxx] Lazy evaluation of value key="lazy value" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] "A message with wonky 💩 characters" |
INFO [xx-xx|xx:xx:xx.xxx] "A message with wonky 💩 characters" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" |
INFO [xx-xx|xx:xx:xx.xxx] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] A multiline message |
INFO [xx-xx|xx:xx:xx.xxx] A multiline message |
||||||
LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above |
LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above |
||||||
INFO [XX-XX|XX:XX:XX.XXX] boolean true=true false=false |
INFO [xx-xx|xx:xx:xx.xxx] boolean true=true false=false |
||||||
INFO [XX-XX|XX:XX:XX.XXX] repeated-key 1 foo=alpha foo=beta |
INFO [xx-xx|xx:xx:xx.xxx] repeated-key 1 foo=alpha foo=beta |
||||||
INFO [XX-XX|XX:XX:XX.XXX] repeated-key 2 xx=short xx=longer |
INFO [xx-xx|xx:xx:xx.xxx] repeated-key 2 xx=short xx=longer |
||||||
INFO [XX-XX|XX:XX:XX.XXX] log at level info |
INFO [xx-xx|xx:xx:xx.xxx] log at level info |
||||||
WARN [XX-XX|XX:XX:XX.XXX] log at level warn |
WARN [xx-xx|xx:xx:xx.xxx] log at level warn |
||||||
ERROR[XX-XX|XX:XX:XX.XXX] log at level error |
ERROR[xx-xx|xx:xx:xx.xxx] log at level error |
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned left" |
INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned left" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar="a long message" a=1 |
INFO [xx-xx|xx:xx:xx.xxx] test bar="a long message" a=1 |
||||||
INFO [XX-XX|XX:XX:XX.XXX] test bar=short a="aligned right" |
INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned right" |
||||||
INFO [XX-XX|XX:XX:XX.XXX] The following logs should align so that the key-fields make 5 columns |
INFO [xx-xx|xx:xx:xx.xxx] The following logs should align so that the key-fields make 5 columns |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first |
INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second |
INFO [xx-xx|xx:xx:xx.xxx] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third |
INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third |
||||||
WARN [XX-XX|XX:XX:XX.XXX] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth |
WARN [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth |
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*big.Int)(nil) <nil>=<nil> |
INFO [xx-xx|xx:xx:xx.xxx] (*big.Int)(nil) <nil>=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*uint256.Int)(nil) <nil>=<nil> |
INFO [xx-xx|xx:xx:xx.xxx] (*uint256.Int)(nil) <nil>=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] (fmt.Stringer)(nil) res=nil |
INFO [xx-xx|xx:xx:xx.xxx] (fmt.Stringer)(nil) res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-stringer res=nil |
INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-stringer res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] error(nil) res=nil |
INFO [xx-xx|xx:xx:xx.xxx] error(nil) res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-concrete-error res= |
INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-error res= |
||||||
INFO [XX-XX|XX:XX:XX.XXX] nil-custom-struct res=<nil> |
INFO [xx-xx|xx:xx:xx.xxx] nil-custom-struct res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] raw nil res=nil |
INFO [xx-xx|xx:xx:xx.xxx] raw nil res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] (*uint64)(nil) res=<nil> |
INFO [xx-xx|xx:xx:xx.xxx] (*uint64)(nil) res=<nil> |
||||||
INFO [XX-XX|XX:XX:XX.XXX] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg |
INFO [xx-xx|xx:xx:xx.xxx] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg |
||||||
|
@ -1,11 +0,0 @@ |
|||||||
Contributors to log15: |
|
||||||
|
|
||||||
- Aaron L |
|
||||||
- Alan Shreve |
|
||||||
- Chris Hines |
|
||||||
- Ciaran Downey |
|
||||||
- Dmitry Chestnykh |
|
||||||
- Evan Shaw |
|
||||||
- Péter Szilágyi |
|
||||||
- Trevor Gattis |
|
||||||
- Vincent Vanackere |
|
@ -1,13 +0,0 @@ |
|||||||
Copyright 2014 Alan Shreve |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0 |
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
@ -1,77 +0,0 @@ |
|||||||
![obligatory xkcd](https://imgs.xkcd.com/comics/standards.png) |
|
||||||
|
|
||||||
# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15) |
|
||||||
|
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](https://golang.org/pkg/io/) and [`net/http`](https://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](https://golang.org/pkg/log/) package. |
|
||||||
|
|
||||||
## Features |
|
||||||
- A simple, easy-to-understand API |
|
||||||
- Promotes structured logging by encouraging use of key/value pairs |
|
||||||
- Child loggers which inherit and add their own private context |
|
||||||
- Lazy evaluation of expensive operations |
|
||||||
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. |
|
||||||
- Color terminal support |
|
||||||
- Built-in support for logging to files, streams, syslog, and the network |
|
||||||
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more |
|
||||||
|
|
||||||
## Versioning |
|
||||||
The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, |
|
||||||
you must vendor the library. |
|
||||||
|
|
||||||
## Importing |
|
||||||
|
|
||||||
```go |
|
||||||
import log "github.com/inconshreveable/log15" |
|
||||||
``` |
|
||||||
|
|
||||||
## Examples |
|
||||||
|
|
||||||
```go |
|
||||||
// all loggers can have key/value context |
|
||||||
srvlog := log.New("module", "app/server") |
|
||||||
|
|
||||||
// all log messages can have key/value context |
|
||||||
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) |
|
||||||
|
|
||||||
// child loggers with inherited context |
|
||||||
connlog := srvlog.New("raddr", c.RemoteAddr()) |
|
||||||
connlog.Info("connection open") |
|
||||||
|
|
||||||
// lazy evaluation |
|
||||||
connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) |
|
||||||
|
|
||||||
// flexible configuration |
|
||||||
srvlog.SetHandler(log.MultiHandler( |
|
||||||
log.StreamHandler(os.Stderr, log.LogfmtFormat()), |
|
||||||
log.LvlFilterHandler( |
|
||||||
log.LvlError, |
|
||||||
log.Must.FileHandler("errors.json", log.JSONFormat())))) |
|
||||||
``` |
|
||||||
|
|
||||||
Will result in output that looks like this: |
|
||||||
|
|
||||||
``` |
|
||||||
WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 |
|
||||||
INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 |
|
||||||
``` |
|
||||||
|
|
||||||
## Breaking API Changes |
|
||||||
The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version |
|
||||||
of log15. |
|
||||||
|
|
||||||
- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler |
|
||||||
- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` |
|
||||||
- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors |
|
||||||
|
|
||||||
## FAQ |
|
||||||
|
|
||||||
### The varargs style is brittle and error prone! Can I have type safety please? |
|
||||||
Yes. Use `log.Ctx`: |
|
||||||
|
|
||||||
```go |
|
||||||
srvlog := log.New(log.Ctx{"module": "app/server"}) |
|
||||||
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) |
|
||||||
``` |
|
||||||
|
|
||||||
## License |
|
||||||
Apache |
|
@ -1,5 +0,0 @@ |
|||||||
This package is a fork of https://github.com/inconshreveable/log15, with some |
|
||||||
minor modifications required by the go-ethereum codebase: |
|
||||||
|
|
||||||
* Support for log level `trace` |
|
||||||
* Modified behavior to exit on `critical` failure |
|
@ -1,327 +0,0 @@ |
|||||||
/* |
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging that is |
|
||||||
both human and machine readable. It is modeled after the standard library's io and net/http |
|
||||||
packages. |
|
||||||
|
|
||||||
This package enforces you to only log key/value pairs. Keys must be strings. Values may be |
|
||||||
any type that you like. The default output format is logfmt, but you may also choose to use |
|
||||||
JSON instead if that suits you. Here's how you log: |
|
||||||
|
|
||||||
log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 |
|
||||||
|
|
||||||
# Getting Started |
|
||||||
|
|
||||||
To get started, you'll want to import the library: |
|
||||||
|
|
||||||
import log "github.com/inconshreveable/log15" |
|
||||||
|
|
||||||
Now you're ready to start logging: |
|
||||||
|
|
||||||
func main() { |
|
||||||
log.Info("Program starting", "args", os.Args()) |
|
||||||
} |
|
||||||
|
|
||||||
# Convention |
|
||||||
|
|
||||||
Because recording a human-meaningful message is common and good practice, the first argument to every |
|
||||||
logging method is the value to the *implicit* key 'msg'. |
|
||||||
|
|
||||||
Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so |
|
||||||
will the current timestamp with key 't'. |
|
||||||
|
|
||||||
You may supply any additional context as a set of key/value pairs to the logging function. log15 allows |
|
||||||
you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for |
|
||||||
logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate |
|
||||||
in the variadic argument list: |
|
||||||
|
|
||||||
log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) |
|
||||||
|
|
||||||
If you really do favor your type-safety, you may choose to pass a log.Ctx instead: |
|
||||||
|
|
||||||
log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) |
|
||||||
|
|
||||||
# Context loggers |
|
||||||
|
|
||||||
Frequently, you want to add context to a logger so that you can track actions associated with it. An http |
|
||||||
request is a good example. You can easily create new loggers that have context that is automatically included |
|
||||||
with each log line: |
|
||||||
|
|
||||||
requestlogger := log.New("path", r.URL.Path) |
|
||||||
|
|
||||||
// later
|
|
||||||
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) |
|
||||||
|
|
||||||
This will output a log line that includes the path context that is attached to the logger: |
|
||||||
|
|
||||||
lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 |
|
||||||
|
|
||||||
# Handlers |
|
||||||
|
|
||||||
The Handler interface defines where log lines are printed to and how they are formatted. Handler is a |
|
||||||
single interface that is inspired by net/http's handler interface: |
|
||||||
|
|
||||||
type Handler interface { |
|
||||||
Log(r *Record) error |
|
||||||
} |
|
||||||
|
|
||||||
Handlers can filter records, format them, or dispatch to multiple other Handlers. |
|
||||||
This package implements a number of Handlers for common logging patterns that are |
|
||||||
easily composed to create flexible, custom logging structures. |
|
||||||
|
|
||||||
Here's an example handler that prints logfmt output to Stdout: |
|
||||||
|
|
||||||
handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) |
|
||||||
|
|
||||||
Here's an example handler that defers to two other handlers. One handler only prints records |
|
||||||
from the rpc package in logfmt to standard out. The other prints records at Error level |
|
||||||
or above in JSON formatted output to the file /var/log/service.json |
|
||||||
|
|
||||||
handler := log.MultiHandler( |
|
||||||
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())), |
|
||||||
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) |
|
||||||
) |
|
||||||
|
|
||||||
# Logging File Names and Line Numbers |
|
||||||
|
|
||||||
This package implements three Handlers that add debugging information to the |
|
||||||
context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's |
|
||||||
an example that adds the source file and line number of each logging call to |
|
||||||
the context. |
|
||||||
|
|
||||||
h := log.CallerFileHandler(log.StdoutHandler) |
|
||||||
log.Root().SetHandler(h) |
|
||||||
... |
|
||||||
log.Error("open file", "err", err) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 |
|
||||||
|
|
||||||
Here's an example that logs the call stack rather than just the call site. |
|
||||||
|
|
||||||
h := log.CallerStackHandler("%+v", log.StdoutHandler) |
|
||||||
log.Root().SetHandler(h) |
|
||||||
... |
|
||||||
log.Error("open file", "err", err) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" |
|
||||||
|
|
||||||
The "%+v" format instructs the handler to include the path of the source file |
|
||||||
relative to the compile time GOPATH. The github.com/go-stack/stack package |
|
||||||
documents the full list of formatting verbs and modifiers available. |
|
||||||
|
|
||||||
# Custom Handlers |
|
||||||
|
|
||||||
The Handler interface is so simple that it's also trivial to write your own. Let's create an |
|
||||||
example handler which tries to write to one handler, but if that fails it falls back to |
|
||||||
writing to another handler and includes the error that it encountered when trying to write |
|
||||||
to the primary. This might be useful when trying to log over a network socket, but if that |
|
||||||
fails you want to log those records to a file on disk. |
|
||||||
|
|
||||||
type BackupHandler struct { |
|
||||||
Primary Handler |
|
||||||
Secondary Handler |
|
||||||
} |
|
||||||
|
|
||||||
func (h *BackupHandler) Log (r *Record) error { |
|
||||||
err := h.Primary.Log(r) |
|
||||||
if err != nil { |
|
||||||
r.Ctx = append(ctx, "primary_err", err) |
|
||||||
return h.Secondary.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
This pattern is so useful that a generic version that handles an arbitrary number of Handlers |
|
||||||
is included as part of this library called FailoverHandler. |
|
||||||
|
|
||||||
# Logging Expensive Operations |
|
||||||
|
|
||||||
Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay |
|
||||||
the price of computing them if you haven't turned up your logging level to a high level of detail. |
|
||||||
|
|
||||||
This package provides a simple type to annotate a logging operation that you want to be evaluated |
|
||||||
lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler |
|
||||||
filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: |
|
||||||
|
|
||||||
func factorRSAKey() (factors []int) { |
|
||||||
// return the factors of a very large number
|
|
||||||
} |
|
||||||
|
|
||||||
log.Debug("factors", log.Lazy{factorRSAKey}) |
|
||||||
|
|
||||||
If this message is not logged for any reason (like logging at the Error level), then |
|
||||||
factorRSAKey is never evaluated. |
|
||||||
|
|
||||||
# Dynamic context values |
|
||||||
|
|
||||||
The same log.Lazy mechanism can be used to attach context to a logger which you want to be |
|
||||||
evaluated when the message is logged, but not when the logger is created. For example, let's imagine |
|
||||||
a game where you have Player objects: |
|
||||||
|
|
||||||
type Player struct { |
|
||||||
name string |
|
||||||
alive bool |
|
||||||
log.Logger |
|
||||||
} |
|
||||||
|
|
||||||
You always want to log a player's name and whether they're alive or dead, so when you create the player |
|
||||||
object, you might do: |
|
||||||
|
|
||||||
p := &Player{name: name, alive: true} |
|
||||||
p.Logger = log.New("name", p.name, "alive", p.alive) |
|
||||||
|
|
||||||
Only now, even after a player has died, the logger will still report they are alive because the logging |
|
||||||
context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation |
|
||||||
of whether the player is alive or not to each log message, so that the log records will reflect the player's |
|
||||||
current state no matter when the log message is written: |
|
||||||
|
|
||||||
p := &Player{name: name, alive: true} |
|
||||||
isAlive := func() bool { return p.alive } |
|
||||||
player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) |
|
||||||
|
|
||||||
# Terminal Format |
|
||||||
|
|
||||||
If log15 detects that stdout is a terminal, it will configure the default |
|
||||||
handler for it (which is log.StdoutHandler) to use TerminalFormat. This format |
|
||||||
logs records nicely for your terminal, including color-coded output based |
|
||||||
on log level. |
|
||||||
|
|
||||||
# Error Handling |
|
||||||
|
|
||||||
Becasuse log15 allows you to step around the type system, there are a few ways you can specify |
|
||||||
invalid arguments to the logging functions. You could, for example, wrap something that is not |
|
||||||
a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries |
|
||||||
are typically the mechanism by which errors are reported, it would be onerous for the logging functions |
|
||||||
to return errors. Instead, log15 handles errors by making these guarantees to you: |
|
||||||
|
|
||||||
- Any log record containing an error will still be printed with the error explained to you as part of the log record. |
|
||||||
|
|
||||||
- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily |
|
||||||
(and if you like, automatically) detect if any of your logging calls are passing bad values. |
|
||||||
|
|
||||||
Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers |
|
||||||
are encouraged to return errors only if they fail to write their log records out to an external source like if the |
|
||||||
syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures |
|
||||||
like the FailoverHandler. |
|
||||||
|
|
||||||
# Library Use |
|
||||||
|
|
||||||
log15 is intended to be useful for library authors as a way to provide configurable logging to |
|
||||||
users of their library. Best practice for use in a library is to always disable all output for your logger |
|
||||||
by default and to provide a public Logger instance that consumers of your library can configure. Like so: |
|
||||||
|
|
||||||
package yourlib |
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15" |
|
||||||
|
|
||||||
var Log = log.New() |
|
||||||
|
|
||||||
func init() { |
|
||||||
Log.SetHandler(log.DiscardHandler()) |
|
||||||
} |
|
||||||
|
|
||||||
Users of your library may then enable it if they like: |
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15" |
|
||||||
import "example.com/yourlib" |
|
||||||
|
|
||||||
func main() { |
|
||||||
handler := // custom handler setup
|
|
||||||
yourlib.Log.SetHandler(handler) |
|
||||||
} |
|
||||||
|
|
||||||
# Best practices attaching logger context |
|
||||||
|
|
||||||
The ability to attach context to a logger is a powerful one. Where should you do it and why? |
|
||||||
I favor embedding a Logger directly into any persistent object in my application and adding |
|
||||||
unique, tracing context keys to it. For instance, imagine I am writing a web browser: |
|
||||||
|
|
||||||
type Tab struct { |
|
||||||
url string |
|
||||||
render *RenderingContext |
|
||||||
// ...
|
|
||||||
|
|
||||||
Logger |
|
||||||
} |
|
||||||
|
|
||||||
func NewTab(url string) *Tab { |
|
||||||
return &Tab { |
|
||||||
// ...
|
|
||||||
url: url, |
|
||||||
|
|
||||||
Logger: log.New("url", url), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
When a new tab is created, I assign a logger to it with the url of |
|
||||||
the tab as context so it can easily be traced through the logs. |
|
||||||
Now, whenever we perform any operation with the tab, we'll log with its |
|
||||||
embedded logger and it will include the tab title automatically: |
|
||||||
|
|
||||||
tab.Debug("moved position", "idx", tab.idx) |
|
||||||
|
|
||||||
There's only one problem. What if the tab url changes? We could |
|
||||||
use log.Lazy to make sure the current url is always written, but that |
|
||||||
would mean that we couldn't trace a tab's full lifetime through our |
|
||||||
logs after the user navigate to a new URL. |
|
||||||
|
|
||||||
Instead, think about what values to attach to your loggers the |
|
||||||
same way you think about what to use as a key in a SQL database schema. |
|
||||||
If it's possible to use a natural key that is unique for the lifetime of the |
|
||||||
object, do so. But otherwise, log15's ext package has a handy RandId |
|
||||||
function to let you generate what you might call "surrogate keys" |
|
||||||
They're just random hex identifiers to use for tracing. Back to our |
|
||||||
Tab example, we would prefer to set up our Logger like so: |
|
||||||
|
|
||||||
import logext "github.com/inconshreveable/log15/ext" |
|
||||||
|
|
||||||
t := &Tab { |
|
||||||
// ...
|
|
||||||
url: url, |
|
||||||
} |
|
||||||
|
|
||||||
t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) |
|
||||||
return t |
|
||||||
|
|
||||||
Now we'll have a unique traceable identifier even across loading new urls, but |
|
||||||
we'll still be able to see the tab's current url in the log messages. |
|
||||||
|
|
||||||
# Must |
|
||||||
|
|
||||||
For all Handler functions which can return an error, there is a version of that |
|
||||||
function which will return no error but panics on failure. They are all available |
|
||||||
on the Must object. For example: |
|
||||||
|
|
||||||
log.Must.FileHandler("/path", log.JSONFormat) |
|
||||||
log.Must.NetHandler("tcp", ":1234", log.JSONFormat) |
|
||||||
|
|
||||||
# Inspiration and Credit |
|
||||||
|
|
||||||
All of the following excellent projects inspired the design of this library: |
|
||||||
|
|
||||||
code.google.com/p/log4go |
|
||||||
|
|
||||||
github.com/op/go-logging |
|
||||||
|
|
||||||
github.com/technoweenie/grohl |
|
||||||
|
|
||||||
github.com/Sirupsen/logrus |
|
||||||
|
|
||||||
github.com/kr/logfmt |
|
||||||
|
|
||||||
github.com/spacemonkeygo/spacelog |
|
||||||
|
|
||||||
golang's stdlib, notably io and net/http |
|
||||||
|
|
||||||
# The Name |
|
||||||
|
|
||||||
https://xkcd.com/927/
|
|
||||||
*/ |
|
||||||
package log |
|
@ -1,375 +1,223 @@ |
|||||||
package log |
package log |
||||||
|
|
||||||
import ( |
import ( |
||||||
|
"context" |
||||||
"fmt" |
"fmt" |
||||||
"io" |
"io" |
||||||
"net" |
"math/big" |
||||||
"os" |
|
||||||
"reflect" |
"reflect" |
||||||
"sync" |
"sync" |
||||||
"sync/atomic" |
"time" |
||||||
|
|
||||||
"github.com/go-stack/stack" |
"github.com/holiman/uint256" |
||||||
|
"golang.org/x/exp/slog" |
||||||
) |
) |
||||||
|
|
||||||
// Handler defines where and how log records are written.
|
// Lazy allows you to defer calculation of a logged value that is expensive
|
||||||
// A Logger prints its log records by writing to a Handler.
|
// to compute until it is certain that it must be evaluated with the given filters.
|
||||||
// Handlers are composable, providing you great flexibility in combining
|
|
||||||
// them to achieve the logging structure that suits your applications.
|
|
||||||
type Handler interface { |
|
||||||
Log(r *Record) error |
|
||||||
} |
|
||||||
|
|
||||||
// FuncHandler returns a Handler that logs records with the given
|
|
||||||
// function.
|
|
||||||
func FuncHandler(fn func(r *Record) error) Handler { |
|
||||||
return funcHandler(fn) |
|
||||||
} |
|
||||||
|
|
||||||
type funcHandler func(r *Record) error |
|
||||||
|
|
||||||
func (h funcHandler) Log(r *Record) error { |
|
||||||
return h(r) |
|
||||||
} |
|
||||||
|
|
||||||
// StreamHandler writes log records to an io.Writer
|
|
||||||
// with the given format. StreamHandler can be used
|
|
||||||
// to easily begin writing log records to other
|
|
||||||
// outputs.
|
|
||||||
//
|
//
|
||||||
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
// You may wrap any function which takes no arguments to Lazy. It may return any
|
||||||
// to evaluate Lazy objects and perform safe concurrent writes.
|
// number of values of any type.
|
||||||
func StreamHandler(wr io.Writer, fmtr Format) Handler { |
type Lazy struct { |
||||||
h := FuncHandler(func(r *Record) error { |
Fn interface{} |
||||||
_, err := wr.Write(fmtr.Format(r)) |
|
||||||
return err |
|
||||||
}) |
|
||||||
return LazyHandler(SyncHandler(h)) |
|
||||||
} |
} |
||||||
|
|
||||||
// SyncHandler can be wrapped around a handler to guarantee that
|
func evaluateLazy(lz Lazy) (interface{}, error) { |
||||||
// only a single Log operation can proceed at a time. It's necessary
|
t := reflect.TypeOf(lz.Fn) |
||||||
// for thread-safe concurrent writes.
|
|
||||||
func SyncHandler(h Handler) Handler { |
|
||||||
var mu sync.Mutex |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
mu.Lock() |
|
||||||
defer mu.Unlock() |
|
||||||
|
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// FileHandler returns a handler which writes log records to the give file
|
if t.Kind() != reflect.Func { |
||||||
// using the given format. If the path
|
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) |
||||||
// already exists, FileHandler will append to the given file. If it does not,
|
|
||||||
// FileHandler will create the file with mode 0644.
|
|
||||||
func FileHandler(path string, fmtr Format) (Handler, error) { |
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
} |
||||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// NetHandler opens a socket to the given address and writes records
|
if t.NumIn() > 0 { |
||||||
// over the connection.
|
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) |
||||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) { |
|
||||||
conn, err := net.Dial(network, addr) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
} |
||||||
|
|
||||||
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil |
if t.NumOut() == 0 { |
||||||
} |
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) |
||||||
|
} |
||||||
// XXX: closingHandler is essentially unused at the moment
|
|
||||||
// it's meant for a future time when the Handler interface supports
|
|
||||||
// a possible Close() operation
|
|
||||||
type closingHandler struct { |
|
||||||
io.WriteCloser |
|
||||||
Handler |
|
||||||
} |
|
||||||
|
|
||||||
func (h *closingHandler) Close() error { |
value := reflect.ValueOf(lz.Fn) |
||||||
return h.WriteCloser.Close() |
results := value.Call([]reflect.Value{}) |
||||||
|
if len(results) == 1 { |
||||||
|
return results[0].Interface(), nil |
||||||
|
} |
||||||
|
values := make([]interface{}, len(results)) |
||||||
|
for i, v := range results { |
||||||
|
values[i] = v.Interface() |
||||||
|
} |
||||||
|
return values, nil |
||||||
} |
} |
||||||
|
|
||||||
// CallerFileHandler returns a Handler that adds the line number and file of
|
type discardHandler struct{} |
||||||
// the calling function to the context with key "caller".
|
|
||||||
func CallerFileHandler(h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// CallerFuncHandler returns a Handler that adds the calling function name to
|
// DiscardHandler returns a no-op handler
|
||||||
// the context with key "fn".
|
func DiscardHandler() slog.Handler { |
||||||
func CallerFuncHandler(h Handler) Handler { |
return &discardHandler{} |
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call)) |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// This function is here to please go vet on Go < 1.8.
|
func (h *discardHandler) Handle(_ context.Context, r slog.Record) error { |
||||||
func formatCall(format string, c stack.Call) string { |
return nil |
||||||
return fmt.Sprintf(format, c) |
|
||||||
} |
} |
||||||
|
|
||||||
// CallerStackHandler returns a Handler that adds a stack trace to the context
|
func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool { |
||||||
// with key "stack". The stack trace is formatted as a space separated list of
|
return false |
||||||
// call sites inside matching []'s. The most recent call site is listed first.
|
|
||||||
// Each call site is formatted according to format. See the documentation of
|
|
||||||
// package github.com/go-stack/stack for the list of supported formats.
|
|
||||||
func CallerStackHandler(format string, h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
s := stack.Trace().TrimBelow(r.Call).TrimRuntime() |
|
||||||
if len(s) > 0 { |
|
||||||
r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) |
|
||||||
} |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// FilterHandler returns a Handler that only writes records to the
|
func (h *discardHandler) WithGroup(name string) slog.Handler { |
||||||
// wrapped Handler if the given function evaluates true. For example,
|
panic("not implemented") |
||||||
// to only log records where the 'err' key is not nil:
|
|
||||||
//
|
|
||||||
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
|
||||||
// for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
// if r.Ctx[i] == "err" {
|
|
||||||
// return r.Ctx[i+1] != nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }, h))
|
|
||||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
if fn(r) { |
|
||||||
return h.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// MatchFilterHandler returns a Handler that only writes records
|
func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { |
||||||
// to the wrapped Handler if the given key in the logged
|
return &discardHandler{} |
||||||
// context matches the value. For example, to only log records
|
|
||||||
// from your ui package:
|
|
||||||
//
|
|
||||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
|
||||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler { |
|
||||||
return FilterHandler(func(r *Record) (pass bool) { |
|
||||||
switch key { |
|
||||||
case r.KeyNames.Lvl: |
|
||||||
return r.Lvl == value |
|
||||||
case r.KeyNames.Time: |
|
||||||
return r.Time == value |
|
||||||
case r.KeyNames.Msg: |
|
||||||
return r.Msg == value |
|
||||||
} |
|
||||||
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 { |
|
||||||
if r.Ctx[i] == key { |
|
||||||
return r.Ctx[i+1] == value |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
}, h) |
|
||||||
} |
} |
||||||
|
|
||||||
// LvlFilterHandler returns a Handler that only writes
|
type TerminalHandler struct { |
||||||
// records which are less than the given verbosity
|
mu sync.Mutex |
||||||
// level to the wrapped Handler. For example, to only
|
wr io.Writer |
||||||
// log Error/Crit records:
|
lvl slog.Level |
||||||
//
|
useColor bool |
||||||
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
attrs []slog.Attr |
||||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { |
// fieldPadding is a map with maximum field value lengths seen until now
|
||||||
return FilterHandler(func(r *Record) (pass bool) { |
// to allow padding log contexts in a bit smarter way.
|
||||||
return r.Lvl <= maxLvl |
fieldPadding map[string]int |
||||||
}, h) |
|
||||||
} |
} |
||||||
|
|
||||||
// MultiHandler dispatches any write to each of its handlers.
|
// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on
|
||||||
// This is useful for writing different types of log information
|
// a terminal with color-coded level output and terser human friendly timestamp.
|
||||||
// to different locations. For example, to log to a file and
|
// This format should only be used for interactive programs or while developing.
|
||||||
// standard error:
|
|
||||||
//
|
//
|
||||||
// log.MultiHandler(
|
// [LEVEL] [TIME] MESSAGE key=value key=value ...
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StderrHandler)
|
|
||||||
func MultiHandler(hs ...Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
for _, h := range hs { |
|
||||||
// what to do about failures?
|
|
||||||
h.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// FailoverHandler writes all log records to the first handler
|
|
||||||
// specified, but will failover and write to the second handler if
|
|
||||||
// the first handler has failed, and so on for all handlers specified.
|
|
||||||
// For example you might want to log to a network socket, but failover
|
|
||||||
// to writing to a file if the network fails, and then to
|
|
||||||
// standard out if the file write fails:
|
|
||||||
//
|
//
|
||||||
// log.FailoverHandler(
|
// Example:
|
||||||
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StdoutHandler)
|
|
||||||
//
|
//
|
||||||
// All writes that do not go to the first handler will add context with keys of
|
// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
|
||||||
// the form "failover_err_{idx}" which explain the error encountered while
|
func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler { |
||||||
// trying to write to the handlers before them in the list.
|
return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor) |
||||||
func FailoverHandler(hs ...Handler) Handler { |
} |
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
var err error |
// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs
|
||||||
for i, h := range hs { |
// records which are less than or equal to the specified verbosity level.
|
||||||
err = h.Log(r) |
func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler { |
||||||
if err == nil { |
return &TerminalHandler{ |
||||||
return nil |
wr: wr, |
||||||
} |
lvl: lvl, |
||||||
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) |
useColor: useColor, |
||||||
} |
fieldPadding: make(map[string]int), |
||||||
|
} |
||||||
return err |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// ChannelHandler writes all records to the given channel.
|
func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error { |
||||||
// It blocks if the channel is full. Useful for async processing
|
h.mu.Lock() |
||||||
// of log messages, it's used by BufferedHandler.
|
defer h.mu.Unlock() |
||||||
func ChannelHandler(recs chan<- *Record) Handler { |
h.wr.Write(h.TerminalFormat(r, h.useColor)) |
||||||
return FuncHandler(func(r *Record) error { |
return nil |
||||||
recs <- r |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// BufferedHandler writes all records to a buffered
|
func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool { |
||||||
// channel of the given size which flushes into the wrapped
|
return level >= h.lvl |
||||||
// handler whenever it is available for writing. Since these
|
|
||||||
// writes happen asynchronously, all writes to a BufferedHandler
|
|
||||||
// never return an error and any errors from the wrapped handler are ignored.
|
|
||||||
func BufferedHandler(bufSize int, h Handler) Handler { |
|
||||||
recs := make(chan *Record, bufSize) |
|
||||||
go func() { |
|
||||||
for m := range recs { |
|
||||||
_ = h.Log(m) |
|
||||||
} |
|
||||||
}() |
|
||||||
return ChannelHandler(recs) |
|
||||||
} |
} |
||||||
|
|
||||||
// LazyHandler writes all values to the wrapped handler after evaluating
|
func (h *TerminalHandler) WithGroup(name string) slog.Handler { |
||||||
// any lazy functions in the record's context. It is already wrapped
|
panic("not implemented") |
||||||
// around StreamHandler and SyslogHandler in this library, you'll only need
|
|
||||||
// it if you write your own Handler.
|
|
||||||
func LazyHandler(h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
// go through the values (odd indices) and reassign
|
|
||||||
// the values of any lazy fn to the result of its execution
|
|
||||||
hadErr := false |
|
||||||
for i := 1; i < len(r.Ctx); i += 2 { |
|
||||||
lz, ok := r.Ctx[i].(Lazy) |
|
||||||
if ok { |
|
||||||
v, err := evaluateLazy(lz) |
|
||||||
if err != nil { |
|
||||||
hadErr = true |
|
||||||
r.Ctx[i] = err |
|
||||||
} else { |
|
||||||
if cs, ok := v.(stack.CallStack); ok { |
|
||||||
v = cs.TrimBelow(r.Call).TrimRuntime() |
|
||||||
} |
|
||||||
r.Ctx[i] = v |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if hadErr { |
|
||||||
r.Ctx = append(r.Ctx, errorKey, "bad lazy") |
|
||||||
} |
|
||||||
|
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
func evaluateLazy(lz Lazy) (interface{}, error) { |
func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler { |
||||||
t := reflect.TypeOf(lz.Fn) |
return &TerminalHandler{ |
||||||
|
wr: h.wr, |
||||||
if t.Kind() != reflect.Func { |
lvl: h.lvl, |
||||||
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) |
useColor: h.useColor, |
||||||
} |
attrs: append(h.attrs, attrs...), |
||||||
|
fieldPadding: make(map[string]int), |
||||||
if t.NumIn() > 0 { |
|
||||||
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) |
|
||||||
} |
} |
||||||
|
|
||||||
if t.NumOut() == 0 { |
|
||||||
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) |
|
||||||
} |
|
||||||
|
|
||||||
value := reflect.ValueOf(lz.Fn) |
|
||||||
results := value.Call([]reflect.Value{}) |
|
||||||
if len(results) == 1 { |
|
||||||
return results[0].Interface(), nil |
|
||||||
} |
|
||||||
values := make([]interface{}, len(results)) |
|
||||||
for i, v := range results { |
|
||||||
values[i] = v.Interface() |
|
||||||
} |
|
||||||
return values, nil |
|
||||||
} |
} |
||||||
|
|
||||||
// DiscardHandler reports success for all writes but does nothing.
|
// ResetFieldPadding zeroes the field-padding for all attribute pairs.
|
||||||
// It is useful for dynamically disabling logging at runtime via
|
func (t *TerminalHandler) ResetFieldPadding() { |
||||||
// a Logger's SetHandler method.
|
t.mu.Lock() |
||||||
func DiscardHandler() Handler { |
t.fieldPadding = make(map[string]int) |
||||||
return FuncHandler(func(r *Record) error { |
t.mu.Unlock() |
||||||
return nil |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
// Must provides the following Handler creation functions
|
type leveler struct{ minLevel slog.Level } |
||||||
// which instead of returning an error parameter only return a Handler
|
|
||||||
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
|
|
||||||
var Must muster |
|
||||||
|
|
||||||
func must(h Handler, err error) Handler { |
func (l *leveler) Level() slog.Level { |
||||||
if err != nil { |
return l.minLevel |
||||||
panic(err) |
|
||||||
} |
|
||||||
return h |
|
||||||
} |
} |
||||||
|
|
||||||
type muster struct{} |
func JSONHandler(wr io.Writer) slog.Handler { |
||||||
|
return slog.NewJSONHandler(wr, &slog.HandlerOptions{ |
||||||
func (m muster) FileHandler(path string, fmtr Format) Handler { |
ReplaceAttr: builtinReplaceJSON, |
||||||
return must(FileHandler(path, fmtr)) |
}) |
||||||
} |
} |
||||||
|
|
||||||
func (m muster) NetHandler(network, addr string, fmtr Format) Handler { |
// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable
|
||||||
return must(NetHandler(network, addr, fmtr)) |
// format for key/value pairs.
|
||||||
|
//
|
||||||
|
// For more details see: http://godoc.org/github.com/kr/logfmt
|
||||||
|
func LogfmtHandler(wr io.Writer) slog.Handler { |
||||||
|
return slog.NewTextHandler(wr, &slog.HandlerOptions{ |
||||||
|
ReplaceAttr: builtinReplaceLogfmt, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
// swapHandler wraps another handler that may be swapped out
|
// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs
|
||||||
// dynamically at runtime in a thread-safe fashion.
|
// records which are less than or equal to the specified verbosity level.
|
||||||
type swapHandler struct { |
func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { |
||||||
handler atomic.Value |
return slog.NewTextHandler(wr, &slog.HandlerOptions{ |
||||||
|
ReplaceAttr: builtinReplaceLogfmt, |
||||||
|
Level: &leveler{level}, |
||||||
|
}) |
||||||
} |
} |
||||||
|
|
||||||
func (h *swapHandler) Log(r *Record) error { |
func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr { |
||||||
return (*h.handler.Load().(*Handler)).Log(r) |
return builtinReplace(nil, attr, true) |
||||||
} |
} |
||||||
|
|
||||||
func (h *swapHandler) Swap(newHandler Handler) { |
func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr { |
||||||
h.handler.Store(&newHandler) |
return builtinReplace(nil, attr, false) |
||||||
} |
} |
||||||
|
|
||||||
func (h *swapHandler) Get() Handler { |
func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr { |
||||||
return *h.handler.Load().(*Handler) |
switch attr.Key { |
||||||
|
case slog.TimeKey: |
||||||
|
if attr.Value.Kind() == slog.KindTime { |
||||||
|
if logfmt { |
||||||
|
return slog.String("t", attr.Value.Time().Format(timeFormat)) |
||||||
|
} else { |
||||||
|
return slog.Attr{Key: "t", Value: attr.Value} |
||||||
|
} |
||||||
|
} |
||||||
|
case slog.LevelKey: |
||||||
|
if l, ok := attr.Value.Any().(slog.Level); ok { |
||||||
|
attr = slog.Any("lvl", LevelString(l)) |
||||||
|
return attr |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
switch v := attr.Value.Any().(type) { |
||||||
|
case time.Time: |
||||||
|
if logfmt { |
||||||
|
attr = slog.String(attr.Key, v.Format(timeFormat)) |
||||||
|
} |
||||||
|
case *big.Int: |
||||||
|
if v == nil { |
||||||
|
attr.Value = slog.StringValue("<nil>") |
||||||
|
} else { |
||||||
|
attr.Value = slog.StringValue(v.String()) |
||||||
|
} |
||||||
|
case *uint256.Int: |
||||||
|
if v == nil { |
||||||
|
attr.Value = slog.StringValue("<nil>") |
||||||
|
} else { |
||||||
|
attr.Value = slog.StringValue(v.Dec()) |
||||||
|
} |
||||||
|
case fmt.Stringer: |
||||||
|
if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) { |
||||||
|
attr.Value = slog.StringValue("<nil>") |
||||||
|
} else { |
||||||
|
attr.Value = slog.StringValue(v.String()) |
||||||
|
} |
||||||
|
} |
||||||
|
return attr |
||||||
} |
} |
||||||
|
@ -1,294 +1,222 @@ |
|||||||
package log |
package log |
||||||
|
|
||||||
import ( |
import ( |
||||||
"fmt" |
"context" |
||||||
|
"math" |
||||||
"os" |
"os" |
||||||
|
"runtime" |
||||||
"time" |
"time" |
||||||
|
|
||||||
"github.com/go-stack/stack" |
"golang.org/x/exp/slog" |
||||||
) |
) |
||||||
|
|
||||||
const timeKey = "t" |
const errorKey = "LOG_ERROR" |
||||||
const lvlKey = "lvl" |
|
||||||
const msgKey = "msg" |
|
||||||
const ctxKey = "ctx" |
|
||||||
const errorKey = "LOG15_ERROR" |
|
||||||
const skipLevel = 2 |
|
||||||
|
|
||||||
type Lvl int |
const ( |
||||||
|
legacyLevelCrit = iota |
||||||
|
legacyLevelError |
||||||
|
legacyLevelWarn |
||||||
|
legacyLevelInfo |
||||||
|
legacyLevelDebug |
||||||
|
legacyLevelTrace |
||||||
|
) |
||||||
|
|
||||||
const ( |
const ( |
||||||
LvlCrit Lvl = iota |
levelMaxVerbosity slog.Level = math.MinInt |
||||||
LvlError |
LevelTrace slog.Level = -8 |
||||||
LvlWarn |
LevelDebug = slog.LevelDebug |
||||||
LvlInfo |
LevelInfo = slog.LevelInfo |
||||||
LvlDebug |
LevelWarn = slog.LevelWarn |
||||||
LvlTrace |
LevelError = slog.LevelError |
||||||
|
LevelCrit slog.Level = 12 |
||||||
|
|
||||||
|
// for backward-compatibility
|
||||||
|
LvlTrace = LevelTrace |
||||||
|
LvlInfo = LevelInfo |
||||||
|
LvlDebug = LevelDebug |
||||||
) |
) |
||||||
|
|
||||||
// AlignedString returns a 5-character string containing the name of a Lvl.
|
// convert from old Geth verbosity level constants
|
||||||
func (l Lvl) AlignedString() string { |
// to levels defined by slog
|
||||||
|
func FromLegacyLevel(lvl int) slog.Level { |
||||||
|
switch lvl { |
||||||
|
case legacyLevelCrit: |
||||||
|
return LevelCrit |
||||||
|
case legacyLevelError: |
||||||
|
return slog.LevelError |
||||||
|
case legacyLevelWarn: |
||||||
|
return slog.LevelWarn |
||||||
|
case legacyLevelInfo: |
||||||
|
return slog.LevelInfo |
||||||
|
case legacyLevelDebug: |
||||||
|
return slog.LevelDebug |
||||||
|
case legacyLevelTrace: |
||||||
|
return LevelTrace |
||||||
|
default: |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here?
|
||||||
|
if lvl > legacyLevelTrace { |
||||||
|
return LevelTrace |
||||||
|
} |
||||||
|
return LevelCrit |
||||||
|
} |
||||||
|
|
||||||
|
// LevelAlignedString returns a 5-character string containing the name of a Lvl.
|
||||||
|
func LevelAlignedString(l slog.Level) string { |
||||||
switch l { |
switch l { |
||||||
case LvlTrace: |
case LevelTrace: |
||||||
return "TRACE" |
return "TRACE" |
||||||
case LvlDebug: |
case slog.LevelDebug: |
||||||
return "DEBUG" |
return "DEBUG" |
||||||
case LvlInfo: |
case slog.LevelInfo: |
||||||
return "INFO " |
return "INFO " |
||||||
case LvlWarn: |
case slog.LevelWarn: |
||||||
return "WARN " |
return "WARN " |
||||||
case LvlError: |
case slog.LevelError: |
||||||
return "ERROR" |
return "ERROR" |
||||||
case LvlCrit: |
case LevelCrit: |
||||||
return "CRIT " |
return "CRIT " |
||||||
default: |
default: |
||||||
panic("bad level") |
return "unknown level" |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
// String returns the name of a Lvl.
|
// LevelString returns a 5-character string containing the name of a Lvl.
|
||||||
func (l Lvl) String() string { |
func LevelString(l slog.Level) string { |
||||||
switch l { |
switch l { |
||||||
case LvlTrace: |
case LevelTrace: |
||||||
return "trce" |
return "trace" |
||||||
case LvlDebug: |
case slog.LevelDebug: |
||||||
return "dbug" |
return "debug" |
||||||
case LvlInfo: |
case slog.LevelInfo: |
||||||
return "info" |
return "info" |
||||||
case LvlWarn: |
case slog.LevelWarn: |
||||||
return "warn" |
return "warn" |
||||||
case LvlError: |
case slog.LevelError: |
||||||
return "eror" |
return "eror" |
||||||
case LvlCrit: |
case LevelCrit: |
||||||
return "crit" |
return "crit" |
||||||
default: |
default: |
||||||
panic("bad level") |
return "unknown" |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
// LvlFromString returns the appropriate Lvl from a string name.
|
|
||||||
// Useful for parsing command line args and configuration files.
|
|
||||||
func LvlFromString(lvlString string) (Lvl, error) { |
|
||||||
switch lvlString { |
|
||||||
case "trace", "trce": |
|
||||||
return LvlTrace, nil |
|
||||||
case "debug", "dbug": |
|
||||||
return LvlDebug, nil |
|
||||||
case "info": |
|
||||||
return LvlInfo, nil |
|
||||||
case "warn": |
|
||||||
return LvlWarn, nil |
|
||||||
case "error", "eror": |
|
||||||
return LvlError, nil |
|
||||||
case "crit": |
|
||||||
return LvlCrit, nil |
|
||||||
default: |
|
||||||
return LvlDebug, fmt.Errorf("unknown level: %v", lvlString) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// A Record is what a Logger asks its handler to write
|
|
||||||
type Record struct { |
|
||||||
Time time.Time |
|
||||||
Lvl Lvl |
|
||||||
Msg string |
|
||||||
Ctx []interface{} |
|
||||||
Call stack.Call |
|
||||||
KeyNames RecordKeyNames |
|
||||||
} |
|
||||||
|
|
||||||
// RecordKeyNames gets stored in a Record when the write function is executed.
|
|
||||||
type RecordKeyNames struct { |
|
||||||
Time string |
|
||||||
Msg string |
|
||||||
Lvl string |
|
||||||
Ctx string |
|
||||||
} |
|
||||||
|
|
||||||
// A Logger writes key/value pairs to a Handler
|
// A Logger writes key/value pairs to a Handler
|
||||||
type Logger interface { |
type Logger interface { |
||||||
// New returns a new Logger that has this logger's context plus the given context
|
// With returns a new Logger that has this logger's attributes plus the given attributes
|
||||||
New(ctx ...interface{}) Logger |
With(ctx ...interface{}) Logger |
||||||
|
|
||||||
// GetHandler gets the handler associated with the logger.
|
// With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'.
|
||||||
GetHandler() Handler |
New(ctx ...interface{}) Logger |
||||||
|
|
||||||
// SetHandler updates the logger to write records to the specified handler.
|
// Log logs a message at the specified level with context key/value pairs
|
||||||
SetHandler(h Handler) |
Log(level slog.Level, msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the trace level with context key/value pairs
|
// Trace log a message at the trace level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage
|
|
||||||
//
|
|
||||||
// log.Trace("msg")
|
|
||||||
// log.Trace("msg", "key1", val1)
|
|
||||||
// log.Trace("msg", "key1", val1, "key2", val2)
|
|
||||||
Trace(msg string, ctx ...interface{}) |
Trace(msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the debug level with context key/value pairs
|
// Debug logs a message at the debug level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Debug("msg")
|
|
||||||
// log.Debug("msg", "key1", val1)
|
|
||||||
// log.Debug("msg", "key1", val1, "key2", val2)
|
|
||||||
Debug(msg string, ctx ...interface{}) |
Debug(msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the info level with context key/value pairs
|
// Info logs a message at the info level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Info("msg")
|
|
||||||
// log.Info("msg", "key1", val1)
|
|
||||||
// log.Info("msg", "key1", val1, "key2", val2)
|
|
||||||
Info(msg string, ctx ...interface{}) |
Info(msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the warn level with context key/value pairs
|
// Warn logs a message at the warn level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Warn("msg")
|
|
||||||
// log.Warn("msg", "key1", val1)
|
|
||||||
// log.Warn("msg", "key1", val1, "key2", val2)
|
|
||||||
Warn(msg string, ctx ...interface{}) |
Warn(msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the error level with context key/value pairs
|
// Error logs a message at the error level with context key/value pairs
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Error("msg")
|
|
||||||
// log.Error("msg", "key1", val1)
|
|
||||||
// log.Error("msg", "key1", val1, "key2", val2)
|
|
||||||
Error(msg string, ctx ...interface{}) |
Error(msg string, ctx ...interface{}) |
||||||
|
|
||||||
// Log a message at the crit level with context key/value pairs, and then exit.
|
// Crit logs a message at the crit level with context key/value pairs, and exits
|
||||||
//
|
|
||||||
// # Usage Examples
|
|
||||||
//
|
|
||||||
// log.Crit("msg")
|
|
||||||
// log.Crit("msg", "key1", val1)
|
|
||||||
// log.Crit("msg", "key1", val1, "key2", val2)
|
|
||||||
Crit(msg string, ctx ...interface{}) |
Crit(msg string, ctx ...interface{}) |
||||||
|
|
||||||
|
// Write logs a message at the specified level
|
||||||
|
Write(level slog.Level, msg string, attrs ...any) |
||||||
} |
} |
||||||
|
|
||||||
type logger struct { |
type logger struct { |
||||||
ctx []interface{} |
inner *slog.Logger |
||||||
h *swapHandler |
|
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { |
// NewLogger returns a logger with the specified handler set
|
||||||
record := &Record{ |
func NewLogger(h slog.Handler) Logger { |
||||||
Time: time.Now(), |
return &logger{ |
||||||
Lvl: lvl, |
slog.New(h), |
||||||
Msg: msg, |
|
||||||
Ctx: newContext(l.ctx, ctx), |
|
||||||
KeyNames: RecordKeyNames{ |
|
||||||
Time: timeKey, |
|
||||||
Msg: msgKey, |
|
||||||
Lvl: lvlKey, |
|
||||||
Ctx: ctxKey, |
|
||||||
}, |
|
||||||
} |
|
||||||
if stackEnabled.Load() { |
|
||||||
record.Call = stack.Caller(skip) |
|
||||||
} |
} |
||||||
l.h.Log(record) |
|
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) Logger { |
// write logs a message at the specified level:
|
||||||
child := &logger{newContext(l.ctx, ctx), new(swapHandler)} |
func (l *logger) Write(level slog.Level, msg string, attrs ...any) { |
||||||
child.SetHandler(l.h) |
if !l.inner.Enabled(context.Background(), level) { |
||||||
return child |
return |
||||||
} |
} |
||||||
|
|
||||||
func newContext(prefix []interface{}, suffix []interface{}) []interface{} { |
var pcs [1]uintptr |
||||||
normalizedSuffix := normalize(suffix) |
runtime.Callers(3, pcs[:]) |
||||||
newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) |
|
||||||
n := copy(newCtx, prefix) |
|
||||||
copy(newCtx[n:], normalizedSuffix) |
|
||||||
return newCtx |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Trace(msg string, ctx ...interface{}) { |
if len(attrs)%2 != 0 { |
||||||
l.write(msg, LvlTrace, ctx, skipLevel) |
attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil") |
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) Debug(msg string, ctx ...interface{}) { |
// evaluate lazy values
|
||||||
l.write(msg, LvlDebug, ctx, skipLevel) |
var hadErr bool |
||||||
} |
for i := 1; i < len(attrs); i += 2 { |
||||||
|
lz, ok := attrs[i].(Lazy) |
||||||
|
if ok { |
||||||
|
v, err := evaluateLazy(lz) |
||||||
|
if err != nil { |
||||||
|
hadErr = true |
||||||
|
attrs[i] = err |
||||||
|
} else { |
||||||
|
attrs[i] = v |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
func (l *logger) Info(msg string, ctx ...interface{}) { |
if hadErr { |
||||||
l.write(msg, LvlInfo, ctx, skipLevel) |
attrs = append(attrs, errorKey, "bad lazy") |
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) Warn(msg string, ctx ...interface{}) { |
r := slog.NewRecord(time.Now(), level, msg, pcs[0]) |
||||||
l.write(msg, LvlWarn, ctx, skipLevel) |
r.Add(attrs...) |
||||||
|
l.inner.Handler().Handle(context.Background(), r) |
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) Error(msg string, ctx ...interface{}) { |
func (l *logger) Log(level slog.Level, msg string, attrs ...any) { |
||||||
l.write(msg, LvlError, ctx, skipLevel) |
l.Write(level, msg, attrs...) |
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) Crit(msg string, ctx ...interface{}) { |
func (l *logger) With(ctx ...interface{}) Logger { |
||||||
l.write(msg, LvlCrit, ctx, skipLevel) |
return &logger{l.inner.With(ctx...)} |
||||||
os.Exit(1) |
|
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) GetHandler() Handler { |
func (l *logger) New(ctx ...interface{}) Logger { |
||||||
return l.h.Get() |
return l.With(ctx...) |
||||||
} |
} |
||||||
|
|
||||||
func (l *logger) SetHandler(h Handler) { |
func (l *logger) Trace(msg string, ctx ...interface{}) { |
||||||
l.h.Swap(h) |
l.Write(LevelTrace, msg, ctx...) |
||||||
} |
} |
||||||
|
|
||||||
func normalize(ctx []interface{}) []interface{} { |
func (l *logger) Debug(msg string, ctx ...interface{}) { |
||||||
// if the caller passed a Ctx object, then expand it
|
l.Write(slog.LevelDebug, msg, ctx...) |
||||||
if len(ctx) == 1 { |
|
||||||
if ctxMap, ok := ctx[0].(Ctx); ok { |
|
||||||
ctx = ctxMap.toArray() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// ctx needs to be even because it's a series of key/value pairs
|
|
||||||
// no one wants to check for errors on logging functions,
|
|
||||||
// so instead of erroring on bad input, we'll just make sure
|
|
||||||
// that things are the right length and users can fix bugs
|
|
||||||
// when they see the output looks wrong
|
|
||||||
if len(ctx)%2 != 0 { |
|
||||||
ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") |
|
||||||
} |
|
||||||
|
|
||||||
return ctx |
|
||||||
} |
} |
||||||
|
|
||||||
// Lazy allows you to defer calculation of a logged value that is expensive
|
func (l *logger) Info(msg string, ctx ...interface{}) { |
||||||
// to compute until it is certain that it must be evaluated with the given filters.
|
l.Write(slog.LevelInfo, msg, ctx...) |
||||||
//
|
|
||||||
// Lazy may also be used in conjunction with a Logger's New() function
|
|
||||||
// to generate a child logger which always reports the current value of changing
|
|
||||||
// state.
|
|
||||||
//
|
|
||||||
// You may wrap any function which takes no arguments to Lazy. It may return any
|
|
||||||
// number of values of any type.
|
|
||||||
type Lazy struct { |
|
||||||
Fn interface{} |
|
||||||
} |
} |
||||||
|
|
||||||
// Ctx is a map of key/value pairs to pass as context to a log function
|
func (l *logger) Warn(msg string, ctx ...any) { |
||||||
// Use this only if you really need greater safety around the arguments you pass
|
l.Write(slog.LevelWarn, msg, ctx...) |
||||||
// to the logging functions.
|
} |
||||||
type Ctx map[string]interface{} |
|
||||||
|
|
||||||
func (c Ctx) toArray() []interface{} { |
|
||||||
arr := make([]interface{}, len(c)*2) |
|
||||||
|
|
||||||
i := 0 |
func (l *logger) Error(msg string, ctx ...interface{}) { |
||||||
for k, v := range c { |
l.Write(slog.LevelError, msg, ctx...) |
||||||
arr[i] = k |
} |
||||||
arr[i+1] = v |
|
||||||
i += 2 |
|
||||||
} |
|
||||||
|
|
||||||
return arr |
func (l *logger) Crit(msg string, ctx ...interface{}) { |
||||||
|
l.Write(LevelCrit, msg, ctx...) |
||||||
|
os.Exit(1) |
||||||
} |
} |
||||||
|
@ -1,58 +0,0 @@ |
|||||||
//go:build !windows && !plan9
|
|
||||||
// +build !windows,!plan9
|
|
||||||
|
|
||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"log/syslog" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// SyslogHandler opens a connection to the system syslog daemon by calling
|
|
||||||
// syslog.New and writes all records to it.
|
|
||||||
func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
|
||||||
wr, err := syslog.New(priority, tag) |
|
||||||
return sharedSyslog(fmtr, wr, err) |
|
||||||
} |
|
||||||
|
|
||||||
// SyslogNetHandler opens a connection to a log daemon over the network and writes
|
|
||||||
// all log records to it.
|
|
||||||
func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
|
||||||
wr, err := syslog.Dial(net, addr, priority, tag) |
|
||||||
return sharedSyslog(fmtr, wr, err) |
|
||||||
} |
|
||||||
|
|
||||||
func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
h := FuncHandler(func(r *Record) error { |
|
||||||
var syslogFn = sysWr.Info |
|
||||||
switch r.Lvl { |
|
||||||
case LvlCrit: |
|
||||||
syslogFn = sysWr.Crit |
|
||||||
case LvlError: |
|
||||||
syslogFn = sysWr.Err |
|
||||||
case LvlWarn: |
|
||||||
syslogFn = sysWr.Warning |
|
||||||
case LvlInfo: |
|
||||||
syslogFn = sysWr.Info |
|
||||||
case LvlDebug: |
|
||||||
syslogFn = sysWr.Debug |
|
||||||
case LvlTrace: |
|
||||||
syslogFn = func(m string) error { return nil } // There's no syslog level for trace
|
|
||||||
} |
|
||||||
|
|
||||||
s := strings.TrimSpace(string(fmtr.Format(r))) |
|
||||||
return syslogFn(s) |
|
||||||
}) |
|
||||||
return LazyHandler(&closingHandler{sysWr, h}), nil |
|
||||||
} |
|
||||||
|
|
||||||
func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { |
|
||||||
return must(SyslogHandler(priority, tag, fmtr)) |
|
||||||
} |
|
||||||
|
|
||||||
func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { |
|
||||||
return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) |
|
||||||
} |
|
Loading…
Reference in new issue