340 lines
7.6 KiB
Go
340 lines
7.6 KiB
Go
// SiYuan - Build Your Eternal Digital Garden
|
|
// Copyright (c) 2020-present, b3log.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 <https://www.gnu.org/licenses/>.
|
|
|
|
package util
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
stdlog "log"
|
|
"os"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
|
|
"github.com/88250/gulu"
|
|
"github.com/getsentry/sentry-go"
|
|
)
|
|
|
|
func ShortStack() string {
|
|
output := string(debug.Stack())
|
|
lines := strings.Split(output, "\n")
|
|
if 5 < len(lines) {
|
|
lines = lines[5:]
|
|
}
|
|
buf := bytes.Buffer{}
|
|
for _, l := range lines {
|
|
if strings.Contains(l, "gin-gonic") {
|
|
break
|
|
}
|
|
buf.WriteString(" ")
|
|
buf.WriteString(l)
|
|
buf.WriteByte('\n')
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
var (
|
|
logger *Logger
|
|
logFile *os.File
|
|
)
|
|
|
|
func LogTracef(format string, v ...interface{}) {
|
|
if !logger.IsTraceEnabled() {
|
|
return
|
|
}
|
|
|
|
defer closeLogger()
|
|
openLogger()
|
|
logger.Tracef(format, v...)
|
|
}
|
|
|
|
func LogDebugf(format string, v ...interface{}) {
|
|
if !logger.IsDebugEnabled() {
|
|
return
|
|
}
|
|
|
|
defer closeLogger()
|
|
openLogger()
|
|
logger.Debugf(format, v...)
|
|
}
|
|
|
|
func LogInfof(format string, v ...interface{}) {
|
|
defer closeLogger()
|
|
openLogger()
|
|
logger.Infof(format, v...)
|
|
}
|
|
|
|
func LogErrorf(format string, v ...interface{}) {
|
|
defer closeLogger()
|
|
openLogger()
|
|
logger.Errorf(format, v...)
|
|
}
|
|
|
|
func LogWarnf(format string, v ...interface{}) {
|
|
if !logger.IsWarnEnabled() {
|
|
return
|
|
}
|
|
|
|
defer closeLogger()
|
|
openLogger()
|
|
logger.Warnf(format, v...)
|
|
}
|
|
|
|
func LogFatalf(format string, v ...interface{}) {
|
|
openLogger()
|
|
logger.Fatalf(format, v...)
|
|
}
|
|
|
|
func openLogger() {
|
|
if gulu.File.IsExist(LogPath) {
|
|
if size := gulu.File.GetFileSize(LogPath); 1024*1024*2 <= size {
|
|
// 日志文件大于 2M 的话删了重建
|
|
os.Remove(LogPath)
|
|
}
|
|
}
|
|
|
|
var err error
|
|
logFile, err = os.OpenFile(LogPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
|
if nil != err {
|
|
stdlog.Fatalf("create log file [%s] failed: %s", LogPath, err)
|
|
}
|
|
logger = NewLogger(io.MultiWriter(os.Stdout, logFile))
|
|
}
|
|
|
|
func closeLogger() {
|
|
logFile.Close()
|
|
}
|
|
|
|
func Recover() {
|
|
if e := recover(); nil != e {
|
|
stack := stack()
|
|
msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, stack)
|
|
LogErrorf(msg)
|
|
}
|
|
}
|
|
|
|
var (
|
|
dunno = []byte("???")
|
|
centerDot = []byte("·")
|
|
dot = []byte(".")
|
|
slash = []byte("/")
|
|
)
|
|
|
|
// stack implements Stack, skipping 2 frames.
|
|
func stack() []byte {
|
|
buf := &bytes.Buffer{} // the returned data
|
|
// As we loop, we open files and read them. These variables record the currently
|
|
// loaded file.
|
|
var lines [][]byte
|
|
var lastFile string
|
|
for i := 2; ; i++ { // Caller we care about is the user, 2 frames up
|
|
pc, file, line, ok := runtime.Caller(i)
|
|
if !ok {
|
|
break
|
|
}
|
|
// Print this much at least. If we can't find the source, it won't show.
|
|
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
|
if file != lastFile {
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
lines = bytes.Split(data, []byte{'\n'})
|
|
lastFile = file
|
|
}
|
|
line-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
|
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// source returns a space-trimmed slice of the n'th line.
|
|
func source(lines [][]byte, n int) []byte {
|
|
if n < 0 || n >= len(lines) {
|
|
return dunno
|
|
}
|
|
return bytes.Trim(lines[n], " \t")
|
|
}
|
|
|
|
// function returns, if possible, the name of the function containing the PC.
|
|
func function(pc uintptr) []byte {
|
|
fn := runtime.FuncForPC(pc)
|
|
if fn == nil {
|
|
return dunno
|
|
}
|
|
name := []byte(fn.Name())
|
|
// The name includes the path name to the package, which is unnecessary
|
|
// since the file name is already included. Plus, it has center dots.
|
|
// That is, we see
|
|
// runtime/debug.*T·ptrmethod
|
|
// and want
|
|
// *T.ptrmethod
|
|
// Since the package path might contains dots (e.g. code.google.com/...),
|
|
// we first remove the path prefix if there is one.
|
|
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
|
name = name[lastslash+1:]
|
|
}
|
|
if period := bytes.Index(name, dot); period >= 0 {
|
|
name = name[period+1:]
|
|
}
|
|
name = bytes.Replace(name, centerDot, dot, -1)
|
|
return name
|
|
}
|
|
|
|
// Logging level.
|
|
const (
|
|
Off = iota
|
|
Trace
|
|
Debug
|
|
Info
|
|
Warn
|
|
Error
|
|
Fatal
|
|
)
|
|
|
|
// the global default logging level, it will be used for creating logger.
|
|
var logLevel = Debug
|
|
|
|
// Logger represents a simple logger with level.
|
|
// The underlying logger is the standard Go logging "log".
|
|
type Logger struct {
|
|
level int
|
|
logger *stdlog.Logger
|
|
}
|
|
|
|
// NewLogger creates a logger.
|
|
func NewLogger(out io.Writer) *Logger {
|
|
ret := &Logger{level: logLevel, logger: stdlog.New(out, "", stdlog.Ldate|stdlog.Ltime|stdlog.Lshortfile)}
|
|
return ret
|
|
}
|
|
|
|
// SetLogLevel sets the logging level of all loggers.
|
|
func SetLogLevel(level string) {
|
|
logLevel = getLevel(level)
|
|
}
|
|
|
|
// getLevel gets logging level int value corresponding to the specified level.
|
|
func getLevel(level string) int {
|
|
level = strings.ToLower(level)
|
|
|
|
switch level {
|
|
case "off":
|
|
return Off
|
|
case "trace":
|
|
return Trace
|
|
case "debug":
|
|
return Debug
|
|
case "info":
|
|
return Info
|
|
case "warn":
|
|
return Warn
|
|
case "error":
|
|
return Error
|
|
case "fatal":
|
|
return Fatal
|
|
default:
|
|
return Info
|
|
}
|
|
}
|
|
|
|
// SetLevel sets the logging level of a logger.
|
|
func (l *Logger) SetLevel(level string) {
|
|
l.level = getLevel(level)
|
|
}
|
|
|
|
// IsTraceEnabled determines whether the trace level is enabled.
|
|
func (l *Logger) IsTraceEnabled() bool {
|
|
return l.level <= Trace
|
|
}
|
|
|
|
// IsDebugEnabled determines whether the debug level is enabled.
|
|
func (l *Logger) IsDebugEnabled() bool {
|
|
return l.level <= Debug
|
|
}
|
|
|
|
// IsWarnEnabled determines whether the debug level is enabled.
|
|
func (l *Logger) IsWarnEnabled() bool {
|
|
return l.level <= Warn
|
|
}
|
|
|
|
// Tracef prints trace level message with format.
|
|
func (l *Logger) Tracef(format string, v ...interface{}) {
|
|
if Trace < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("T ")
|
|
l.logger.Output(3, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// Debugf prints debug level message with format.
|
|
func (l *Logger) Debugf(format string, v ...interface{}) {
|
|
if Debug < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("D ")
|
|
l.logger.Output(3, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// Infof prints info level message with format.
|
|
func (l *Logger) Infof(format string, v ...interface{}) {
|
|
if Info < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("I ")
|
|
l.logger.Output(3, fmt.Sprintf(format, v...))
|
|
}
|
|
|
|
// Warnf prints warning level message with format.
|
|
func (l *Logger) Warnf(format string, v ...interface{}) {
|
|
if Warn < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("W ")
|
|
msg := fmt.Sprintf(format, v...)
|
|
l.logger.Output(3, msg)
|
|
}
|
|
|
|
// Errorf prints error level message with format.
|
|
func (l *Logger) Errorf(format string, v ...interface{}) {
|
|
if Error < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("E ")
|
|
msg := fmt.Sprintf(format, v...)
|
|
l.logger.Output(3, msg)
|
|
sentry.CaptureMessage(msg)
|
|
}
|
|
|
|
// Fatalf prints fatal level message with format and exit process with code 1.
|
|
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
|
if Fatal < l.level {
|
|
return
|
|
}
|
|
|
|
l.logger.SetPrefix("F ")
|
|
msg := fmt.Sprintf(format, v...)
|
|
l.logger.Output(3, msg)
|
|
sentry.CaptureMessage(msg)
|
|
closeLogger()
|
|
os.Exit(ExitCodeFatal)
|
|
}
|