ccpaging/nxlog4go

View on GitHub
nxlog4go.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright (C) 2017, ccpaging <ccpaging@gmail.com>.  All rights reserved.
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>.  All rights reserved.
//
// Package nxlog4go provides simple, fast, low cost, and extensible logging.
// It can be used in test, development, and production environment.
//
// Logger
//
// - prefix, to write at beginning of each line
// - Enable / disable caller func since it's expensive.
// - The default console output compatibility with go log
//     level, the log level
//      out, io.Writer / io.MultiWriter
//   layout, specifies how the data will be written
// - filters, point to filters map
//
// Filters
//
// - The filters map indexed with tag name
//
// Filter
//
// - level, the log level
//      FINEST
//     FINE
//     DEBUG
//     TRACE
//     INFO
//     WARN
//     ERROR
//     CRITICAL
// - appender
//
// Appender
//
// - An interface for anything
// - Write function should be able to write logs
// - Close function clean up anything lingering about the Appender
// - SetOption function. Configurable
// - Extensible. Anyone can port own appender as part of nxlog4go.
//
// Layout
//
// - With time stamp cached
// - fast byte convert
// - The default PatternLayout is easy to use.
//
// Enhanced Logging
//
// This is inspired by the logging functionality in log4go. Essentially, you create a Logger
// object with a console writer or create output filters for it. You can send whatever you
// want to the Logger, and it will filter and formatter that based on your settings and
// send it to the outputs. This way, you can put as much debug code in your program as
// you want, and when you're done you can filter out the mundane messages so only
// the important ones show up as the format you want.
//
// Utility functions are provided to make life easier.
//
// You may using your configuration file format as same as your project's.
//
// You may extend your own appender for your needs.
//
// Here is some example code to get started:
//
// log := nxlog4go.New(nxlog4go.DEBUG)
// l.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
//
// Usage notes:
// - The utility functions (Info, Debug, Warn, etc) derive their source from the
//   calling function, and this incurs extra overhead. It can be disabled.
// - Adding new Entry field "prefix" to identify different module/package
//   in large project
//
// Changes from log4go
//
// The most of interfaces and the internals have been changed have been changed, then you will
// have to update your code. Sorry! I hope it is worth.

package nxlog4go

import (
    "sync"

    "github.com/ccpaging/nxlog4go/cast"
    "github.com/ccpaging/nxlog4go/driver"
)

// Version information
const (
    Version = "nxlog4go-v2.0.3"
    Major   = 2
    Minor   = 0
    Build   = 3
)

/****** Logger ******/

// A Logger represents an active logging object that generates lines of
// output to an io.Writer, and a collection of Filters through which
// log messages are written. Each logging operation makes a single call to
// the Writer's Write method. A Logger can be used simultaneously from
// multiple goroutines; it guarantees to serialize access to the Writer.
type Logger struct {
    mu      *sync.Mutex // ensures atomic writes; protects the following fields
    prefix  string      // prefix to write at beginning of each line
    caller  bool        // enable or disable calling runtime.Caller(...)
    stdf    *stdFilter
    filters map[string]*driver.Filter // a collection of Filter
}

const (
    stdfName string = "stdout"
)

// NewLogger creates a new logger with a "stderr" writer to send
// formatted log messages at or above level to standard output.
func NewLogger(level int) *Logger {
    return &Logger{
        mu:      new(sync.Mutex),
        caller:  true,
        stdf:    newStdFilter(level),
        filters: make(map[string]*driver.Filter),
    }
}

// Clone creates a new clone logger.
//  New logger can be used in different module.
//    Using owner prefix and runtime caller switch.
//  Running in the parallel go routines and packages is safe.
func (l *Logger) Clone() *Logger {
    return &Logger{
        mu:      l.mu,
        prefix:  l.prefix,
        caller:  l.caller,
        stdf:    l.stdf,
        filters: l.filters,
    }
}

// Copy copies the all filters of a logger.
func (l *Logger) Copy(src *Logger) {
    l.mu = src.mu
    l.stdf = src.stdf
    l.filters = src.filters
}

// SetOptions sets name-value pair options.
//
// Return *Logger.
func (l *Logger) SetOptions(args ...interface{}) *Logger {
    ops, idx, _ := driver.ArgsToMap(args...)
    for _, k := range idx {
        l.Set(k, ops[k])
    }
    return l
}

// Set sets name-value option with:
//    prefix - The output prefix
//    caller - Enable or disable the runtime caller function
//    fields - Select Entry with field pairs or values
//    level  - The output level
//
// layout options...
//
// Return errors.
func (l *Logger) Set(k string, v interface{}) (err error) {
    l.mu.Lock()
    defer l.mu.Unlock()

    var (
        s string
        e bool
        n int
    )

    switch k {
    case "prefix":
        if s, err = cast.ToString(v); err == nil {
            l.prefix = s
        }
    case "caller":
        if e, err = cast.ToBool(v); err == nil {
            l.caller = e
        }
    case "level":
        if n, err = Level(INFO).IntE(v); err == nil {
            l.stdf.level = n
        }
    default:
        return l.stdf.lo.Set(k, v)
    }

    return
}

// Layout returns the output layout for the logger.
func (l *Logger) Layout() driver.Layout {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.stdf.lo
}

// SetLayout sets the output layout for the logger.
func (l *Logger) SetLayout(layout driver.Layout) *Logger {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.stdf.lo = layout
    return l
}

// SetFilters sets the output filters for the logger.
func (l *Logger) SetFilters(filters map[string]*driver.Filter) *Logger {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.filters = filters
    return l
}

// Filters returns the output filters for the logger.
func (l *Logger) Filters() map[string]*driver.Filter {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.filters
}

// With creates a child logger and adds structured context to it. Args added
// to the child don't affect the parent, and vice versa.
func (l *Logger) With(args ...interface{}) *Entry {
    // do not using with(args) which may create a slicer
    return NewEntry(l).With(args...)
}

// Enable sets the standard filter's Enabler to deny all,
// or restores the default at/above level enabler.
func (l *Logger) Enable(enable bool) *Logger {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.stdf.enb = enable
    return l
}

func (l *Logger) enabled(level int) bool {
    if l.stdf.enabled(level) {
        return true
    }

    if len(l.filters) > 0 {
        return true
    }

    return false
}

// Dispatch encodes a log recorder to bytes and writes it.
func (l *Logger) Dispatch(r *driver.Recorder) {
    l.mu.Lock()
    defer l.mu.Unlock()

    if l.stdf.enabled(r.Level) {
        l.stdf.dispatch(r)
    }

    for _, f := range l.filters {
        if f != nil {
            f.Dispatch(r)
        }
    }
}

// Close closes all log filters in preparation for exiting the program.
// Calling this is not really imperative, unless you want to
// guarantee that all log messages are written.
//
// Notice: Close() removes all filters (and thus all appenders) except "stdout"
// from the logger.
func (l *Logger) Close() {
    l.mu.Lock()
    defer l.mu.Unlock()

    for name, f := range l.filters {
        if name == stdfName {
            continue
        }
        if f != nil {
            f.Close()
        }
        delete(l.filters, name)
    }
}