log.go (7480B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package log 19 20 import ( 21 "context" 22 "fmt" 23 "log/syslog" 24 "os" 25 "strings" 26 "sync/atomic" 27 "syscall" 28 "time" 29 30 "codeberg.org/gruf/go-kv" 31 "codeberg.org/gruf/go-logger/v2/level" 32 ) 33 34 var ( 35 // loglvl is the currently set logging level. 36 loglvl atomic.Uint32 37 38 // lvlstrs is the lookup table of log levels to strings. 39 lvlstrs = level.Default() 40 41 // syslog output, only set if enabled. 42 sysout *syslog.Writer 43 44 // timefmt is the logging time format used. 45 timefmt = "02/01/2006 15:04:05.000" 46 47 // ctxhooks allows modifying log content based on context. 48 ctxhooks []func(context.Context, []kv.Field) []kv.Field 49 ) 50 51 // Hook adds the given hook to the global logger context hooks stack. 52 func Hook(hook func(ctx context.Context, kvs []kv.Field) []kv.Field) { 53 ctxhooks = append(ctxhooks, hook) 54 } 55 56 // Level returns the currently set log level. 57 func Level() level.LEVEL { 58 return level.LEVEL(loglvl.Load()) 59 } 60 61 // SetLevel sets the max logging level. 62 func SetLevel(lvl level.LEVEL) { 63 loglvl.Store(uint32(lvl)) 64 } 65 66 // New starts a new log entry. 67 func New() Entry { 68 return Entry{} 69 } 70 71 func WithContext(ctx context.Context) Entry { 72 return Entry{ctx: ctx} 73 } 74 75 func WithField(key string, value interface{}) Entry { 76 return New().WithField(key, value) 77 } 78 79 func WithFields(fields ...kv.Field) Entry { 80 return New().WithFields(fields...) 81 } 82 83 func Trace(ctx context.Context, a ...interface{}) { 84 logf(ctx, 3, level.TRACE, nil, args(len(a)), a...) 85 } 86 87 func Tracef(ctx context.Context, s string, a ...interface{}) { 88 logf(ctx, 3, level.TRACE, nil, s, a...) 89 } 90 91 func Debug(ctx context.Context, a ...interface{}) { 92 logf(ctx, 3, level.DEBUG, nil, args(len(a)), a...) 93 } 94 95 func Debugf(ctx context.Context, s string, a ...interface{}) { 96 logf(ctx, 3, level.DEBUG, nil, s, a...) 97 } 98 99 func Info(ctx context.Context, a ...interface{}) { 100 logf(ctx, 3, level.INFO, nil, args(len(a)), a...) 101 } 102 103 func Infof(ctx context.Context, s string, a ...interface{}) { 104 logf(ctx, 3, level.INFO, nil, s, a...) 105 } 106 107 func Warn(ctx context.Context, a ...interface{}) { 108 logf(ctx, 3, level.WARN, nil, args(len(a)), a...) 109 } 110 111 func Warnf(ctx context.Context, s string, a ...interface{}) { 112 logf(ctx, 3, level.WARN, nil, s, a...) 113 } 114 115 func Error(ctx context.Context, a ...interface{}) { 116 logf(ctx, 3, level.ERROR, nil, args(len(a)), a...) 117 } 118 119 func Errorf(ctx context.Context, s string, a ...interface{}) { 120 logf(ctx, 3, level.ERROR, nil, s, a...) 121 } 122 123 func Fatal(ctx context.Context, a ...interface{}) { 124 defer syscall.Exit(1) 125 logf(ctx, 3, level.FATAL, nil, args(len(a)), a...) 126 } 127 128 func Fatalf(ctx context.Context, s string, a ...interface{}) { 129 defer syscall.Exit(1) 130 logf(ctx, 3, level.FATAL, nil, s, a...) 131 } 132 133 func Panic(ctx context.Context, a ...interface{}) { 134 defer panic(fmt.Sprint(a...)) 135 logf(ctx, 3, level.PANIC, nil, args(len(a)), a...) 136 } 137 138 func Panicf(ctx context.Context, s string, a ...interface{}) { 139 defer panic(fmt.Sprintf(s, a...)) 140 logf(ctx, 3, level.PANIC, nil, s, a...) 141 } 142 143 // Log will log formatted args as 'msg' field to the log at given level. 144 func Log(ctx context.Context, lvl level.LEVEL, a ...interface{}) { 145 logf(ctx, 3, lvl, nil, args(len(a)), a...) 146 } 147 148 // Logf will log format string as 'msg' field to the log at given level. 149 func Logf(ctx context.Context, lvl level.LEVEL, s string, a ...interface{}) { 150 logf(ctx, 3, lvl, nil, s, a...) 151 } 152 153 // Print will log formatted args to the stdout log output. 154 func Print(a ...interface{}) { 155 printf(3, nil, args(len(a)), a...) 156 } 157 158 // Print will log format string to the stdout log output. 159 func Printf(s string, a ...interface{}) { 160 printf(3, nil, s, a...) 161 } 162 163 func printf(depth int, fields []kv.Field, s string, a ...interface{}) { 164 // Acquire buffer 165 buf := getBuf() 166 167 // Append formatted timestamp 168 buf.B = append(buf.B, `timestamp="`...) 169 buf.B = time.Now().AppendFormat(buf.B, timefmt) 170 buf.B = append(buf.B, `" `...) 171 172 // Append formatted caller func 173 buf.B = append(buf.B, `func=`...) 174 buf.B = append(buf.B, Caller(depth+1)...) 175 buf.B = append(buf.B, ' ') 176 177 if len(fields) > 0 { 178 // Append formatted fields 179 kv.Fields(fields).AppendFormat(buf, false) 180 buf.B = append(buf.B, ' ') 181 } 182 183 // Append formatted args 184 fmt.Fprintf(buf, s, a...) 185 186 if buf.B[len(buf.B)-1] != '\n' { 187 // Append a final newline 188 buf.B = append(buf.B, '\n') 189 } 190 191 if sysout != nil { 192 // Write log entry to syslog 193 logsys(level.INFO, buf.String()) 194 } 195 196 // Write to log and release 197 _, _ = os.Stdout.Write(buf.B) 198 putBuf(buf) 199 } 200 201 func logf(ctx context.Context, depth int, lvl level.LEVEL, fields []kv.Field, s string, a ...interface{}) { 202 var out *os.File 203 204 // Check if enabled. 205 if lvl > Level() { 206 return 207 } 208 209 // Split errors to stderr, 210 // all else goes to stdout. 211 if lvl <= level.ERROR { 212 out = os.Stderr 213 } else { 214 out = os.Stdout 215 } 216 217 // Acquire buffer 218 buf := getBuf() 219 220 // Append formatted timestamp 221 buf.B = append(buf.B, `timestamp="`...) 222 buf.B = time.Now().AppendFormat(buf.B, timefmt) 223 buf.B = append(buf.B, `" `...) 224 225 // Append formatted caller func 226 buf.B = append(buf.B, `func=`...) 227 buf.B = append(buf.B, Caller(depth+1)...) 228 buf.B = append(buf.B, ' ') 229 230 // Append formatted level string 231 buf.B = append(buf.B, `level=`...) 232 buf.B = append(buf.B, lvlstrs[lvl]...) 233 buf.B = append(buf.B, ' ') 234 235 if ctx != nil { 236 // Pass context through hooks. 237 for _, hook := range ctxhooks { 238 fields = hook(ctx, fields) 239 } 240 } 241 242 // Append formatted fields with msg 243 kv.Fields(append(fields, kv.Field{ 244 K: "msg", V: fmt.Sprintf(s, a...), 245 })).AppendFormat(buf, false) 246 247 if buf.B[len(buf.B)-1] != '\n' { 248 // Append a final newline 249 buf.B = append(buf.B, '\n') 250 } 251 252 if sysout != nil { 253 // Write log entry to syslog 254 logsys(lvl, buf.String()) 255 } 256 257 // Write to log and release 258 _, _ = out.Write(buf.B) 259 putBuf(buf) 260 } 261 262 // logsys will log given msg at given severity to the syslog. 263 // Max length: https://www.rfc-editor.org/rfc/rfc5424.html#section-6.1 264 func logsys(lvl level.LEVEL, msg string) { 265 if max := 2048; len(msg) > max { 266 // Truncate up to max 267 msg = msg[:max] 268 } 269 switch lvl { 270 case level.TRACE, level.DEBUG: 271 _ = sysout.Debug(msg) 272 case level.INFO: 273 _ = sysout.Info(msg) 274 case level.WARN: 275 _ = sysout.Warning(msg) 276 case level.ERROR: 277 _ = sysout.Err(msg) 278 case level.FATAL, level.PANIC: 279 _ = sysout.Crit(msg) 280 } 281 } 282 283 // args returns an args format string of format '%v' * count. 284 func args(count int) string { 285 const args = `%v%v%v%v%v%v%v%v%v%v` + 286 `%v%v%v%v%v%v%v%v%v%v` + 287 `%v%v%v%v%v%v%v%v%v%v` + 288 `%v%v%v%v%v%v%v%v%v%v` 289 290 // Use predetermined args str 291 if count < len(args) { 292 return args[:count*2] 293 } 294 295 // Allocate buffer of needed len 296 var buf strings.Builder 297 buf.Grow(count * 2) 298 299 // Manually build an args str 300 for i := 0; i < count; i++ { 301 buf.WriteString(`%v`) 302 } 303 304 return buf.String() 305 }