diff --git a/log/formatter.go b/log/formatter.go new file mode 100644 index 000000000..7cb1ee5f2 --- /dev/null +++ b/log/formatter.go @@ -0,0 +1,21 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed 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 log + +// Formatter formats records in different ways: text, json, etc. +type Formatter interface { + Format(*Record) ([]byte, error) +} diff --git a/log/level.go b/log/level.go new file mode 100644 index 000000000..84bfc394f --- /dev/null +++ b/log/level.go @@ -0,0 +1,75 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed 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 log + +import ( + "fmt" +) + +// Level ... +type Level int + +const ( + // DebugLevel debug + DebugLevel Level = iota + // InfoLevel info + InfoLevel + // WarningLevel warning + WarningLevel + // ErrorLevel error + ErrorLevel + // FatalLevel fatal + FatalLevel +) + +func (l Level) string() (lvl string) { + switch l { + case DebugLevel: + lvl = "DEBUG" + case InfoLevel: + lvl = "INFO" + case WarningLevel: + lvl = "WARNING" + case ErrorLevel: + lvl = "ERROR" + case FatalLevel: + lvl = "FATAL" + default: + lvl = "UNKNOWN" + } + + return +} + +func parseLevel(lvl string) (level Level, err error) { + + switch lvl { + case "debug": + level = DebugLevel + case "info": + level = InfoLevel + case "warning": + level = WarningLevel + case "error": + level = ErrorLevel + case "fatal": + level = FatalLevel + default: + err = fmt.Errorf("invalid log level: %s", lvl) + } + + return +} diff --git a/log/logger.go b/log/logger.go new file mode 100644 index 000000000..816594245 --- /dev/null +++ b/log/logger.go @@ -0,0 +1,263 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed 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 log + +import ( + "fmt" + "io" + "os" + "runtime" + "sync" + "time" +) + +var logger = New(os.Stdout, NewTextFormatter(), WarningLevel) + +func init() { + // TODO add item in configuaration file + lvl := os.Getenv("LOG_LEVEL") + if len(lvl) == 0 { + logger.SetLevel(InfoLevel) + return + } + + level, err := parseLevel(lvl) + if err != nil { + logger.SetLevel(InfoLevel) + return + } + + logger.SetLevel(level) + +} + +// Logger provides a struct with fields that describe the details of logger. +type Logger struct { + out io.Writer + fmtter Formatter + lvl Level + mu sync.Mutex +} + +// New returns a customized Logger +func New(out io.Writer, fmtter Formatter, lvl Level) *Logger { + return &Logger{ + out: out, + fmtter: fmtter, + lvl: lvl, + } +} + +//SetOutput sets the output of Logger l +func (l *Logger) SetOutput(out io.Writer) { + l.mu.Lock() + defer l.mu.Unlock() + + l.out = out +} + +//SetFormatter sets the formatter of Logger l +func (l *Logger) SetFormatter(fmtter Formatter) { + l.mu.Lock() + defer l.mu.Unlock() + + l.fmtter = fmtter +} + +//SetLevel sets the level of Logger l +func (l *Logger) SetLevel(lvl Level) { + l.mu.Lock() + defer l.mu.Unlock() + + l.lvl = lvl +} + +//SetOutput sets the output of default Logger +func SetOutput(out io.Writer) { + logger.SetOutput(out) +} + +//SetFormatter sets the formatter of default Logger +func SetFormatter(fmtter Formatter) { + logger.SetFormatter(fmtter) +} + +//SetLevel sets the level of default Logger +func SetLevel(lvl Level) { + logger.SetLevel(lvl) +} + +func (l *Logger) output(record *Record) (err error) { + b, err := l.fmtter.Format(record) + if err != nil { + return + } + + l.mu.Lock() + defer l.mu.Unlock() + + _, err = l.out.Write(b) + + return +} + +// Debug ... +func (l *Logger) Debug(v ...interface{}) { + if l.lvl <= DebugLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprint(v...), line, DebugLevel) + l.output(record) + } +} + +// Debugf ... +func (l *Logger) Debugf(format string, v ...interface{}) { + if l.lvl <= DebugLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, DebugLevel) + l.output(record) + } +} + +// Info ... +func (l *Logger) Info(v ...interface{}) { + if l.lvl <= InfoLevel { + record := NewRecord(time.Now(), fmt.Sprint(v...), "", InfoLevel) + l.output(record) + } +} + +// Infof ... +func (l *Logger) Infof(format string, v ...interface{}) { + if l.lvl <= InfoLevel { + record := NewRecord(time.Now(), fmt.Sprintf(format, v...), "", InfoLevel) + l.output(record) + } +} + +// Warning ... +func (l *Logger) Warning(v ...interface{}) { + if l.lvl <= WarningLevel { + record := NewRecord(time.Now(), fmt.Sprint(v...), "", WarningLevel) + l.output(record) + } +} + +// Warningf ... +func (l *Logger) Warningf(format string, v ...interface{}) { + if l.lvl <= WarningLevel { + record := NewRecord(time.Now(), fmt.Sprintf(format, v...), "", WarningLevel) + l.output(record) + } +} + +// Error ... +func (l *Logger) Error(v ...interface{}) { + if l.lvl <= ErrorLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprint(v...), line, ErrorLevel) + l.output(record) + } +} + +// Errorf ... +func (l *Logger) Errorf(format string, v ...interface{}) { + if l.lvl <= ErrorLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, ErrorLevel) + l.output(record) + } +} + +// Fatal ... +func (l *Logger) Fatal(v ...interface{}) { + if l.lvl <= FatalLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprint(v...), line, FatalLevel) + l.output(record) + } + os.Exit(1) +} + +// Fatalf ... +func (l *Logger) Fatalf(format string, v ...interface{}) { + if l.lvl <= FatalLevel { + line := line(2) + record := NewRecord(time.Now(), fmt.Sprintf(format, v...), line, FatalLevel) + l.output(record) + } + os.Exit(1) +} + +// Debug ... +func Debug(v ...interface{}) { + logger.Debug(v...) +} + +// Debugf ... +func Debugf(format string, v ...interface{}) { + logger.Debugf(format, v...) +} + +// Info ... +func Info(v ...interface{}) { + logger.Info(v...) +} + +// Infof ... +func Infof(format string, v ...interface{}) { + logger.Infof(format, v...) +} + +// Warning ... +func Warning(v ...interface{}) { + logger.Warning(v...) +} + +// Warningf ... +func Warningf(format string, v ...interface{}) { + logger.Warningf(format, v...) +} + +// Error ... +func Error(v ...interface{}) { + logger.Error(v...) +} + +// Errorf ... +func Errorf(format string, v ...interface{}) { + logger.Errorf(format, v...) +} + +// Fatal ... +func Fatal(v ...interface{}) { + logger.Fatal(v...) +} + +// Fatalf ... +func Fatalf(format string, v ...interface{}) { + logger.Fatalf(format, v...) +} + +func line(calldepth int) string { + _, file, line, ok := runtime.Caller(calldepth) + if !ok { + file = "???" + line = 0 + } + + return fmt.Sprintf("%s:%d", file, line) +} diff --git a/log/record.go b/log/record.go new file mode 100644 index 000000000..aec9f5b97 --- /dev/null +++ b/log/record.go @@ -0,0 +1,38 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed 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 log + +import ( + "time" +) + +// Record holds information about log +type Record struct { + Time time.Time // time when the log produced + Msg string // content of the log + Line string // in which file and line that the log produced + Lvl Level // level of the log +} + +// NewRecord creates a record according to the arguments provided and returns it +func NewRecord(time time.Time, msg, line string, lvl Level) *Record { + return &Record{ + Time: time, + Msg: msg, + Line: line, + Lvl: lvl, + } +} diff --git a/log/textformatter.go b/log/textformatter.go new file mode 100644 index 000000000..1ac177e26 --- /dev/null +++ b/log/textformatter.go @@ -0,0 +1,63 @@ +/* + Copyright (c) 2016 VMware, Inc. All Rights Reserved. + Licensed 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 log + +import ( + "fmt" + "time" +) + +var defaultTimeFormat = time.RFC3339 // 2006-01-02T15:04:05Z07:00 + +// TextFormatter represents a kind of formatter that formats the logs as plain text +type TextFormatter struct { + timeFormat string +} + +// NewTextFormatter returns a TextFormatter, the format of time is time.RFC3339 +func NewTextFormatter() *TextFormatter { + return &TextFormatter{ + timeFormat: defaultTimeFormat, + } +} + +// Format formats the logs as "time [level] line message" +func (t *TextFormatter) Format(r *Record) (b []byte, err error) { + s := fmt.Sprintf("%s [%s] ", r.Time.Format(t.timeFormat), r.Lvl.string()) + + if len(r.Line) != 0 { + s = s + r.Line + " " + } + + if len(r.Msg) != 0 { + s = s + r.Msg + } + + b = []byte(s) + + if len(b) == 0 || b[len(b)-1] != '\n' { + b = append(b, '\n') + } + + return +} + +// SetTimeFormat sets time format of TextFormatter if the parameter fmt is not null +func (t *TextFormatter) SetTimeFormat(fmt string) { + if len(fmt) != 0 { + t.timeFormat = fmt + } +}