summaryrefslogtreecommitdiff
path: root/internal/log
diff options
context:
space:
mode:
Diffstat (limited to 'internal/log')
-rw-r--r--internal/log/caller.go89
-rw-r--r--internal/log/caller_test.go54
-rw-r--r--internal/log/entry.go117
-rw-r--r--internal/log/init.go62
-rw-r--r--internal/log/log.go310
-rw-r--r--internal/log/log_test.go69
-rw-r--r--internal/log/pool.go49
-rw-r--r--internal/log/syslog_test.go24
-rw-r--r--internal/log/trimhook.go53
-rw-r--r--internal/log/writer.go37
10 files changed, 662 insertions, 202 deletions
diff --git a/internal/log/caller.go b/internal/log/caller.go
new file mode 100644
index 000000000..f7062f06a
--- /dev/null
+++ b/internal/log/caller.go
@@ -0,0 +1,89 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package log
+
+import (
+ "runtime"
+ "strings"
+ "sync"
+)
+
+var (
+ // fnCache is a cache of PCs to their calculated function names.
+ fnCache = map[uintptr]string{}
+
+ // strCache is a cache of strings to the originally allocated version
+ // of that string contents. so we don't have hundreds of the same instances
+ // of string floating around in memory.
+ strCache = map[string]string{}
+
+ // cacheMu protects fnCache and strCache.
+ cacheMu sync.Mutex
+)
+
+// Caller fetches the calling function name, skipping 'depth'. Results are cached per PC.
+func Caller(depth int) string {
+ var rpc [1]uintptr
+
+ // Fetch pcs of callers
+ n := runtime.Callers(depth, rpc[:])
+
+ if n > 0 {
+ // Look for value in cache
+ cacheMu.Lock()
+ fn, ok := fnCache[rpc[0]]
+ cacheMu.Unlock()
+
+ if ok {
+ return fn
+ }
+
+ // Fetch frame info for caller pc
+ frame, _ := runtime.CallersFrames(rpc[:]).Next()
+
+ if frame.PC != 0 {
+ name := frame.Function
+
+ // Drop all but the package name and function name, no mod path
+ if idx := strings.LastIndex(name, "/"); idx >= 0 {
+ name = name[idx+1:]
+ }
+
+ // Drop any generic type parameter markers
+ if idx := strings.Index(name, "[...]"); idx >= 0 {
+ name = name[:idx] + name[idx+5:]
+ }
+
+ // Cache this func name
+ cacheMu.Lock()
+ fn, ok := strCache[name]
+ if !ok {
+ // Cache ptr to this allocated str
+ strCache[name] = name
+ fn = name
+ }
+ fnCache[rpc[0]] = fn
+ cacheMu.Unlock()
+
+ return fn
+ }
+ }
+
+ return "???"
+}
diff --git a/internal/log/caller_test.go b/internal/log/caller_test.go
new file mode 100644
index 000000000..59bf342d1
--- /dev/null
+++ b/internal/log/caller_test.go
@@ -0,0 +1,54 @@
+package log_test
+
+import (
+ "runtime"
+ "strings"
+ "testing"
+
+ "codeberg.org/gruf/go-atomics"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+)
+
+// noopt exists to prevent certain optimisations during benching.
+var noopt = atomics.NewString()
+
+func BenchmarkCaller(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ name := log.Caller(2)
+ noopt.Store(name)
+ }
+ })
+}
+
+func BenchmarkCallerNoCache(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ var rpc [1]uintptr
+
+ // Fetch pcs of callers
+ n := runtime.Callers(2, rpc[:])
+
+ if n > 0 {
+ // Fetch frame info for caller pc
+ frame, _ := runtime.CallersFrames(rpc[:]).Next()
+
+ if frame.PC != 0 {
+ name := frame.Function
+
+ // Drop all but the package name and function name, no mod path
+ if idx := strings.LastIndex(name, "/"); idx >= 0 {
+ name = name[idx+1:]
+ }
+
+ // Drop any generic type parameter markers
+ if idx := strings.Index(name, "[...]"); idx >= 0 {
+ name = name[:idx] + name[idx+5:]
+ }
+
+ noopt.Store(name)
+ }
+ }
+ }
+ })
+}
diff --git a/internal/log/entry.go b/internal/log/entry.go
new file mode 100644
index 000000000..2e6f62e96
--- /dev/null
+++ b/internal/log/entry.go
@@ -0,0 +1,117 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package log
+
+import (
+ "fmt"
+ "syscall"
+
+ "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-logger/v2/level"
+)
+
+type Entry struct {
+ fields []kv.Field
+}
+
+func (e Entry) WithField(key string, value interface{}) Entry {
+ e.fields = append(e.fields, kv.Field{K: key, V: value})
+ return e
+}
+
+func (e Entry) WithFields(fields ...kv.Field) Entry {
+ e.fields = append(e.fields, fields...)
+ return e
+}
+
+func (e Entry) Trace(a ...interface{}) {
+ logf(level.TRACE, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Tracef(s string, a ...interface{}) {
+ logf(level.TRACE, e.fields, s, a...)
+}
+
+func (e Entry) Debug(a ...interface{}) {
+ logf(level.DEBUG, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Debugf(s string, a ...interface{}) {
+ logf(level.DEBUG, e.fields, s, a...)
+}
+
+func (e Entry) Info(a ...interface{}) {
+ logf(level.INFO, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Infof(s string, a ...interface{}) {
+ logf(level.INFO, e.fields, s, a...)
+}
+
+func (e Entry) Warn(a ...interface{}) {
+ logf(level.WARN, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Warnf(s string, a ...interface{}) {
+ logf(level.WARN, e.fields, s, a...)
+}
+
+func (e Entry) Error(a ...interface{}) {
+ logf(level.ERROR, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Errorf(s string, a ...interface{}) {
+ logf(level.ERROR, e.fields, s, a...)
+}
+
+func (e Entry) Fatal(a ...interface{}) {
+ defer syscall.Exit(1)
+ logf(level.FATAL, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Fatalf(s string, a ...interface{}) {
+ defer syscall.Exit(1)
+ logf(level.FATAL, e.fields, s, a...)
+}
+
+func (e Entry) Panic(a ...interface{}) {
+ defer panic(fmt.Sprint(a...))
+ logf(level.PANIC, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Panicf(s string, a ...interface{}) {
+ defer panic(fmt.Sprintf(s, a...))
+ logf(level.PANIC, e.fields, s, a...)
+}
+
+func (e Entry) Log(lvl level.LEVEL, a ...interface{}) {
+ logf(lvl, e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Logf(lvl level.LEVEL, s string, a ...interface{}) {
+ logf(lvl, e.fields, s, a...)
+}
+
+func (e Entry) Print(a ...interface{}) {
+ printf(e.fields, args(len(a)), a...)
+}
+
+func (e Entry) Printf(s string, a ...interface{}) {
+ printf(e.fields, s, a...)
+}
diff --git a/internal/log/init.go b/internal/log/init.go
new file mode 100644
index 000000000..9ec790ba2
--- /dev/null
+++ b/internal/log/init.go
@@ -0,0 +1,62 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package log
+
+import (
+ "fmt"
+ "log/syslog"
+ "strings"
+
+ "codeberg.org/gruf/go-logger/v2/level"
+)
+
+// ParseLevel will parse the log level from given string and set to appropriate level.
+func ParseLevel(str string) error {
+ switch strings.ToLower(str) {
+ case "trace":
+ SetLevel(level.TRACE)
+ case "debug":
+ SetLevel(level.DEBUG)
+ case "", "info":
+ SetLevel(level.INFO)
+ case "warn":
+ SetLevel(level.WARN)
+ case "error":
+ SetLevel(level.ERROR)
+ case "fatal":
+ SetLevel(level.FATAL)
+ default:
+ return fmt.Errorf("unknown log level: %q", str)
+ }
+ return nil
+}
+
+// EnableSyslog will enabling logging to the syslog at given address.
+func EnableSyslog(proto, addr string) error {
+ // Dial a connection to the syslog daemon
+ writer, err := syslog.Dial(proto, addr, 0, "gotosocial")
+ if err != nil {
+ return err
+ }
+
+ // Set the syslog writer
+ sysout = writer
+
+ return nil
+}
diff --git a/internal/log/log.go b/internal/log/log.go
index 67dd03606..a9c7340ed 100644
--- a/internal/log/log.go
+++ b/internal/log/log.go
@@ -19,95 +19,259 @@
package log
import (
- "bytes"
+ "fmt"
"io"
"log/syslog"
"os"
+ "strings"
+ "syscall"
+ "time"
- "github.com/sirupsen/logrus"
- lSyslog "github.com/sirupsen/logrus/hooks/syslog"
- "github.com/superseriousbusiness/gotosocial/internal/config"
+ "codeberg.org/gruf/go-atomics"
+ "codeberg.org/gruf/go-kv"
+ "codeberg.org/gruf/go-logger/v2/level"
)
-// Initialize initializes the global Logrus logger, reading the desired
-// log level from the viper store, or using a default if the level
-// has not been set in viper.
-//
-// It also sets the output to log.SplitErrOutputs(...)
-// so you get error logs on stderr and normal logs on stdout.
-//
-// If syslog settings are also in viper, then Syslog will be initialized as well.
-func Initialize() error {
- out := SplitErrOutputs(os.Stdout, os.Stderr)
- logrus.SetOutput(out)
-
- // check if a desired log level has been set
- if lvl := config.GetLogLevel(); lvl != "" {
- level, err := logrus.ParseLevel(lvl)
- if err != nil {
- return err
- }
- logrus.SetLevel(level)
-
- if level == logrus.TraceLevel {
- logrus.SetReportCaller(true)
- }
+var (
+ // loglvl is the currently set logging level.
+ loglvl atomics.Uint32
+
+ // lvlstrs is the lookup table of log levels to strings.
+ lvlstrs = level.Default()
+
+ // Preprepared stdout/stderr log writers.
+ stdout = &safewriter{w: os.Stdout}
+ stderr = &safewriter{w: os.Stderr}
+
+ // Syslog output, only set if enabled.
+ sysout *syslog.Writer
+)
+
+// Level returns the currently set log level.
+func Level() level.LEVEL {
+ return level.LEVEL(loglvl.Load())
+}
+
+// SetLevel sets the max logging level.
+func SetLevel(lvl level.LEVEL) {
+ loglvl.Store(uint32(lvl))
+}
+
+func WithField(key string, value interface{}) Entry {
+ return Entry{fields: []kv.Field{{K: key, V: value}}}
+}
+
+func WithFields(fields ...kv.Field) Entry {
+ return Entry{fields: fields}
+}
+
+func Trace(a ...interface{}) {
+ logf(level.TRACE, nil, args(len(a)), a...)
+}
+
+func Tracef(s string, a ...interface{}) {
+ logf(level.TRACE, nil, s, a...)
+}
+
+func Debug(a ...interface{}) {
+ logf(level.DEBUG, nil, args(len(a)), a...)
+}
+
+func Debugf(s string, a ...interface{}) {
+ logf(level.DEBUG, nil, s, a...)
+}
+
+func Info(a ...interface{}) {
+ logf(level.INFO, nil, args(len(a)), a...)
+}
+
+func Infof(s string, a ...interface{}) {
+ logf(level.INFO, nil, s, a...)
+}
+
+func Warn(a ...interface{}) {
+ logf(level.WARN, nil, args(len(a)), a...)
+}
+
+func Warnf(s string, a ...interface{}) {
+ logf(level.WARN, nil, s, a...)
+}
+
+func Error(a ...interface{}) {
+ logf(level.ERROR, nil, args(len(a)), a...)
+}
+
+func Errorf(s string, a ...interface{}) {
+ logf(level.ERROR, nil, s, a...)
+}
+
+func Fatal(a ...interface{}) {
+ defer syscall.Exit(1)
+ logf(level.FATAL, nil, args(len(a)), a...)
+}
+
+func Fatalf(s string, a ...interface{}) {
+ defer syscall.Exit(1)
+ logf(level.FATAL, nil, s, a...)
+}
+
+func Panic(a ...interface{}) {
+ defer panic(fmt.Sprint(a...))
+ logf(level.PANIC, nil, args(len(a)), a...)
+}
+
+func Panicf(s string, a ...interface{}) {
+ defer panic(fmt.Sprintf(s, a...))
+ logf(level.PANIC, nil, s, a...)
+}
+
+// Log will log formatted args as 'msg' field to the log at given level.
+func Log(lvl level.LEVEL, a ...interface{}) {
+ logf(lvl, nil, args(len(a)), a...)
+}
+
+// Logf will log format string as 'msg' field to the log at given level.
+func Logf(lvl level.LEVEL, s string, a ...interface{}) {
+ logf(lvl, nil, s, a...)
+}
+
+// Print will log formatted args to the stdout log output.
+func Print(a ...interface{}) {
+ printf(nil, args(len(a)), a...)
+}
+
+// Print will log format string to the stdout log output.
+func Printf(s string, a ...interface{}) {
+ printf(nil, s, a...)
+}
+
+func printf(fields []kv.Field, s string, a ...interface{}) {
+ // Acquire buffer
+ buf := getBuf()
+
+ // Append formatted timestamp
+ now := time.Now().Format("02/01/2006 15:04:05.000")
+ buf.B = append(buf.B, `timestamp="`...)
+ buf.B = append(buf.B, now...)
+ buf.B = append(buf.B, `" `...)
+
+ // Append formatted caller func
+ buf.B = append(buf.B, `func=`...)
+ buf.B = append(buf.B, Caller(4)...)
+ buf.B = append(buf.B, ' ')
+
+ if len(fields) > 0 {
+ // Append formatted fields
+ kv.Fields(fields).AppendFormat(buf, false)
+ buf.B = append(buf.B, ' ')
+ }
+
+ // Append formatted args
+ fmt.Fprintf(buf, s, a...)
+
+ // Append a final newline
+ buf.B = append(buf.B, '\n')
+
+ // Write to log and release
+ _, _ = stdout.Write(buf.B)
+ putBuf(buf)
+}
+
+func logf(lvl level.LEVEL, fields []kv.Field, s string, a ...interface{}) {
+ var out io.Writer
+
+ // Check if enabled.
+ if lvl > Level() {
+ return
}
- // set our custom formatter options
- logrus.SetFormatter(&logrus.TextFormatter{
- DisableColors: true,
- FullTimestamp: true,
-
- // By default, quoting is enabled to help differentiate key-value
- // fields in log lines. But when debug (or higher, e.g. trace) logging
- // is enabled, we disable this. This allows easier copy-pasting of
- // entry fields without worrying about escaped quotes.
- DisableQuote: logrus.GetLevel() >= logrus.DebugLevel,
- })
-
- // check if syslog has been enabled, and configure it if so
- if config.GetSyslogEnabled() {
- protocol := config.GetSyslogProtocol()
- address := config.GetSyslogAddress()
-
- hook, err := lSyslog.NewSyslogHook(protocol, address, syslog.LOG_INFO, "")
- if err != nil {
- return err
- }
-
- logrus.AddHook(&trimHook{hook})
+ // Split errors to stderr,
+ // all else goes to stdout.
+ if lvl <= level.ERROR {
+ out = stderr
+ } else {
+ out = stdout
+ }
+
+ // Acquire buffer
+ buf := getBuf()
+
+ // Append formatted timestamp
+ now := time.Now().Format("02/01/2006 15:04:05.000")
+ buf.B = append(buf.B, `timestamp="`...)
+ buf.B = append(buf.B, now...)
+ buf.B = append(buf.B, `" `...)
+
+ // Append formatted caller func
+ buf.B = append(buf.B, `func=`...)
+ buf.B = append(buf.B, Caller(4)...)
+ buf.B = append(buf.B, ' ')
+
+ // Append formatted level string
+ buf.B = append(buf.B, `level=`...)
+ buf.B = append(buf.B, lvlstrs[lvl]...)
+ buf.B = append(buf.B, ' ')
+
+ // Append formatted fields with msg
+ kv.Fields(append(fields, kv.Field{
+ "msg", fmt.Sprintf(s, a...),
+ })).AppendFormat(buf, false)
+
+ // Append a final newline
+ buf.B = append(buf.B, '\n')
+
+ if sysout != nil {
+ // Write log entry to syslog
+ logsys(lvl, buf.String())
}
- return nil
+ // Write to log and release
+ _, _ = out.Write(buf.B)
+ putBuf(buf)
}
-// SplitErrOutputs returns an OutputSplitFunc that splits output to either one of
-// two given outputs depending on whether the level is "error","fatal","panic".
-func SplitErrOutputs(out, err io.Writer) OutputSplitFunc {
- return func(lvl []byte) io.Writer {
- switch string(lvl) /* convert to str for compare is no-alloc */ {
- case "error", "fatal", "panic":
- return err
- default:
- return out
- }
+// logsys will log given msg at given severity to the syslog.
+func logsys(lvl level.LEVEL, msg string) {
+ // Truncate message if > 1700 chars
+ if len(msg) > 1700 {
+ msg = msg[:1697] + "..."
+ }
+
+ // Log at appropriate syslog severity
+ switch lvl {
+ case level.TRACE:
+ case level.DEBUG:
+ case level.INFO:
+ _ = sysout.Info(msg)
+ case level.WARN:
+ _ = sysout.Warning(msg)
+ case level.ERROR:
+ _ = sysout.Err(msg)
+ case level.FATAL:
+ _ = sysout.Crit(msg)
}
}
-// OutputSplitFunc implements the io.Writer interface for use with Logrus, and simply
-// splits logs between stdout and stderr depending on their severity.
-type OutputSplitFunc func(lvl []byte) io.Writer
+// args returns an args format string of format '%v' * count.
+func args(count int) string {
+ const args = `%v%v%v%v%v%v%v%v%v%v` +
+ `%v%v%v%v%v%v%v%v%v%v` +
+ `%v%v%v%v%v%v%v%v%v%v` +
+ `%v%v%v%v%v%v%v%v%v%v`
+
+ // Use predetermined args str
+ if count < len(args) {
+ return args[:count*2]
+ }
-var levelBytes = []byte("level=")
+ // Allocate buffer of needed len
+ var buf strings.Builder
+ buf.Grow(count * 2)
-func (fn OutputSplitFunc) Write(b []byte) (int, error) {
- var lvl []byte
- if i := bytes.Index(b, levelBytes); i >= 0 {
- blvl := b[i+len(levelBytes):]
- if i := bytes.IndexByte(blvl, ' '); i >= 0 {
- lvl = blvl[:i]
- }
+ // Manually build an args str
+ for i := 0; i < count; i++ {
+ buf.WriteString(`%v`)
}
- return fn(lvl).Write(b)
+
+ return buf.String()
}
diff --git a/internal/log/log_test.go b/internal/log/log_test.go
deleted file mode 100644
index 01aae73ae..000000000
--- a/internal/log/log_test.go
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- GoToSocial
- Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-package log_test
-
-import (
- "bytes"
- "testing"
-
- "github.com/sirupsen/logrus"
- "github.com/superseriousbusiness/gotosocial/internal/log"
-)
-
-func TestOutputSplitFunc(t *testing.T) {
- var outbuf, errbuf bytes.Buffer
-
- out := log.SplitErrOutputs(&outbuf, &errbuf)
-
- log := logrus.New()
- log.SetOutput(out)
- log.SetLevel(logrus.TraceLevel)
-
- for _, lvl := range logrus.AllLevels {
- func() {
- defer func() { recover() }()
- log.Log(lvl, "hello world")
- }()
-
- t.Logf("outbuf=%q errbuf=%q", outbuf.String(), errbuf.String())
-
- switch lvl {
- case logrus.PanicLevel:
- if outbuf.Len() > 0 || errbuf.Len() == 0 {
- t.Error("expected panic to log to OutputSplitter.Err")
- }
- case logrus.FatalLevel:
- if outbuf.Len() > 0 || errbuf.Len() == 0 {
- t.Error("expected fatal to log to OutputSplitter.Err")
- }
- case logrus.ErrorLevel:
- if outbuf.Len() > 0 || errbuf.Len() == 0 {
- t.Error("expected error to log to OutputSplitter.Err")
- }
- default:
- if outbuf.Len() == 0 || errbuf.Len() > 0 {
- t.Errorf("expected %s to log to OutputSplitter.Out", lvl)
- }
- }
-
- // Reset buffers
- outbuf.Reset()
- errbuf.Reset()
- }
-}
diff --git a/internal/log/pool.go b/internal/log/pool.go
new file mode 100644
index 000000000..cb2edfbea
--- /dev/null
+++ b/internal/log/pool.go
@@ -0,0 +1,49 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package log
+
+import (
+ "sync"
+
+ "codeberg.org/gruf/go-byteutil"
+)
+
+// bufPool provides a memory pool of log buffers.
+var bufPool = sync.Pool{
+ New: func() any {
+ return &byteutil.Buffer{
+ B: make([]byte, 0, 512),
+ }
+ },
+}
+
+// getBuf acquires a buffer from memory pool.
+func getBuf() *byteutil.Buffer {
+ buf, _ := bufPool.Get().(*byteutil.Buffer)
+ return buf
+}
+
+// putBuf places (after resetting) buffer back in memory pool, dropping if capacity too large.
+func putBuf(buf *byteutil.Buffer) {
+ if buf.Cap() > int(^uint16(0)) {
+ return // drop large buffer
+ }
+ buf.Reset()
+ bufPool.Put(buf)
+}
diff --git a/internal/log/syslog_test.go b/internal/log/syslog_test.go
index d58dfdcba..aafa93419 100644
--- a/internal/log/syslog_test.go
+++ b/internal/log/syslog_test.go
@@ -19,15 +19,16 @@
package log_test
import (
+ "fmt"
"os"
"path"
"regexp"
"testing"
"github.com/google/uuid"
- "github.com/sirupsen/logrus"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/testrig"
"gopkg.in/mcuadros/go-syslog.v2"
"gopkg.in/mcuadros/go-syslog.v2/format"
@@ -65,17 +66,21 @@ func (suite *SyslogTestSuite) TearDownTest() {
}
func (suite *SyslogTestSuite) TestSyslog() {
- logrus.Warn("this is a test of the emergency broadcast system!")
+ log.Info("this is a test of the emergency broadcast system!")
entry := <-suite.syslogChannel
- suite.Regexp(regexp.MustCompile(`time=.* msg=this is a test of the emergency broadcast system! func=.*`), entry["content"])
+ suite.Regexp(regexp.MustCompile(`timestamp=.* func=.* level=INFO msg="this is a test of the emergency broadcast system!"`), entry["content"])
}
func (suite *SyslogTestSuite) TestSyslogLongMessage() {
- logrus.Warn(longMessage)
+ log.Warn(longMessage)
+
+ funcName := log.Caller(2)
+ prefix := fmt.Sprintf(`timestamp="02/01/2006 15:04:05.000" func=%s level=WARN msg="`, funcName)
entry := <-suite.syslogChannel
- suite.Regexp(regexp.MustCompile(`time=.* msg=condimentum lacinia quis vel eros donec ac odio tempor orci dapibus ultrices in iaculis nunc sed augue lacus viverra vitae congue eu consequat ac felis donec et odio pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales ut eu sem integer vitae justo eget magna fermentum iaculis eu non diam phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet enim tortor at auctor urna nunc id cursus metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget duis at tellus at urna condimentum mattis pellentesque id nibh tortor id aliquet lectus proin nibh nisl condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas sed tempus urna et pharetra pharetra massa massa ultricies mi quis hendrerit dolor magna eget est lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas integer eget aliquet nibh praesent tristique magna sit amet purus gravida quis blandit turpis cursus in hac habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat mauris nunc congue nisi vitae suscipit tellus mauris a diam maecenas sed enim ut sem viverra aliquet eget sit amet tellus cras adipiscing enim eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt dui ut ornare lectus sit a\.\.\. func=.*`), entry["content"])
+ regex := fmt.Sprintf(`timestamp=.* func=.* level=WARN msg="%s\.\.\.`, longMessage[:1700-len(prefix)-3])
+ suite.Regexp(regexp.MustCompile(regex), entry["content"])
}
func (suite *SyslogTestSuite) TestSyslogLongMessageUnixgram() {
@@ -99,10 +104,15 @@ func (suite *SyslogTestSuite) TestSyslogLongMessageUnixgram() {
testrig.InitTestLog()
- logrus.Warn(longMessage)
+ log.Warn(longMessage)
+
+ funcName := log.Caller(2)
+ prefix := fmt.Sprintf(`timestamp="02/01/2006 15:04:05.000" func=%s level=WARN msg="`, funcName)
entry := <-syslogChannel
- suite.Regexp(regexp.MustCompile(`time=.* msg=condimentum lacinia quis vel eros donec ac odio tempor orci dapibus ultrices in iaculis nunc sed augue lacus viverra vitae congue eu consequat ac felis donec et odio pellentesque diam volutpat commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales ut eu sem integer vitae justo eget magna fermentum iaculis eu non diam phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet enim tortor at auctor urna nunc id cursus metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget duis at tellus at urna condimentum mattis pellentesque id nibh tortor id aliquet lectus proin nibh nisl condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas sed tempus urna et pharetra pharetra massa massa ultricies mi quis hendrerit dolor magna eget est lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas integer eget aliquet nibh praesent tristique magna sit amet purus gravida quis blandit turpis cursus in hac habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat mauris nunc congue nisi vitae suscipit tellus mauris a diam maecenas sed enim ut sem viverra aliquet eget sit amet tellus cras adipiscing enim eu turpis egestas pretium aenean pharetra magna ac placerat vestibulum lectus mauris ultrices eros in cursus turpis massa tincidunt dui ut ornare lectus sit a\.\.\. func=.*`), entry["content"])
+ regex := fmt.Sprintf(`timestamp=.* func=.* level=WARN msg="%s\.\.\.`, longMessage[:1700-len(prefix)-3])
+
+ suite.Regexp(regexp.MustCompile(regex), entry["content"])
if err := syslogServer.Kill(); err != nil {
panic(err)
diff --git a/internal/log/trimhook.go b/internal/log/trimhook.go
deleted file mode 100644
index c9faccb90..000000000
--- a/internal/log/trimhook.go
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- GoToSocial
- Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-package log
-
-import (
- "github.com/sirupsen/logrus"
-)
-
-// trimHook is a wrapper round a logrus hook that trims the *entry.Message
-// to no more than 1700 characters before sending it through to the wrapped hook,
-// to avoid spamming syslog with messages that are too long for it.
-type trimHook struct {
- wrappedHook logrus.Hook
-}
-
-func (t *trimHook) Fire(e *logrus.Entry) error {
- // only copy/truncate if we need to
- if len(e.Message) < 1700 {
- return t.wrappedHook.Fire(e)
- }
-
- // it's too long, truncate + fire a copy of the entry so we don't meddle with the original
- return t.wrappedHook.Fire(&logrus.Entry{
- Logger: e.Logger,
- Data: e.Data,
- Time: e.Time,
- Level: e.Level,
- Caller: e.Caller,
- Message: e.Message[:1696] + "...", // truncate
- Buffer: e.Buffer,
- Context: e.Context,
- })
-}
-
-func (t *trimHook) Levels() []logrus.Level {
- return t.wrappedHook.Levels()
-}
diff --git a/internal/log/writer.go b/internal/log/writer.go
new file mode 100644
index 000000000..4356d9b7d
--- /dev/null
+++ b/internal/log/writer.go
@@ -0,0 +1,37 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package log
+
+import (
+ "io"
+ "sync"
+)
+
+// safewriter wraps a writer to provide mutex safety on write.
+type safewriter struct {
+ w io.Writer
+ m sync.Mutex
+}
+
+func (w *safewriter) Write(b []byte) (int, error) {
+ w.m.Lock()
+ n, err := w.w.Write(b)
+ w.m.Unlock()
+ return n, err
+}