blob: a013d16ad0be37c647316d6725c2e16eacbdecff [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build !build_with_native_toolchain
package syslog
import (
"context"
"encoding/binary"
"errors"
"flag"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync/atomic"
"syscall/zx"
"unicode/utf8"
"go.fuchsia.dev/fuchsia/src/lib/component"
"fidl/fuchsia/logger"
)
// ErrMsgTooLong is returned when a message is truncated.
type ErrMsgTooLong struct {
// Msg is the truncated part of the message.
Msg string
}
func (e *ErrMsgTooLong) Error() string {
return "too long message was truncated"
}
const (
_ = iota
DebugVerbosity
TraceVerbosity
)
type LogLevel int32
const (
AllLevel = LogLevel(logger.LogLevelFilterAll)
TraceLevel = LogLevel(logger.LogLevelFilterTrace)
DebugLevel = LogLevel(logger.LogLevelFilterDebug)
InfoLevel = LogLevel(logger.LogLevelFilterInfo)
WarningLevel = LogLevel(logger.LogLevelFilterWarn)
ErrorLevel = LogLevel(logger.LogLevelFilterError)
FatalLevel = LogLevel(logger.LogLevelFilterFatal)
NoneLevel = LogLevel(logger.LogLevelFilterNone)
)
var _ flag.Value = (*LogLevel)(nil)
// Set implements the flag.Value interface.
func (ll *LogLevel) Set(s string) error {
s = strings.ToUpper(s)
lls := logLevelFromString(s)
if lls != NoneLevel {
*ll = lls
return nil
}
v, err := strconv.Atoi(s)
if err != nil {
return err
}
*ll = LogLevel(v)
return nil
}
func logLevelFromString(s string) LogLevel {
switch s {
case "ALL":
return AllLevel
case "TRACE":
return TraceLevel
case "DEBUG":
return DebugLevel
case "INFO":
return InfoLevel
case "WARNING":
return WarningLevel
case "ERROR":
return ErrorLevel
case "FATAL":
return FatalLevel
default:
return NoneLevel
}
}
func (ll LogLevel) String() string {
switch ll {
case AllLevel:
return "ALL"
case TraceLevel:
return "TRACE"
case DebugLevel:
return "DEBUG"
case InfoLevel:
return "INFO"
case WarningLevel:
return "WARNING"
case ErrorLevel:
return "ERROR"
case FatalLevel:
return "FATAL"
default:
return fmt.Sprintf("LOG(%d)", ll)
}
}
type Writer struct {
*Logger
}
func (l *Writer) Write(data []byte) (n int, err error) {
origLen := len(data)
// Strip out the trailing newline the `log` library adds because the
// logging service also adds a trailing newline.
if len(data) > 0 && data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
if err := l.Infof("%s", data); err != nil {
return 0, err
}
return origLen, nil
}
func ConnectToLogger(c *component.Connector) (zx.Socket, error) {
localS, peerS, err := zx.NewSocket(zx.SocketDatagram)
if err != nil {
return zx.Socket(zx.HandleInvalid), err
}
req, logSink, err := logger.NewLogSinkWithCtxInterfaceRequest()
if err != nil {
_ = localS.Close()
_ = peerS.Close()
return zx.Socket(zx.HandleInvalid), err
}
c.ConnectToEnvService(req)
{
err := logSink.Connect(context.Background(), peerS)
_ = logSink.Close()
if err != nil {
_ = localS.Close()
return zx.Socket(zx.HandleInvalid), err
}
}
return localS, nil
}
type LogInitOptions struct {
LogLevel LogLevel // Accessed atomically.
MinSeverityForFileAndLineInfo LogLevel
Socket zx.Socket
Tags []string
Writer io.Writer
}
type Logger struct {
droppedLogs uint32
options LogInitOptions
pid uint64
}
func NewLogger(options LogInitOptions) (*Logger, error) {
if l, max := len(options.Tags), int(logger.MaxTags); l > max {
return nil, fmt.Errorf("too many tags: %d/%d", l, max)
}
for _, tag := range options.Tags {
if l, max := len(tag), int(logger.MaxTagLenBytes); l > max {
return nil, fmt.Errorf("tag too long: %d/%d", l, max)
}
}
return &Logger{
options: options,
pid: uint64(os.Getpid()),
}, nil
}
func NewLoggerWithDefaults(c *component.Connector, tags ...string) (*Logger, error) {
s, err := ConnectToLogger(c)
if err != nil {
return nil, err
}
return NewLogger(LogInitOptions{
LogLevel: InfoLevel,
MinSeverityForFileAndLineInfo: ErrorLevel,
Socket: s,
Tags: tags,
})
}
func (l *Logger) logToWriter(writer io.Writer, time zx.Time, logLevel LogLevel, tag, msg string) error {
if writer == nil {
writer = os.Stderr
}
var tagsStorage [logger.MaxTags + 1]string
tags := tagsStorage[:0]
for _, tag := range l.options.Tags {
if len(tag) > 0 {
tags = append(tags, tag)
}
}
if len(tag) > 0 {
tags = append(tags, tag)
}
var buffer [int(logger.MaxTags+1) * int(logger.MaxTagLenBytes)]byte
pos := 0
for i, tag := range tags {
if i > 0 {
pos += copy(buffer[pos:], ", ")
}
pos += copy(buffer[pos:], tag)
}
_, err := fmt.Fprintf(writer, "[%05d.%06d][%d][0][%s] %s: %s\n", time/1000000000, (time/1000)%1000000, l.pid, buffer[:pos], logLevel, msg)
return err
}
func (l *Logger) logToSocket(time zx.Time, logLevel LogLevel, tag, msg string) error {
const golangThreadID = 0
var buffer [logger.MaxDatagramLenBytes]byte
pos := 0
for _, i := range [...]uint64{
l.pid,
golangThreadID,
uint64(time),
} {
binary.LittleEndian.PutUint64(buffer[pos:], i)
pos += 8
}
for _, i := range [...]uint32{
uint32(logLevel),
atomic.LoadUint32(&l.droppedLogs),
} {
binary.LittleEndian.PutUint32(buffer[pos:], i)
pos += 4
}
// Write global tags
for _, tag := range l.options.Tags {
if length := len(tag); length != 0 {
buffer[pos] = byte(length)
pos += 1
pos += copy(buffer[pos:], tag)
}
}
// Write local tags
if length := len(tag); length != 0 {
buffer[pos] = byte(length)
pos += 1
pos += copy(buffer[pos:], tag)
}
const ellipsis = "..."
// Write msg
buffer[pos] = 0
pos += 1
payload := msg
if len(payload)+1 > len(buffer)-pos {
payload = payload[:len(buffer)-pos-1-len(ellipsis)]
// Remove the last byte until the result is valid UTF-8.
for {
if rune, _ := utf8.DecodeLastRuneInString(payload); rune != utf8.RuneError {
break
}
payload = payload[:len(payload)-1]
}
}
pos += copy(buffer[pos:], payload)
if payload != msg {
pos += copy(buffer[pos:], ellipsis)
}
buffer[pos] = 0
pos += 1
if _, err := l.options.Socket.Write(buffer[:pos], 0); err != nil {
atomic.AddUint32(&l.droppedLogs, 1)
return err
}
if payload != msg {
return &ErrMsgTooLong{Msg: msg[len(payload):]}
}
return nil
}
func (l *Logger) logf(callDepth int, logLevel LogLevel, tag string, format string, a ...interface{}) error {
if LogLevel(atomic.LoadInt32((*int32)(&l.options.LogLevel))) > logLevel {
return nil
}
time := zx.Sys_clock_get_monotonic()
msg := fmt.Sprintf(format, a...)
if logLevel >= l.options.MinSeverityForFileAndLineInfo {
_, file, line, ok := runtime.Caller(callDepth)
if !ok {
file = "???"
line = 0
} else {
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
}
file = short
}
msg = fmt.Sprintf("%s(%d): %s ", file, line, msg)
}
if len(tag) > int(logger.MaxTagLenBytes) {
tag = tag[:logger.MaxTagLenBytes]
}
if logLevel == FatalLevel {
defer os.Exit(1)
}
if atomic.LoadUint32((*uint32)(&l.options.Socket)) != uint32(zx.HandleInvalid) {
switch err := l.logToSocket(time, logLevel, tag, msg).(type) {
case *zx.Error:
switch err.Status {
case zx.ErrPeerClosed, zx.ErrBadState:
atomic.StoreUint32((*uint32)(&l.options.Socket), uint32(zx.HandleInvalid))
default:
return err
}
default:
return err
}
}
return l.logToWriter(l.options.Writer, time, logLevel, tag, msg)
}
func (l *Logger) SetSeverity(logLevel LogLevel) {
atomic.StoreInt32((*int32)(&l.options.LogLevel), int32(logLevel))
}
// severityFromVerbosity provides the severity corresponding to the given
// verbosity. Note that verbosity relative to the default severity and can
// be thought of as incrementally "more vebose than" the baseline.
func severityFromVerbosity(verbosity int) LogLevel {
if verbosity < 0 {
verbosity = 0
}
level := InfoLevel - LogLevel(uint8(verbosity)*logger.LogVerbosityStepSize)
// verbosity scale sits in the interstitial space between INFO and DEBUG
if level < (DebugLevel + 1) {
return DebugLevel + 1
}
return level
}
func (l *Logger) SetVerbosity(verbosity int) {
atomic.StoreInt32((*int32)(&l.options.LogLevel), int32(severityFromVerbosity(verbosity)))
}
func (l *Logger) Tracef(format string, a ...interface{}) error {
return l.logf(2, TraceLevel, "", format, a...)
}
func (l *Logger) Debugf(format string, a ...interface{}) error {
return l.logf(2, DebugLevel, "", format, a...)
}
func (l *Logger) Infof(format string, a ...interface{}) error {
return l.logf(2, InfoLevel, "", format, a...)
}
func (l *Logger) Warnf(format string, a ...interface{}) error {
return l.logf(2, WarningLevel, "", format, a...)
}
func (l *Logger) Errorf(format string, a ...interface{}) error {
return l.logf(2, ErrorLevel, "", format, a...)
}
func (l *Logger) Fatalf(format string, a ...interface{}) error {
return l.logf(2, FatalLevel, "", format, a...)
}
func (l *Logger) VLogf(verbosity int, format string, a ...interface{}) error {
return l.logf(2, severityFromVerbosity(verbosity), "", format, a...)
}
func (l *Logger) TraceTf(tag, format string, a ...interface{}) error {
return l.logf(2, TraceLevel, tag, format, a...)
}
func (l *Logger) DebugTf(tag, format string, a ...interface{}) error {
return l.logf(2, DebugLevel, tag, format, a...)
}
func (l *Logger) InfoTf(tag, format string, a ...interface{}) error {
return l.logf(2, InfoLevel, tag, format, a...)
}
func (l *Logger) WarnTf(tag, format string, a ...interface{}) error {
return l.logf(2, WarningLevel, tag, format, a...)
}
func (l *Logger) ErrorTf(tag, format string, a ...interface{}) error {
return l.logf(2, ErrorLevel, tag, format, a...)
}
func (l *Logger) FatalTf(tag, format string, a ...interface{}) error {
return l.logf(2, FatalLevel, tag, format, a...)
}
func (l *Logger) VLogTf(verbosity int, tag, format string, a ...interface{}) error {
return l.logf(2, severityFromVerbosity(verbosity), tag, format, a...)
}
var defaultLogger = &Logger{
options: LogInitOptions{
LogLevel: InfoLevel,
MinSeverityForFileAndLineInfo: ErrorLevel,
Writer: os.Stderr,
},
pid: uint64(os.Getpid()),
}
func logf(callDepth int, logLevel LogLevel, tag string, format string, a ...interface{}) error {
if l := GetDefaultLogger(); l != nil {
return l.logf(callDepth+1, logLevel, tag, format, a...)
}
return errors.New("default logger not initialized")
}
// Public APIs for default logger
func GetDefaultLogger() *Logger {
return defaultLogger
}
func SetDefaultLogger(l *Logger) {
defaultLogger = l
}
func SetSeverity(logLevel LogLevel) {
if l := GetDefaultLogger(); l != nil {
atomic.StoreInt32((*int32)(&l.options.LogLevel), int32(logLevel))
}
}
func SetVerbosity(verbosity int) {
if l := GetDefaultLogger(); l != nil {
atomic.StoreInt32((*int32)(&l.options.LogLevel), int32(severityFromVerbosity(verbosity)))
}
}
func Tracef(format string, a ...interface{}) error {
return logf(2, TraceLevel, "", format, a...)
}
func Debugf(format string, a ...interface{}) error {
return logf(2, DebugLevel, "", format, a...)
}
func Infof(format string, a ...interface{}) error {
return logf(2, InfoLevel, "", format, a...)
}
func Warnf(format string, a ...interface{}) error {
return logf(2, WarningLevel, "", format, a...)
}
func Errorf(format string, a ...interface{}) error {
return logf(2, ErrorLevel, "", format, a...)
}
func Fatalf(format string, a ...interface{}) error {
return logf(2, FatalLevel, "", format, a...)
}
func VLogf(verbosity int, format string, a ...interface{}) error {
return logf(2, severityFromVerbosity(verbosity), "", format, a...)
}
func TraceTf(tag, format string, a ...interface{}) error {
return logf(2, TraceLevel, tag, format, a...)
}
func DebugTf(tag, format string, a ...interface{}) error {
return logf(2, DebugLevel, tag, format, a...)
}
func InfoTf(tag, format string, a ...interface{}) error {
return logf(2, InfoLevel, tag, format, a...)
}
func WarnTf(tag, format string, a ...interface{}) error {
return logf(2, WarningLevel, tag, format, a...)
}
func ErrorTf(tag, format string, a ...interface{}) error {
return logf(2, ErrorLevel, tag, format, a...)
}
func FatalTf(tag, format string, a ...interface{}) error {
return logf(2, FatalLevel, tag, format, a...)
}
func VLogTf(verbosity int, tag, format string, a ...interface{}) error {
return logf(2, severityFromVerbosity(verbosity), tag, format, a...)
}