Skip to content

Instantly share code, notes, and snippets.

@raphael
Last active February 11, 2016 01:07
Show Gist options
  • Save raphael/7d7c05c2ff840918dd5d to your computer and use it in GitHub Desktop.
Save raphael/7d7c05c2ff840918dd5d to your computer and use it in GitHub Desktop.
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