blob: 9cbc6cbdf21eb6ad6b2a2317fa19071ed0921d80 [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.
package logger
import (
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"syscall/zx"
"app/context"
logger_fidl "fidl/fuchsia/logger"
)
var (
ErrNotInitialized = errors.New("default logger not initialized")
ErrInitialized = errors.New("default logger is already initialized")
ErrInvalidArg = errors.New("Invalid arguments to logger")
ErrMsgTooLongString = "Msg too long. it was truncated"
)
// This is returned when msg is truncated. It contains the truncated part.
type ErrMsgTooLong struct {
Msg string // rest of the message
}
func (e ErrMsgTooLong) Error() string {
return ErrMsgTooLongString
}
const (
MAX_GLOBAL_TAGS = 4
MAX_TAG_LEN = 63
SOCKET_BUFFER_LEN = 2032
SOCKET_DATAGRAM = 1 << 0
)
type LogLevel int32
const (
InfoLevel LogLevel = iota
WarningLevel
ErrorLevel
FatalLevel
)
func (ll LogLevel) String() string {
switch ll {
case InfoLevel:
return "INFO"
case WarningLevel:
return "WARNING"
case ErrorLevel:
return "ERROR"
case FatalLevel:
return "FATAL"
default:
if int(ll) < 0 {
return fmt.Sprintf("VLOG(%d)", -(int(ll)))
}
return "INVALID"
}
}
type LogInitOptions struct {
Connector *context.Connector
Loglevel LogLevel
ConsoleWriter io.Writer
LogServiceChannel *zx.Socket
Tags []string
}
func GetDefaultInitOptions() LogInitOptions {
return LogInitOptions{
Loglevel: InfoLevel,
ConsoleWriter: nil,
LogServiceChannel: nil,
Tags: nil,
}
}
type Logger struct {
logLevel LogLevel
tags []string
socket *zx.Socket
writer atomic.Value
tagString string
pid uint64
droppedLogs uint32
fallbackMux sync.Mutex
}
func (l *Logger) setTags(tags []string) error {
if len(tags) > MAX_GLOBAL_TAGS {
return ErrInvalidArg
}
for _, tag := range tags {
if len(tag) > MAX_TAG_LEN {
return ErrInvalidArg
}
}
if l.writer.Load() != nil {
l.tagString = strings.Join(tags, ", ")
}
l.tags = tags
return nil
}
func (l *Logger) ActivateFallbackMode() {
l.fallbackMux.Lock()
defer l.fallbackMux.Unlock()
if l.writer.Load() != nil {
return //already active
}
if l.tagString == "" {
l.tagString = strings.Join(l.tags, ", ")
}
l.writer.Store(os.Stderr)
}
func connectToLogger(c *context.Connector) (*zx.Socket, error) {
sin, sout, err := zx.NewSocket(SOCKET_DATAGRAM)
if err != nil {
return nil, err
}
req, ls, err := logger_fidl.NewLogSinkInterfaceRequest()
if err != nil {
return nil, err
}
c.ConnectToEnvService(req)
err = ls.Connect(sout)
ls.Close()
return &sin, err
}
func NewLogger(options LogInitOptions) (*Logger, error) {
l := Logger{
logLevel: options.Loglevel,
socket: options.LogServiceChannel,
pid: uint64(os.Getpid()),
}
if options.ConsoleWriter == nil && options.LogServiceChannel == nil {
if options.Connector == nil {
return nil, fmt.Errorf("Init Error: Writer, LogServiceChannel or Connector needs to be provided")
}
if sock, err := connectToLogger(options.Connector); err != nil {
fmt.Fprintf(os.Stderr, "not able to conenct to log sink, will write logs to stderr: %s", err)
l.writer.Store(os.Stderr)
} else {
l.socket = sock
}
} else if options.ConsoleWriter != nil {
l.writer.Store(options.ConsoleWriter)
}
if err := l.setTags(options.Tags); err != nil {
return nil, err
}
return &l, nil
}
func (l *Logger) logToWriter(writer io.Writer, time zx.Time, logLevel LogLevel, tag, msg string) error {
if len(l.tagString) != 0 {
if len(tag) != 0 {
tag = fmt.Sprintf("%s, %s", l.tagString, tag)
} else {
tag = l.tagString
}
}
_, err := io.WriteString(writer, fmt.Sprintf("[%05d.%06d][%d][0][%s] %s: %s\n", time/1000000000, (time/1000)%1000000, l.pid, tag, logLevel, msg))
return err
}
func (l *Logger) logToSocket(time zx.Time, logLevel LogLevel, tag, msg string) error {
var buffer [SOCKET_BUFFER_LEN]byte
binary.LittleEndian.PutUint64(buffer[0:8], l.pid)
binary.LittleEndian.PutUint64(buffer[8:16], 0) // golang doesn't have tid
binary.LittleEndian.PutUint64(buffer[16:24], uint64(time))
binary.LittleEndian.PutUint32(buffer[24:28], uint32(logLevel))
binary.LittleEndian.PutUint32(buffer[28:32], atomic.LoadUint32(&l.droppedLogs))
pos := 32
// Write global tags
for _, tag := range l.tags {
length := len(tag)
if length == 0 {
continue
}
buffer[pos] = byte(length)
pos = pos + 1
copy(buffer[pos:pos+length], tag)
pos = pos + length
}
// Write local tags
if len(tag) != 0 {
length := len(tag)
buffer[pos] = byte(length)
pos = pos + 1
copy(buffer[pos:pos+length], tag)
pos = pos + length
}
//write msg
buffer[pos] = 0
pos = pos + 1
errMsgTooLong := ErrMsgTooLong{""}
if len(msg) >= SOCKET_BUFFER_LEN-pos {
// truncate
errMsgTooLong.Msg = msg[SOCKET_BUFFER_LEN-pos-4:]
msg = msg[:SOCKET_BUFFER_LEN-pos-4]
copy(buffer[pos:], msg)
copy(buffer[pos+len(msg):], "...")
pos = pos + len(msg) + 3
} else {
copy(buffer[pos:], msg)
pos = pos + len(msg)
}
buffer[pos] = 0
if _, err := l.socket.Write(buffer[:pos+1], 0); err != nil {
atomic.AddUint32(&l.droppedLogs, 1)
return err
}
if errMsgTooLong.Msg != "" {
return errMsgTooLong
}
return nil
}
func (l *Logger) logf(callDepth int, logLevel LogLevel, tag string, format string, a ...interface{}) error {
if l.logLevel > logLevel {
return nil
}
time := zx.Sys_clock_get(0) //MONOTONIC
msg := fmt.Sprintf(format, a...)
if logLevel >= ErrorLevel {
_, 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) > MAX_TAG_LEN {
tag = tag[:MAX_TAG_LEN]
}
if logLevel == FatalLevel {
defer os.Exit(1)
}
w := l.writer.Load()
if w != nil {
return l.logToWriter(w.(io.Writer), time, logLevel, tag, msg)
} else {
if err := l.logToSocket(time, logLevel, tag, msg); err != nil {
if status, ok := err.(zx.Error); ok {
if status.Status == zx.ErrPeerClosed || status.Status == zx.ErrBadState {
l.ActivateFallbackMode()
w = l.writer.Load()
if w != nil {
return l.logToWriter(w.(io.Writer), time, logLevel, tag, msg)
}
}
}
return err
}
}
return nil
}
func (l *Logger) SetSeverity(logLevel LogLevel) {
l.logLevel = logLevel
}
func (l *Logger) SetVerbosity(verbosity int) {
l.logLevel = LogLevel(-verbosity)
}
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, LogLevel(-verbosity), "", 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, LogLevel(-verbosity), tag, format, a...)
}
var defaultLogger *Logger
func logf(callDepth int, logLevel LogLevel, tag string, format string, a ...interface{}) error {
l := GetDefaultLogger()
if l != nil {
return l.logf(callDepth+1, logLevel, tag, format, a...)
} else {
return ErrNotInitialized
}
}
// Public APIs for default logger
func GetDefaultLogger() *Logger {
return defaultLogger
}
func InitDefaultLogger(c *context.Connector) error {
options := GetDefaultInitOptions()
options.Connector = c
return InitDefaultLoggerWithConfig(options)
}
func InitDefaultLoggerWithTags(c *context.Connector, tags ...string) error {
o := GetDefaultInitOptions()
o.Tags = tags
o.Connector = c
return InitDefaultLoggerWithConfig(o)
}
func InitDefaultLoggerWithConfig(options LogInitOptions) error {
if defaultLogger != nil {
return ErrInitialized
}
if l, err := NewLogger(options); err != nil {
return err
} else {
defaultLogger = l
}
return nil
}
func SetSeverity(logLevel LogLevel) {
if l := GetDefaultLogger(); l != nil {
l.logLevel = logLevel
}
}
func SetVerbosity(verbosity int) {
if l := GetDefaultLogger(); l != nil {
l.logLevel = LogLevel(-verbosity)
}
}
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, LogLevel(-verbosity), "", 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, LogLevel(-verbosity), tag, format, a...)
}
func ToErrMsgTooLong(e error) *ErrMsgTooLong {
if err, ok := e.(ErrMsgTooLong); ok {
return &err
} else {
return nil
}
}