diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f6ec87d --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module protocol.realy.lol + +go 1.23.4 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/fatih/color v1.18.0 + go.uber.org/atomic v1.11.0 +) + +require ( + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/stretchr/testify v1.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c6f03cf --- /dev/null +++ b/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/lol/log.go b/pkg/lol/log.go new file mode 100644 index 0000000..0a86fbb --- /dev/null +++ b/pkg/lol/log.go @@ -0,0 +1,316 @@ +package lol + +import ( + "fmt" + "io" + "os" + "runtime" + "strings" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/fatih/color" + "go.uber.org/atomic" +) + +const ( + Off = iota + Fatal + Error + Warn + Info + Debug + Trace +) + +var LevelNames = []string{ + "off", + "fatal", + "error", + "warn", + "info", + "debug", + "trace", +} + +type ( + // LevelPrinter defines a set of terminal printing primitives that output with + // extra data, time, log logLevelList, and code location + + // Ln prints lists of interfaces with spaces in between + Ln func(a ...interface{}) + // F prints like fmt.Println surrounded []byte log details + F func(format string, a ...interface{}) + // S prints a spew.Sdump for an enveloper slice + S func(a ...interface{}) + // C accepts a function so that the extra computation can be avoided if it is + // not being viewed + C func(closure func() string) + // Chk is a shortcut for printing if there is an error, or returning true + Chk func(e error) bool + // Err is a pass-through function that uses fmt.Errorf to construct an error + // and returns the error after printing it to the log + Err func(format string, a ...interface{}) error + LevelPrinter struct { + Ln + F + S + C + Chk + Err + } + LevelSpec struct { + ID int + Name string + Colorizer func(a ...interface{}) string + } + + // Entry is a log entry to be printed as json to the log file + Entry struct { + Time time.Time + Level string + Package string + CodeLocation string + Text string + } +) + +var ( + // sep is just a convenient shortcut for this very longwinded expression + sep = string(os.PathSeparator) + // writer can be swapped out for any io.*writer* that you want to use instead of + // stdout. + writer io.Writer = os.Stderr + // LevelSpecs specifies the id, string name and color-printing function + LevelSpecs = []LevelSpec{ + {Off, "", NoSprint}, + {Fatal, "FTL", color.New(color.BgRed, color.FgHiWhite).Sprint}, + {Error, "ERR", color.New(color.FgHiRed).Sprint}, + {Warn, "WRN", color.New(color.FgHiYellow).Sprint}, + {Info, "INF", color.New(color.FgHiGreen).Sprint}, + {Debug, "DBG", color.New(color.FgHiBlue).Sprint}, + {Trace, "TRC", color.New(color.FgHiMagenta).Sprint}, + } + NoTimeStomp atomic.Bool +) + +func NoSprint(a ...any) string { return "" } + +// Log is a set of log printers for the various Level items. +type Log struct { + F, E, W, I, D, T LevelPrinter +} + +type Check struct { + F, E, W, I, D, T Chk +} +type Errorf struct { + F, E, W, I, D, T Err +} + +type Logger struct { + *Log + *Check + *Errorf +} + +var Level atomic.Int32 +var Main = &Logger{} + +func init() { + // Main = &Logger{} + Main.Log, Main.Check, Main.Errorf = New(os.Stderr) + SetLoggers(Info) +} + +func SetLoggers(level int) { + Main.Log.T.F("log level %s", LevelSpecs[level].Colorizer(LevelNames[level])) + Level.Store(int32(level)) +} + +func GetLogLevel(level string) (i int) { + for i = range LevelNames { + if level == LevelNames[i] { + return i + } + } + return Info +} + +func SetLogLevel(level string) { + for i := range LevelNames { + if level == LevelNames[i] { + SetLoggers(i) + return + } + } +} + +func JoinStrings(a ...any) (s string) { + for i := range a { + s += fmt.Sprint(a[i]) + if i < len(a)-1 { + s += " " + } + } + return +} + +var msgCol = color.New(color.FgBlue).Sprint + +func GetPrinter(l int32, writer io.Writer) LevelPrinter { + return LevelPrinter{ + Ln: func(a ...interface{}) { + if Level.Load() < l { + return + } + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name), + JoinStrings(a...), + msgCol(GetLoc(2)), + ) + }, + F: func(format string, a ...interface{}) { + if Level.Load() < l { + return + } + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name), + fmt.Sprintf(format, a...), + msgCol(GetLoc(2)), + ) + }, + S: func(a ...interface{}) { + if Level.Load() < l { + return + } + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name), + spew.Sdump(a...), + msgCol(GetLoc(2)), + ) + }, + C: func(closure func() string) { + if Level.Load() < l { + return + } + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name), + closure(), + msgCol(GetLoc(2)), + ) + }, + Chk: func(e error) bool { + if Level.Load() < l { + return e != nil + } + if e != nil { + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name), + e.Error(), + msgCol(GetLoc(2)), + ) + return true + } + return false + }, + Err: func(format string, a ...interface{}) error { + if Level.Load() < l { + fmt.Fprintf(writer, + "%s%s %s %s\n", + msgCol(Timestamper()), + LevelSpecs[l].Colorizer(LevelSpecs[l].Name, " "), + fmt.Sprintf(format, a...), + msgCol(GetLoc(2)), + ) + } + return fmt.Errorf(format, a...) + }, + } +} + +func GetNullPrinter() LevelPrinter { + return LevelPrinter{ + Ln: func(a ...interface{}) {}, + F: func(format string, a ...interface{}) {}, + S: func(a ...interface{}) {}, + C: func(closure func() string) {}, + Chk: func(e error) bool { return e != nil }, + Err: func(format string, a ...interface{}) error { return fmt.Errorf(format, a...) }, + } +} + +func New(writer io.Writer) (l *Log, c *Check, errorf *Errorf) { + l = &Log{ + T: GetPrinter(Trace, writer), + D: GetPrinter(Debug, writer), + I: GetPrinter(Info, writer), + W: GetPrinter(Warn, writer), + E: GetPrinter(Error, writer), + F: GetPrinter(Fatal, writer), + } + c = &Check{ + F: l.F.Chk, + E: l.E.Chk, + W: l.W.Chk, + I: l.I.Chk, + D: l.D.Chk, + T: l.T.Chk, + } + errorf = &Errorf{ + F: l.F.Err, + E: l.E.Err, + W: l.W.Err, + I: l.I.Err, + D: l.D.Err, + T: l.T.Err, + } + return +} + +// Timestamper e +func Timestamper() (s string) { + if NoTimeStomp.Load() { + return + } + timeText := fmt.Sprint(time.Now().UnixNano()) + lt := len(timeText) + lb := lt + 1 + var timeBytes = make([]byte, lb) + copy(timeBytes[lb-9:lb], timeText[lt-9:lt]) + timeBytes[lb-10] = '.' + lb -= 10 + lt -= 9 + copy(timeBytes[:lb], timeText[:lt]) + return fmt.Sprint(string(timeBytes), " ") +} + +var wd, _ = os.Getwd() + +func GetNLoc(n int) (output string) { + for ; n > 1; n-- { + output += fmt.Sprintf("%s\n", GetLoc(n)) + } + return +} + +func GetLoc(skip int) (output string) { + _, file, line, _ := runtime.Caller(skip) + split := strings.Split(file, wd+string(os.PathSeparator)) + if len(split) < 2 { + output = fmt.Sprintf("%s:%d", file, line) + } else { + output = fmt.Sprintf("%s:%d", split[1], line) + } + return +} diff --git a/pkg/lol/readme.md b/pkg/lol/readme.md new file mode 100644 index 0000000..afd1fa5 --- /dev/null +++ b/pkg/lol/readme.md @@ -0,0 +1,35 @@ +# lol + +location of log + +This is a very simple, but practical library for logging in applications. Its +main feature is printing source code locations to make debugging easier. + +## usage + +put this somewhere in your package: + +```go +var log, chk, errorf = lol.Main.Log, lol.Main.Check, lol.Main.Errorf +``` + +then you can invoke like this: + +```go + log.I.S(spew.this.thing) + errorf.E("print and return this error") + if err = bogus; chk.E(err) { return bogus.response } // print an error at the site and return the error +``` + +## terminals + +Due to how so few terminals actually support source location hyperlinks, pretty much tilix and intellij terminal are +the only two that really provide adequate functionality, this logging library defaults to output format that works +best with intellij. As such, the terminal is aware of the CWD and the code locations printed are relative, as +required to get the hyperlinkization from this terminal. Handling support for Tilix requires more complications and +due to advances with IntelliJ's handling it is not practical to support any other for this purpose. Users of this +library can always fall back to manually interpreting and accessing the relative file path to find the source of a log. + +In addition, due to this terminal's slow rendering of long lines, long log strings are automatically broken into 80 +character lines, and if there is comma separators in the line, the line is broken at the comma instead of at +column80. This works perfectly for this purpose. \ No newline at end of file diff --git a/readme.md b/readme.md index 1c4d523..2aa1b58 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,16 @@ # R.E.A.L.Y. Protocol -> relay events and like yeah +> relay events and like... yeah -relay events and like yeah protocol +[![Documentation](https://img.shields.io/badge/godoc-documentation-blue.svg)](https://pkg.go.dev/protocol.realy.lol) +[![matrix chat](https://img.shields.io/badge/matrix-chat-green.svg)](https://matrix.to/#/#realy-general:matrix.org) + +zap mleku: ⚡️mleku@getalby.com Inspired by the event bus architecture of [nostr](https://github.com/nostr-protocol) but redesigned to avoid the serious deficiencies of that protocol for both developers and users. - [why](./why.md) - [event spec](./spec.md) -- [matrix chat](https://matrix.to/#/#realy-general:matrix.org) - [reference relays](./relays/readme.md) - [reference clients](./clients/readme.md) - [GO libraries](./pkg/readme.md) \ No newline at end of file