mediamtx/internal/logger/logger.go

199 lines
3.9 KiB
Go

package logger
import (
"bytes"
"fmt"
"io"
"os"
"sync"
"time"
"github.com/gookit/color"
)
// Level is a log level.
type Level int
// Log levels.
const (
Debug Level = iota
Info
Warn
)
// Destination is a log destination.
type Destination int
const (
// DestinationStdout writes logs to the standard output.
DestinationStdout Destination = iota
// DestinationFile writes logs to a file.
DestinationFile
// DestinationSyslog writes logs to the system logger.
DestinationSyslog
)
// Logger is a log handler.
type Logger struct {
level Level
destinations map[Destination]struct{}
mutex sync.Mutex
file *os.File
syslog io.WriteCloser
stdoutBuffer bytes.Buffer
fileBuffer bytes.Buffer
syslogBuffer bytes.Buffer
}
// New allocates a log handler.
func New(level Level, destinations map[Destination]struct{}, filePath string) (*Logger, error) {
lh := &Logger{
level: level,
destinations: destinations,
}
if _, ok := destinations[DestinationFile]; ok {
var err error
lh.file, err = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
lh.Close()
return nil, err
}
}
if _, ok := destinations[DestinationSyslog]; ok {
var err error
lh.syslog, err = newSyslog("rtsp-simple-server")
if err != nil {
lh.Close()
return nil, err
}
}
return lh, nil
}
// Close closes a log handler.
func (lh *Logger) Close() {
if lh.file != nil {
lh.file.Close()
}
if lh.syslog != nil {
lh.syslog.Close()
}
}
// https://golang.org/src/log/log.go#L78
func itoa(i int, wid int) []byte {
// Assemble decimal in reverse order.
var b [20]byte
bp := len(b) - 1
for i >= 10 || wid > 1 {
wid--
q := i / 10
b[bp] = byte('0' + i - q*10)
bp--
i = q
}
// i < 10
b[bp] = byte('0' + i)
return b[bp:]
}
func writeTime(buf *bytes.Buffer, doColor bool) {
var intbuf bytes.Buffer
// date
now := time.Now()
year, month, day := now.Date()
intbuf.Write(itoa(year, 4))
intbuf.WriteByte('/')
intbuf.Write(itoa(int(month), 2))
intbuf.WriteByte('/')
intbuf.Write(itoa(day, 2))
intbuf.WriteByte(' ')
// time
hour, min, sec := now.Clock()
intbuf.Write(itoa(hour, 2))
intbuf.WriteByte(':')
intbuf.Write(itoa(min, 2))
intbuf.WriteByte(':')
intbuf.Write(itoa(sec, 2))
intbuf.WriteByte(' ')
if doColor {
buf.WriteString(color.RenderString(color.Gray.Code(), intbuf.String()))
} else {
buf.WriteString(intbuf.String())
}
}
func writeLevel(buf *bytes.Buffer, level Level, doColor bool) {
switch level {
case Debug:
if doColor {
buf.WriteString(color.RenderString(color.Debug.Code(), "D "))
} else {
buf.WriteString("D ")
}
case Info:
if doColor {
buf.WriteString(color.RenderString(color.Green.Code(), "I "))
} else {
buf.WriteString("I ")
}
case Warn:
if doColor {
buf.WriteString(color.RenderString(color.Warn.Code(), "W "))
} else {
buf.WriteString("W ")
}
}
}
func writeContent(buf *bytes.Buffer, format string, args []interface{}) {
buf.Write([]byte(fmt.Sprintf(format, args...)))
buf.WriteByte('\n')
}
// Log writes a log entry.
func (lh *Logger) Log(level Level, format string, args ...interface{}) {
if level < lh.level {
return
}
lh.mutex.Lock()
defer lh.mutex.Unlock()
if _, ok := lh.destinations[DestinationStdout]; ok {
lh.stdoutBuffer.Reset()
writeTime(&lh.stdoutBuffer, true)
writeLevel(&lh.stdoutBuffer, level, true)
writeContent(&lh.stdoutBuffer, format, args)
print(lh.stdoutBuffer.String())
}
if _, ok := lh.destinations[DestinationFile]; ok {
lh.fileBuffer.Reset()
writeTime(&lh.fileBuffer, false)
writeLevel(&lh.fileBuffer, level, false)
writeContent(&lh.fileBuffer, format, args)
lh.file.Write(lh.fileBuffer.Bytes())
}
if _, ok := lh.destinations[DestinationSyslog]; ok {
lh.syslogBuffer.Reset()
writeTime(&lh.syslogBuffer, false)
writeLevel(&lh.syslogBuffer, level, false)
writeContent(&lh.syslogBuffer, format, args)
lh.syslog.Write(lh.syslogBuffer.Bytes())
}
}