blob: 16c94b453b911692b4fa5f412d91d115d4383eb9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package common
import (
"errors"
"fmt"
"htrace/conf"
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
// A logSink is a place logs can be written to.
type logSink struct {
path logPath
file *os.File
lock sync.Mutex
refCount int // protected by logFilesLock
}
// Write to the logSink.
func (sink *logSink) write(str string) {
sink.lock.Lock()
defer sink.lock.Unlock()
_, err := sink.file.Write([]byte(str))
if err != nil {
fmt.Fprintf(os.Stderr, "Error logging to '%s': %s\n", sink.path, err.Error())
}
}
// Unreference the logSink. If there are no more references, and the logSink is
// closeable, then we will close it here.
func (sink *logSink) Unref() {
logFilesLock.Lock()
defer logFilesLock.Unlock()
sink.refCount--
if sink.refCount <= 0 {
if sink.path.IsCloseable() {
err := sink.file.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "Error closing log file %s: %s\n",
sink.path, err.Error())
}
}
logSinks[sink.path] = nil
}
}
type logPath string
// An empty LogPath represents "stdout."
const STDOUT_LOG_PATH = ""
// Convert a path to a logPath.
func logPathFromString(path string) logPath {
if path == STDOUT_LOG_PATH {
return logPath("")
}
absPath, err := filepath.Abs(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get absolute path of %s: %s\n",
path, err.Error())
return logPath(path)
}
return logPath(absPath)
}
// Convert the path to a human-readable string.
func (path logPath) String() string {
if path == "" {
return "(stdout)"
} else {
return string(path)
}
}
// Return true if the path is closeable. stdout is not closeable.
func (path logPath) IsCloseable() bool {
return path != STDOUT_LOG_PATH
}
func (path logPath) Open() *logSink {
if path == STDOUT_LOG_PATH {
return &logSink{path: path, file: os.Stdout}
}
file, err := os.OpenFile(string(path), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
sink := &logSink{path: STDOUT_LOG_PATH, file: os.Stdout}
fmt.Fprintf(os.Stderr, "Failed to open log file %s: %s\n",
path, err.Error())
return sink
}
return &logSink{path: path, file: file}
}
var logFilesLock sync.Mutex
var logSinks map[logPath]*logSink = make(map[logPath]*logSink)
func getOrCreateLogSink(pathStr string) *logSink {
path := logPathFromString(pathStr)
logFilesLock.Lock()
defer logFilesLock.Unlock()
sink := logSinks[path]
if sink == nil {
sink = path.Open()
logSinks[path] = sink
}
sink.refCount++
return sink
}
type Level int
const (
TRACE Level = iota
DEBUG
INFO
WARN
ERROR
)
var levelToString map[Level]string = map[Level]string{
TRACE: "TRACE",
DEBUG: "DEBUG",
INFO: "INFO",
WARN: "WARN",
ERROR: "ERROR",
}
func (level Level) String() string {
return levelToString[level]
}
func (level Level) LogString() string {
return level.String()[0:1]
}
func LevelFromString(str string) (Level, error) {
for k, v := range levelToString {
if strings.ToLower(v) == strings.ToLower(str) {
return k, nil
}
}
var levelNames sort.StringSlice
levelNames = make([]string, len(levelToString))
var i int
for _, v := range levelToString {
levelNames[i] = v
i++
}
sort.Sort(levelNames)
return TRACE, errors.New(fmt.Sprintf("No such level as '%s'. Valid "+
"levels are '%v'\n", str, levelNames))
}
type Logger struct {
sink *logSink
Level Level
}
func NewLogger(faculty string, cnf *conf.Config) *Logger {
path, level := parseConf(faculty, cnf)
sink := getOrCreateLogSink(path)
return &Logger{sink: sink, Level: level}
}
func parseConf(faculty string, cnf *conf.Config) (string, Level) {
facultyLogPathKey := faculty + "." + conf.HTRACE_LOG_PATH
var facultyLogPath string
if cnf.Contains(facultyLogPathKey) {
facultyLogPath = cnf.Get(facultyLogPathKey)
} else {
facultyLogPath = cnf.Get(conf.HTRACE_LOG_PATH)
}
facultyLogLevelKey := faculty + "." + conf.HTRACE_LOG_LEVEL
var facultyLogLevelStr string
if cnf.Contains(facultyLogLevelKey) {
facultyLogLevelStr = cnf.Get(facultyLogLevelKey)
} else {
facultyLogLevelStr = cnf.Get(conf.HTRACE_LOG_LEVEL)
}
level, err := LevelFromString(facultyLogLevelStr)
if err != nil {
fmt.Fprintf(os.Stderr, "Error configuring log level: %s. Using TRACE.\n")
level = TRACE
}
return facultyLogPath, level
}
func (lg *Logger) Trace(str string) {
lg.Write(TRACE, str)
}
func (lg *Logger) Tracef(format string, v ...interface{}) {
lg.Write(TRACE, fmt.Sprintf(format, v...))
}
func (lg *Logger) Debug(str string) {
lg.Write(DEBUG, str)
}
func (lg *Logger) Debugf(format string, v ...interface{}) {
lg.Write(DEBUG, fmt.Sprintf(format, v...))
}
func (lg *Logger) Info(str string) {
lg.Write(INFO, str)
}
func (lg *Logger) Infof(format string, v ...interface{}) {
lg.Write(INFO, fmt.Sprintf(format, v...))
}
func (lg *Logger) Warn(str string) error {
lg.Write(WARN, str)
return errors.New(str)
}
func (lg *Logger) Warnf(format string, v ...interface{}) error {
str := fmt.Sprintf(format, v...)
lg.Write(WARN, str)
return errors.New(str)
}
func (lg *Logger) Error(str string) error {
lg.Write(ERROR, str)
return errors.New(str)
}
func (lg *Logger) Errorf(format string, v ...interface{}) error {
str := fmt.Sprintf(format, v...)
lg.Write(ERROR, str)
return errors.New(str)
}
func (lg *Logger) Write(level Level, str string) {
if level >= lg.Level {
lg.sink.write(time.Now().UTC().Format(time.RFC3339) + " " +
level.LogString() + ": " + str)
}
}
//
// A few functions which can be used to determine if a certain level of tracing
// is enabled. These are useful in situations when evaluating the parameters
// of a logging function is expensive. (Note, however, that we don't pay the
// cost of string concatenation and manipulation when a log message doesn't
// trigger.)
//
func (lg *Logger) TraceEnabled() bool {
return lg.Level <= TRACE
}
func (lg *Logger) DebugEnabled() bool {
return lg.Level <= DEBUG
}
func (lg *Logger) InfoEnabled() bool {
return lg.Level <= INFO
}
func (lg *Logger) WarnEnabled() bool {
return lg.Level <= WARN
}
func (lg *Logger) ErrorEnabled() bool {
return lg.Level <= ERROR
}
func (lg *Logger) LevelEnabled(level Level) bool {
return lg.Level <= level
}
func (lg *Logger) Close() {
lg.sink.Unref()
lg.sink = nil
}
// Wraps an htrace logger in a golang standard logger.
//
// This is a bit messy because of the difference in interfaces between the
// golang standard logger and the htrace logger. The golang standard logger
// doesn't support log levels directly, so you must choose up front what htrace
// log level all messages should be treated as. Golang standard loggers expect
// to be able to write to an io.Writer, but make no guarantees about whether
// they will break messages into multiple Write() calls (although this does
// not seem to be a major problem in practice.)
//
// Despite these limitations, it's still useful to have this method to be able
// to log things that come out of the go HTTP server and other standard library
// systems.
type WrappedLogger struct {
lg *Logger
level Level
}
func (lg *Logger) Wrap(prefix string, level Level) *log.Logger {
wlg := &WrappedLogger{
lg: lg,
level: level,
}
return log.New(wlg, prefix, 0)
}
func (wlg *WrappedLogger) Write(p []byte) (int, error) {
wlg.lg.Write(wlg.level, string(p))
return len(p), nil
}