Last active
February 11, 2016 01:07
-
-
Save raphael/7d7c05c2ff840918dd5d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package log | |
/**************** Types ****************/ | |
// The logger interface supports Printf and Structured logging | |
type Logger interface { | |
// fmt.PrintXX style | |
Printf(fmt string, vals ...interface{}) | |
// Structured style | |
Fields(data []KV) | |
} | |
// Structured logging element | |
type KV { | |
K string | |
V interface{} | |
} | |
/**************** Default Logger ***************/ | |
// The default logger uses the stdlib logger. | |
type DefaultLogger struct { | |
*log.Logger // stdlib logger | |
} | |
func New(w io.Writer, prefix string, flag int) Logger { | |
return &DefaultLogger{Logger: log.New(w, prefix, flag)} | |
} | |
func (l *DefaultLogger) Fields(data []KV) { | |
fmt, values := formatFromFields(data) | |
l.Printf(fmt, values) | |
} | |
/******************* Package Functions ***************/ | |
// Printf extract the current logger from the context and logs with it | |
func Printf(ctx *goa.Context, fmt string, vals ...string) { | |
if logger := ctx.Get(LoggerKey); logger != nil { | |
if ls, ok := logger.([]Logger); ok { | |
for _, l := range ls { | |
l.Printf(ctx, fmt, vals) | |
} | |
} | |
} | |
} | |
// Ditto for Fields. | |
func Fields(data []KV) { | |
// ... | |
} | |
/*************** Setting the logger / Middleware ******/ | |
// With is a middleware that sets the request logger. | |
func With(logger ...Logger) goa.Middleware { | |
return func(h goa.Handler) goa.Handler { | |
return func(ctx *goa.Context) error { | |
new := ctx.With(LoggerKey, logger) | |
return h(new) | |
} | |
} | |
} | |
/***************** LoggerFunc *****************/ | |
// LoggerFunc is a logger that logs conditionally depending on the context. | |
type LoggerFunc func(ctx *goa.Context) Logger | |
func (l LoggerFunc) Printf(ctx *goa.Context, msg string, vals ...interface{}) { | |
logger := l(ctx) | |
if logger != nil { | |
logger.Printf(ctx, msg, vals) | |
} | |
} | |
func (l LoggerFunc) Fields(ctx *goa.Context, data []KV) { | |
// ... | |
} | |
/************** Contextual *****************/ | |
// ContextualLogger is a logger that logs contextual information in each log | |
// entry. The contextual information includes the app name, controller name, | |
// action name and unique request ID. | |
func NewContextual(logger Logger) Logger { | |
return &contextual{Logger: logger} | |
} | |
func (c *contextual) Printf(ctx *goa.Context, fmt string, v ...interface{}) { | |
var newFmt []string | |
var vals []interface{} | |
if s := ctx.Service(); s != nil { | |
newFmt = []string{"app: %s"} | |
vals = []interface{s.Name} | |
} | |
if c := ctx.Controller(); c != "" { | |
newFmt = append(newFmt, "ctrl: %s") | |
vals = append(vals, c) | |
} | |
if a := ctx.Action(); a != "" { | |
newFmt = append(newFmt, "action: %s") | |
vals = append(vals, a) | |
} | |
if reqID := ctx.RequestID(); reqID != "" { | |
newFmt = append(newFmt, "reqID: %s") | |
vals = append(vals, reqID) | |
} | |
newFmt = append(newFmt, fmt) | |
vals = append(vals, v...) | |
c.Logger.Printf(strings.Join(newFmt, " - "), vals) | |
} | |
func (c *contextual) Fields(data []KV) { | |
// ... | |
} | |
/********************** Syslog *******************/ | |
// NewSyslogLogger creates a log.Logger whose output is written to the system log | |
// service with the specified priority. The logFlag argument is the flag set | |
// passed through to log.New to create the Logger. | |
func NewSyslogLogger(p syslog.Priority, logFlag int) (*SyslogLogger, error) { | |
l, err := syslog.NewLogger(p, logFlag) | |
if err != nil { | |
return nil, err | |
} | |
return &DefaultLogger{Logger: l}, nil | |
} | |
// NewFilterLogger creates a logger that only logs if the PriorityKey | |
// context value is lower than p. | |
func PriorityFilter(p syslog.Priority, logger Logger) LoggerFunc { | |
return func(ctx *goa.Context) Logger { | |
pv := ctx.Get(PriorityKey) | |
upper, ok := pv.(syslog.Priority) | |
if !ok { | |
return nil | |
} | |
if p <= upper { | |
return logger | |
} | |
return nil | |
} | |
} | |
// SetPriority sets the current logging priority in the context. | |
SetPriority(ctx *goa.Context, p syslog.Priority) { | |
ctx.Context = ctx.Context.With(PriorityKey, p) | |
} | |
/********************* Adapters **********************/ | |
// These must return the exact type to allow for further | |
// customization by the client. | |
func NewLog15(ctx ...interface{}) log15.Logger { | |
// .... | |
} | |
func NewLogrus() *logrus.Logger { | |
// .... | |
} | |
/********************* Usage ***************************/ | |
var DebugLogger log.Logger | |
// in main: | |
func main() { | |
// Create logger(s) | |
lr := log.NewLogrus() | |
// ... Setup logrus logger with logrus package | |
DebugLogger = log.Contextual(lr) | |
defaultLogger := log.NewLogrus() | |
// ... Ditto | |
defaultLogger = log.PriorityFilter(defaultLogger, syslog.INFO) | |
defaultLogger = log.Contextual(defaultLogger) | |
stderrLogger := log.Contextual(log.New(os.Stderr, "", log.LstdFlags)) | |
// Register them | |
service.Use(log.With(defaultLogger, stderrLogger)) | |
// ... | |
} | |
// in controllers: | |
// Either use the package functions which use the logger registered with `log.With` | |
log.Fields(ctx, []KV{{"foo": 1}, {"bar": time.Now()}}) | |
log.Printf(ctx, fmt, vals) | |
// Or use the debug logger | |
// you need both because you don't want to double log when logging debug which would | |
// happen if you had registered DebugLogger in the middleware and were using the | |
// package functions to log. | |
DebugLogger.Printf("foo: %v", 1) | |
/************** Not sure ********************/ | |
// We could let users get the underlying logger from the context | |
// would be inefficient though: | |
Log15(ctx *goa.Context) log15.Logger { | |
if l := ctx.Get(log15Key); l != nil { | |
return l.(log15.Logger) | |
} | |
ls, ok := ctx.Get(LoggerKey) | |
if !ok { | |
return nil | |
} | |
for _, l := range ls { | |
if res := rlog15(l); res != nil { | |
ctx.Set(log15Key, res) | |
} | |
return res | |
} | |
return nil | |
} | |
// unpeel the onion | |
func rlog15(ctx *goa.Context, l interface{}) log15.Logger | |
switch actual := l.(type) { | |
case log15.Logger: | |
return actual | |
case contextual: | |
return rlog15(actual.Logger) | |
case LoggerFunc: | |
return rlog15(actual.Logger) | |
default: | |
return nil | |
} | |
} | |
// The user can do | |
log.Log15(ctx).Debug(...) | |
// The alternative is to have the user keep the logger around which seems better |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment