diff options
author | David Fisher <ddf1991@gmail.com> | 2013-09-24 13:41:27 -0700 |
---|---|---|
committer | David Fisher <ddf1991@gmail.com> | 2013-09-24 13:45:20 -0700 |
commit | 192de075a64a9f33d80f9d3969bf069e77f1a255 (patch) | |
tree | 25b4dad914771b3756d4860b8895dce0942cc142 | |
parent | 4d19cb1636614de01cffc64599f034092e1ba9f1 (diff) |
First version/rewrite
-rw-r--r-- | README.md | 116 | ||||
-rw-r--r-- | logger/commands.go | 156 | ||||
-rw-r--r-- | logger/fields.go | 69 | ||||
-rw-r--r-- | logger/logger.go | 78 | ||||
-rw-r--r-- | logger/priority.go | 38 | ||||
-rw-r--r-- | logger/sinks.go | 127 |
6 files changed, 583 insertions, 1 deletions
@@ -1,4 +1,118 @@ go-logging ========== -simple logging library for go which supports logging to systemd +go-logging is a simple logging library for Go which supports logging to +systemd. It is a mostly-from-scratch rewrite of +[ccding/go-logging](https://github.com/ccding/go-logging) with some features +removed. + +### Examples +#### Default +This example uses the default logger to log to standard out and (if available) to systemd: +```go +package main +import ( + "github.com/sakana/go-logging/logger" +) + +func main() { + logger.Info("Hello World.") + logger.Error("There's nothing more to this program.") +} +``` + +#### Using Sinks and Formats +```go +package main + +import ( + "github.com/sakana/go-logging/logger" + "os" +) + +func main() { + l := logger.NewSimple( + logger.WriterSink( os.Stderr, + "%s: %s[%d] %s\n", + []string{"priority", "executable", "pid", "message"})) + l.Info("Here's a differently formatted log message.") +} +``` + +#### Custom Sink +This example only logs messages with priority `PriErr` and greater. +```go +package main + +import ( + "github.com/sakana/go-logging/logger" + "os" +) + +func main() { + l := logger.NewSimple( + &PriorityFilter{ + logger.PriErr, + logger.WriterSink(os.Stdout, logger.BasicFormat, logger.BasicFields), + }) + l.Info("This will be filtered out") + l.Info("and not printed at all.") + l.Error("This will be printed, though!") + l.Critical("And so will this!") +} + +type PriorityFilter struct { + priority logger.Priority + target logger.Sink +} + +func (filter *PriorityFilter) Log(fields logger.Fields) { + // lower priority values indicate more important messages + if fields["priority"].(logger.Priority) <= filter.priority { + filter.target.Log(fields) + } +} +``` + +### Fields +The following fields are available for use in all sinks: +```go +"prefix" string // static field available to all sinks +"seq" uint64 // auto-incrementing sequence number +"start_time" time.Time // start time of the logger +"time" string // formatted time of log entry +"full_time" time.Time // time of log entry +"rtime" time.Duration // relative time of log entry since started +"pid" int // process id +"executable" string // executable filename +``` +In addition, if `verbose=true` is passed to `New()`, the the following (somewhat expensive) runtime fields are also available: +```go +"funcname" string // function name where the log function was called +"lineno" int // line number where the log function was called +"pathname" string // full pathname of caller +"filename" string // filename of caller +``` + +### Logging functions +All these functions can also be called directly to use the default logger. +```go +func (*Logger) Log(priority Priority, message string) +func (*Logger) Logf(priority Priority, format string, v ...interface{}) +func (*Logger) Emergency(message string) +func (*Logger) Emergencyf(format string, v...interface{}) +func (*Logger) Alert(message string) +func (*Logger) Alertf(format string, v...interface{}) +func (*Logger) Critical(message string) +func (*Logger) Criticalf(format string, v...interface{}) +func (*Logger) Error(message string) +func (*Logger) Errorf(format string, v...interface{}) +func (*Logger) Warning(message string) +func (*Logger) Warningf(format string, v...interface{}) +func (*Logger) Notice(message string) +func (*Logger) Noticef(format string, v...interface{}) +func (*Logger) Info(message string) +func (*Logger) Infof(format string, v...interface{}) +func (*Logger) Debug(message string) +func (*Logger) Debugf(format string, v...interface{}) +``` diff --git a/logger/commands.go b/logger/commands.go new file mode 100644 index 0000000..188a5a4 --- /dev/null +++ b/logger/commands.go @@ -0,0 +1,156 @@ +// Copyright 2013, David Fisher. 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. +// +// author: David Fisher <ddf1991@gmail.com> +// based on previous package by: Cong Ding <dinggnu@gmail.com> +package logger + +import ( + "fmt" +) + +var BasicFormat = "%s [%9s] %s- %s\n" +var BasicFields = []string{"time", "priority", "prefix", "message"} +var RichFormat = "%s [%9s] %d %s - %s:%s:%d - %s\n" +var RichFields = []string{"full_time", "priority", "seq", "prefix", "filename", "funcname", "lineno", "message"} + +// This function has an unusual name to aid in finding it while walking the +// stack. We need to do some dead reckoning from this function to access the +// caller's stack, so there is a consistent call depth above this function. +func (logger *Logger) Log(priority Priority, message string) { + fields := logger.fieldValues() + fields["priority"] = priority + fields["message"] = message + for _, sink := range logger.sinks { + sink.Log(fields) + } +} + +func (logger *Logger) Logf(priority Priority, format string, v ...interface{}) { + logger.Log(priority, fmt.Sprintf(format, v...)) +} + + +func (logger *Logger) Emergency(message string) { + logger.Log(PriEmerg, message) +} +func (logger *Logger) Emergencyf(format string, v...interface{}) { + logger.Log(PriEmerg, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Alert(message string) { + logger.Log(PriAlert, message) +} +func (logger *Logger) Alertf(format string, v...interface{}) { + logger.Log(PriAlert, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Critical(message string) { + logger.Log(PriCrit, message) +} +func (logger *Logger) Criticalf(format string, v...interface{}) { + logger.Log(PriCrit, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Error(message string) { + logger.Log(PriErr, message) +} +func (logger *Logger) Errorf(format string, v...interface{}) { + logger.Log(PriErr, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Warning(message string) { + logger.Log(PriWarning, message) +} +func (logger *Logger) Warningf(format string, v...interface{}) { + logger.Log(PriWarning, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Notice(message string) { + logger.Log(PriNotice, message) +} +func (logger *Logger) Noticef(format string, v...interface{}) { + logger.Log(PriNotice, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Info(message string) { + logger.Log(PriInfo, message) +} +func (logger *Logger) Infof(format string, v...interface{}) { + logger.Log(PriInfo, fmt.Sprintf(format, v...)) +} + +func (logger *Logger) Debug(message string) { + logger.Log(PriDebug, message) +} +func (logger *Logger) Debugf(format string, v...interface{}) { + logger.Log(PriDebug, fmt.Sprintf(format, v...)) +} + + +func Emergency(message string) { + defaultLogger.Log(PriEmerg, message) +} +func Emergencyf(format string, v...interface{}) { + defaultLogger.Log(PriEmerg, fmt.Sprintf(format, v...)) +} + +func Alert(message string) { + defaultLogger.Log(PriAlert, message) +} +func Alertf(format string, v...interface{}) { + defaultLogger.Log(PriAlert, fmt.Sprintf(format, v...)) +} + +func Critical(message string) { + defaultLogger.Log(PriCrit, message) +} +func Criticalf(format string, v...interface{}) { + defaultLogger.Log(PriCrit, fmt.Sprintf(format, v...)) +} + +func Error(message string) { + defaultLogger.Log(PriErr, message) +} +func Errorf(format string, v...interface{}) { + defaultLogger.Log(PriErr, fmt.Sprintf(format, v...)) +} + +func Warning(message string) { + defaultLogger.Log(PriWarning, message) +} +func Warningf(format string, v...interface{}) { + defaultLogger.Log(PriWarning, fmt.Sprintf(format, v...)) +} + +func Notice(message string) { + defaultLogger.Log(PriNotice, message) +} +func Noticef(format string, v...interface{}) { + defaultLogger.Log(PriNotice, fmt.Sprintf(format, v...)) +} + +func Info(message string) { + defaultLogger.Log(PriInfo, message) +} +func Infof(format string, v...interface{}) { + defaultLogger.Log(PriInfo, fmt.Sprintf(format, v...)) +} + +func Debug(message string) { + defaultLogger.Log(PriDebug, message) +} +func Debugf(format string, v...interface{}) { + defaultLogger.Log(PriDebug, fmt.Sprintf(format, v...)) +} diff --git a/logger/fields.go b/logger/fields.go new file mode 100644 index 0000000..e06dfcc --- /dev/null +++ b/logger/fields.go @@ -0,0 +1,69 @@ +// Copyright 2013, David Fisher. 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. +// +// author: David Fisher <ddf1991@gmail.com> +// based on previous package by: Cong Ding <dinggnu@gmail.com> +package logger + +import ( + "os" + "path" + "runtime" + "strings" + "sync/atomic" + "time" +) + +type Fields map[string]interface{} + +func (logger *Logger) fieldValues() Fields { + now := time.Now() + fields := Fields{ + "prefix": logger.prefix, // static field available to all sinks + "seq": logger.nextSeq(), // auto-incrementing sequence number + "start_time": logger.created, // start time of the logger + "time": now.Format(time.StampMilli), // formatted time of log entry + "full_time": now, // time of log entry + "rtime": time.Since(logger.created), // relative time of log entry since started + "pid": os.Getpid(), // process id + "executable": logger.executable, // executable filename + } + + if logger.verbose { + setVerboseFields(fields) + } + return fields +} + +func (logger *Logger) nextSeq() uint64 { + return atomic.AddUint64(&logger.seq, 1) +} + +func setVerboseFields(fields Fields) { + callers := make([]uintptr, 10) + n := runtime.Callers(3, callers) // starts in (*Logger).Log or similar + callers = callers[:n] + + for _, pc := range callers { + f := runtime.FuncForPC(pc) + if !strings.Contains(f.Name(), "logger.(*Logger)") { + fields["funcname"] = f.Name() + pathname, lineno := f.FileLine(pc) + fields["lineno"] = lineno + fields["pathname"] = pathname + fields["filename"] = path.Base(pathname) + return + } + } +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..0767034 --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,78 @@ +// Copyright 2013, David Fisher. 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. +// +// author: David Fisher <ddf1991@gmail.com> +// based on previous package by: Cong Ding <dinggnu@gmail.com> +package logger + +import ( + "bitbucket.org/kardianos/osext" + "github.com/coreos/go-systemd/journal" + "path" + "time" + "os" +) + +// Logger is user-immutable immutable struct which can log to several outputs +type Logger struct { + sinks []Sink // the sinks this logger will log to + verbose bool // gather expensive logging data? + prefix string // static field available to all log sinks under this logger + + created time.Time // time when this logger was created + seq uint64 // sequential number of log message, starting at 1 + executable string // executable name +} + +// New creates a new Logger which logs to all the supplied sinks. The prefix +// argument is passed to all loggers under the field "prefix" with every log +// message. If verbose is true, more expensive runtime fields will be computed +// and passed to loggers. These fields are funcname, lineno, pathname, and +// filename. +func New(prefix string, verbose bool, sinks ...Sink) *Logger { + return &Logger{ + sinks: sinks, + verbose: verbose, + prefix: prefix, + + created: time.Now(), + seq: 0, + executable: getExecutableName(), + } +} + +func getExecutableName() string { + executablePath, err := osext.Executable() + if err != nil { + return "(UNKNOWN)" + } else { + return path.Base(executablePath) + } +} + +// NewSimple(sinks...) is equivalent to New("", false, sinks...) +func NewSimple(sinks ...Sink) *Logger { + return New("", false, sinks...) +} + +var defaultLogger *Logger + +func init() { + sinks := make([]Sink, 0) + sinks = append(sinks, WriterSink(os.Stdout, BasicFormat, BasicFields)) + if journal.Enabled() { + sinks = append(sinks, JournalSink()) + } + defaultLogger = NewSimple(sinks...) +} diff --git a/logger/priority.go b/logger/priority.go new file mode 100644 index 0000000..429041b --- /dev/null +++ b/logger/priority.go @@ -0,0 +1,38 @@ +package logger + +type Priority int + +const ( + PriEmerg Priority = iota + PriAlert + PriCrit + PriErr + PriWarning + PriNotice + PriInfo + PriDebug +) + +func (priority Priority) String() string { + switch priority { + case PriEmerg: + return "EMERGENCY" + case PriAlert: + return "ALERT" + case PriCrit: + return "CRITICAL" + case PriErr: + return "ERROR" + case PriWarning: + return "WARNING" + case PriNotice: + return "NOTICE" + case PriInfo: + return "INFO" + case PriDebug: + return "DEBUG" + + default: + return "UNKNOWN" + } +} diff --git a/logger/sinks.go b/logger/sinks.go new file mode 100644 index 0000000..9c4f35c --- /dev/null +++ b/logger/sinks.go @@ -0,0 +1,127 @@ +// Copyright 2013, David Fisher. 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. +// +// author: David Fisher <ddf1991@gmail.com> +// based on previous package by: Cong Ding <dinggnu@gmail.com> +package logger + +import ( + "fmt" + "github.com/coreos/go-systemd/journal" + "io" + "strings" + "sync" +) + +const AsyncBuffer = 100 + +type Sink interface { + Log(Fields) +} + +type writerSink struct { + lock sync.Mutex + out io.Writer + format string + fields []string +} + +func (sink *writerSink) Log(fields Fields) { + vals := make([]interface{}, len(sink.fields)) + for i, field := range sink.fields { + var ok bool + vals[i], ok = fields[field] + if !ok { + vals[i] = "???" + } + } + + sink.lock.Lock() + defer sink.lock.Unlock() + fmt.Fprintf(sink.out, sink.format, vals...) +} + +func WriterSink(out io.Writer, format string, fields []string) Sink { + return &writerSink{ + out: out, + format: format, + fields: fields, + } +} + +type journalSink struct{} + +func (sink *journalSink) Log(fields Fields) { + message := fields["message"].(string) + priority := toJournalPriority(fields["priority"].(Priority)) + journalFields := make(map[string]string) + for k, v := range fields { + if k == "message" || k == "priority" { + continue + } + journalFields[strings.ToUpper(k)] = fmt.Sprint(v) + } + journal.Send(message, priority, journalFields) +} + +func toJournalPriority(priority Priority) journal.Priority { + switch priority { + case PriEmerg: + return journal.PriEmerg + case PriAlert: + return journal.PriAlert + case PriCrit: + return journal.PriCrit + case PriErr: + return journal.PriErr + case PriWarning: + return journal.PriWarning + case PriNotice: + return journal.PriNotice + case PriInfo: + return journal.PriInfo + case PriDebug: + return journal.PriDebug + + default: + return journal.PriErr + } +} + +func JournalSink() Sink { + return &journalSink{} +} + +type tryJournalSink struct { + j journalSink + w writerSink +} + +func (sink *tryJournalSink) Log(fields Fields) { + if journal.Enabled() { + sink.j.Log(fields) + } else { + sink.w.Log(fields) + } +} + +func JournalFallbackSink(out io.Writer, format string, fields []string) Sink { + return &tryJournalSink{ + w: writerSink{ + out: out, + format: format, + fields: fields, + }, + } +} |