| /* |
| * 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" |
| "org/apache/htrace/conf" |
| "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().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) Close() { |
| lg.sink.Unref() |
| lg.sink = nil |
| } |