gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }