summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Fisher <ddf1991@gmail.com>2013-09-24 13:41:27 -0700
committerDavid Fisher <ddf1991@gmail.com>2013-09-24 13:45:20 -0700
commit192de075a64a9f33d80f9d3969bf069e77f1a255 (patch)
tree25b4dad914771b3756d4860b8895dce0942cc142
parent4d19cb1636614de01cffc64599f034092e1ba9f1 (diff)
First version/rewrite
-rw-r--r--README.md116
-rw-r--r--logger/commands.go156
-rw-r--r--logger/fields.go69
-rw-r--r--logger/logger.go78
-rw-r--r--logger/priority.go38
-rw-r--r--logger/sinks.go127
6 files changed, 583 insertions, 1 deletions
diff --git a/README.md b/README.md
index 6365fc2..54c7b52 100644
--- a/README.md
+++ b/README.md
@@ -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,
+ },
+ }
+}