bump(github.com/ccding/go-logging): d4e747a24b2af160872a886a430a16ac65f76456

This commit is contained in:
Brandon Philips
2013-08-06 10:50:08 -07:00
parent aac485d67b
commit 7992171974
15 changed files with 1521 additions and 0 deletions

View File

@ -0,0 +1,98 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
// Logln receives log request from the client. The request includes a set of
// variables.
func (logger *Logger) Log(level Level, v ...interface{}) {
// Don't delete this calling. The calling is used to keep the same
// calldepth for all the logging functions. The calldepth is used to
// get runtime information such as line number, function name, etc.
logger.log(level, v...)
}
// Logf receives log request from the client. The request has a string
// parameter to describe the format of output.
func (logger *Logger) Logf(level Level, format string, v ...interface{}) {
logger.logf(level, format, v...)
}
// Other quick commands for different level
func (logger *Logger) Critical(v ...interface{}) {
logger.log(CRITICAL, v...)
}
func (logger *Logger) Fatal(v ...interface{}) {
logger.log(CRITICAL, v...)
}
func (logger *Logger) Error(v ...interface{}) {
logger.log(ERROR, v...)
}
func (logger *Logger) Warn(v ...interface{}) {
logger.log(WARNING, v...)
}
func (logger *Logger) Warning(v ...interface{}) {
logger.log(WARNING, v...)
}
func (logger *Logger) Info(v ...interface{}) {
logger.log(INFO, v...)
}
func (logger *Logger) Debug(v ...interface{}) {
logger.log(DEBUG, v...)
}
func (logger *Logger) Notset(v ...interface{}) {
logger.log(NOTSET, v...)
}
func (logger *Logger) Criticalf(format string, v ...interface{}) {
logger.logf(CRITICAL, format, v...)
}
func (logger *Logger) Fatalf(format string, v ...interface{}) {
logger.logf(CRITICAL, format, v...)
}
func (logger *Logger) Errorf(format string, v ...interface{}) {
logger.logf(ERROR, format, v...)
}
func (logger *Logger) Warnf(format string, v ...interface{}) {
logger.logf(WARNING, format, v...)
}
func (logger *Logger) Warningf(format string, v ...interface{}) {
logger.logf(WARNING, format, v...)
}
func (logger *Logger) Infof(format string, v ...interface{}) {
logger.logf(INFO, format, v...)
}
func (logger *Logger) Debugf(format string, v ...interface{}) {
logger.logf(DEBUG, format, v...)
}
func (logger *Logger) Notsetf(format string, v ...interface{}) {
logger.logf(NOTSET, format, v...)
}

View File

@ -0,0 +1,236 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
import (
"bitbucket.org/kardianos/osext"
"os"
"path"
"runtime"
"sync/atomic"
"time"
)
// The struct for each log record
type record struct {
level Level
seqid uint64
pathname string
filename string
module string
lineno int
funcname string
thread int
process int
message string
time time.Time
}
// This variable maps fields in recordArgs to relavent function signatures
var fields = map[string]func(*Logger, *record) interface{}{
"name": (*Logger).lname, // name of the logger
"seqid": (*Logger).nextSeqid, // sequence number
"levelno": (*Logger).levelno, // level number
"levelname": (*Logger).levelname, // level name
"created": (*Logger).created, // starting time of the logger
"nsecs": (*Logger).nsecs, // nanosecond of the starting time
"time": (*Logger).time, // record created time
"timestamp": (*Logger).timestamp, // timestamp of record
"rtime": (*Logger).rtime, // relative time since started
"filename": (*Logger).filename, // source filename of the caller
"pathname": (*Logger).pathname, // filename with path
"module": (*Logger).module, // executable filename
"lineno": (*Logger).lineno, // line number in source code
"funcname": (*Logger).funcname, // function name of the caller
"thread": (*Logger).thread, // thread id
"process": (*Logger).process, // process id
"message": (*Logger).message, // logger message
}
var runtimeFields = map[string]bool{
"name": false,
"seqid": false,
"levelno": false,
"levelname": false,
"created": false,
"nsecs": false,
"time": false,
"timestamp": false,
"rtime": false,
"filename": true,
"pathname": true,
"module": false,
"lineno": true,
"funcname": true,
"thread": true,
"process": false,
"message": false,
}
// If it fails to get some fields with string type, these fields are set to
// errString value.
const errString = "???"
// GetGoID returns the id of goroutine, which is defined in ./get_go_id.c
func GetGoID() int32
// genRuntime generates the runtime information, including pathname, function
// name, filename, line number.
func genRuntime(r *record) {
calldepth := 5
pc, file, line, ok := runtime.Caller(calldepth)
if ok {
// Generate short function name
fname := runtime.FuncForPC(pc).Name()
fshort := fname
for i := len(fname) - 1; i > 0; i-- {
if fname[i] == '.' {
fshort = fname[i+1:]
break
}
}
r.pathname = file
r.funcname = fshort
r.filename = path.Base(file)
r.lineno = line
} else {
r.pathname = errString
r.funcname = errString
r.filename = errString
// Here we uses -1 rather than 0, because the default value in
// golang is 0 and we should know the value is uninitialized
// or failed to get
r.lineno = -1
}
}
// Logger name
func (logger *Logger) lname(r *record) interface{} {
return logger.name
}
// Next sequence number
func (logger *Logger) nextSeqid(r *record) interface{} {
if r.seqid == 0 {
r.seqid = atomic.AddUint64(&(logger.seqid), 1)
}
return r.seqid
}
// Log level number
func (logger *Logger) levelno(r *record) interface{} {
return int32(r.level)
}
// Log level name
func (logger *Logger) levelname(r *record) interface{} {
return levelNames[r.level]
}
// File name of calling logger, with whole path
func (logger *Logger) pathname(r *record) interface{} {
if r.pathname == "" {
genRuntime(r)
}
return r.pathname
}
// File name of calling logger
func (logger *Logger) filename(r *record) interface{} {
if r.filename == "" {
genRuntime(r)
}
return r.filename
}
// module name
func (logger *Logger) module(r *record) interface{} {
module, _ := osext.Executable()
return path.Base(module)
}
// Line number
func (logger *Logger) lineno(r *record) interface{} {
if r.lineno == 0 {
genRuntime(r)
}
return r.lineno
}
// Function name
func (logger *Logger) funcname(r *record) interface{} {
if r.funcname == "" {
genRuntime(r)
}
return r.funcname
}
// Timestamp of starting time
func (logger *Logger) created(r *record) interface{} {
return logger.startTime.UnixNano()
}
// RFC3339Nano time
func (logger *Logger) time(r *record) interface{} {
if r.time.IsZero() {
r.time = time.Now()
}
return r.time.Format(logger.timeFormat)
}
// Nanosecond of starting time
func (logger *Logger) nsecs(r *record) interface{} {
return logger.startTime.Nanosecond()
}
// Nanosecond timestamp
func (logger *Logger) timestamp(r *record) interface{} {
if r.time.IsZero() {
r.time = time.Now()
}
return r.time.UnixNano()
}
// Nanoseconds since logger created
func (logger *Logger) rtime(r *record) interface{} {
if r.time.IsZero() {
r.time = time.Now()
}
return r.time.Sub(logger.startTime).Nanoseconds()
}
// Thread ID
func (logger *Logger) thread(r *record) interface{} {
if r.thread == 0 {
r.thread = int(GetGoID())
}
return r.thread
}
// Process ID
func (logger *Logger) process(r *record) interface{} {
if r.process == 0 {
r.process = os.Getpid()
}
return r.process
}
// The log message
func (logger *Logger) message(r *record) interface{} {
return r.message
}

View File

@ -0,0 +1,60 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
import (
"fmt"
"strconv"
"testing"
)
func empty() {
}
func TestGetGoID(t *testing.T) {
for i := 0; i < 1000; i++ {
goid := int(GetGoID())
go empty()
goid2 := int(GetGoID())
if goid != goid2 {
t.Errorf("%v, %v\n", goid, goid2)
}
}
}
func TestSeqid(t *testing.T) {
logger, _ := BasicLogger("test")
for i := 0; i < 1000; i++ {
r := new(record)
name := strconv.Itoa(i + 1)
seq := logger.nextSeqid(r)
if fmt.Sprintf("%d", seq) != name {
t.Errorf("%v, %v\n", seq, name)
}
}
logger.Destroy()
}
func TestName(t *testing.T) {
name := "test"
logger, _ := BasicLogger(name)
r := new(record)
if logger.lname(r) != name {
t.Errorf("%v, %v\n", logger.lname(r), name)
}
logger.Destroy()
}

View File

@ -0,0 +1,62 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
import (
"errors"
"fmt"
"strings"
)
// pre-defined formats
const (
BasicFormat = "%s [%6s] %30s - %s\n name,levelname,time,message"
RichFormat = "%s [%6s] %d %30s - %d - %s:%s:%d - %s\n name, levelname, seqid, time, thread, filename, funcname, lineno, message"
)
// genLog generates log string from the format setting.
func (logger *Logger) genLog(level Level, message string) string {
fs := make([]interface{}, len(logger.recordArgs))
r := new(record)
r.message = message
r.level = level
for k, v := range logger.recordArgs {
fs[k] = fields[v](logger, r)
}
return fmt.Sprintf(logger.recordFormat, fs...)
}
// parseFormat checks the legality of format and parses it to recordFormat and recordArgs
func (logger *Logger) parseFormat(format string) error {
logger.runtime = false
fts := strings.Split(format, "\n")
if len(fts) != 2 {
return errors.New("logging format error")
}
logger.recordFormat = fts[0]
logger.recordArgs = strings.Split(fts[1], ",")
for k, v := range logger.recordArgs {
tv := strings.TrimSpace(v)
_, ok := fields[tv]
if ok == false {
return errors.New("logging format error")
}
logger.recordArgs[k] = tv
logger.runtime = logger.runtime || runtimeFields[tv]
}
return nil
}

View File

@ -0,0 +1,25 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
// This file defines GetGoId function, which is used to get the id of the
// current goroutine. More details about this function are availeble in the
// runtime.c file of golang source code.
#include <runtime.h>
void ·GetGoID(int32 ret) {
ret = g->goid;
USED(&ret);
}

View File

@ -0,0 +1,68 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
// Level is the type of level.
type Level int32
// Values of level
const (
CRITICAL Level = 50
FATAL Level = CRITICAL
ERROR Level = 40
WARNING Level = 30
WARN Level = WARNING
INFO Level = 20
DEBUG Level = 10
NOTSET Level = 0
)
// The mapping from level to level name
var levelNames = map[Level]string{
CRITICAL: "CRITICAL",
ERROR: "ERROR",
WARNING: "WARNING",
INFO: "INFO",
DEBUG: "DEBUG",
NOTSET: "NOTSET",
}
// The mapping from level name to level
var levelValues = map[string]Level{
"CRITICAL": CRITICAL,
"ERROR": ERROR,
"WARN": WARNING,
"WARNING": WARNING,
"INFO": INFO,
"DEBUG": DEBUG,
"NOTSET": NOTSET,
}
// String function casts level value to string
func (level *Level) String() string {
return levelNames[*level]
}
// GetLevelName lets users be able to get level name from level value.
func GetLevelName(levelValue Level) string {
return levelNames[levelValue]
}
// GetLevelValue lets users be able to get level value from level name.
func GetLevelValue(levelName string) Level {
return levelValues[levelName]
}

View File

@ -0,0 +1,260 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
// Package logging implements log library for other applications. It provides
// functions Debug, Info, Warning, Error, Critical, and formatting version
// Logf.
//
// Example:
//
// logger := logging.SimpleLogger("main")
// logger.SetLevel(logging.WARNING)
// logger.Error("test for error")
// logger.Warning("test for warning", "second parameter")
// logger.Debug("test for debug")
//
package logging
import (
"github.com/ccding/go-config-reader/config"
"io"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
)
// Pre-defined formats
const (
DefaultFileName = "logging.log" // default logging filename
DefaultConfigFile = "logging.conf" // default logging configuration file
DefaultTimeFormat = "2006-01-02 15:04:05.999999999" // defaulttime format
bufSize = 1000 // buffer size for writer
queueSize = 10000 // chan queue size in async logging
reqSize = 10000 // chan queue size in async logging
)
// Logger is the logging struct.
type Logger struct {
// Be careful of the alignment issue of the variable seqid because it
// uses the sync/atomic.AddUint64() operation. If the alignment is
// wrong, it will cause a panic. To solve the alignment issue in an
// easy way, we put seqid to the beginning of the structure.
// seqid is only visiable internally.
seqid uint64 // last used sequence number in record
// These variables can be configured by users.
name string // logger name
level Level // record level higher than this will be printed
recordFormat string // format of the record
recordArgs []string // arguments to be used in the recordFormat
out io.Writer // writer
sync bool // use sync or async way to record logs
timeFormat string // format for time
// These variables are visible to users.
startTime time.Time // start time of the logger
// Internally used variables, which don't have get and set functions.
wlock sync.Mutex // writer lock
queue chan string // queue used in async logging
request chan request // queue used in non-runtime logging
flush chan bool // flush signal for the watcher to write
quit chan bool // quit signal for the watcher to quit
fd *os.File // file handler, used to close the file on destroy
runtime bool // with runtime operation or not
}
// SimpleLogger creates a new logger with simple configuration.
func SimpleLogger(name string) (*Logger, error) {
return createLogger(name, WARNING, BasicFormat, DefaultTimeFormat, os.Stdout, false)
}
// BasicLogger creates a new logger with basic configuration.
func BasicLogger(name string) (*Logger, error) {
return FileLogger(name, WARNING, BasicFormat, DefaultTimeFormat, DefaultFileName, false)
}
// RichLogger creates a new logger with simple configuration.
func RichLogger(name string) (*Logger, error) {
return FileLogger(name, NOTSET, RichFormat, DefaultTimeFormat, DefaultFileName, false)
}
// FileLogger creates a new logger with file output.
func FileLogger(name string, level Level, format string, timeFormat string, file string, sync bool) (*Logger, error) {
out, err := os.Create(file)
if err != nil {
return new(Logger), err
}
logger, err := createLogger(name, level, format, timeFormat, out, sync)
if err == nil {
logger.fd = out
}
return logger, err
}
// WriterLogger creates a new logger with a writer
func WriterLogger(name string, level Level, format string, timeFormat string, out io.Writer, sync bool) (*Logger, error) {
return createLogger(name, level, format, timeFormat, out, sync)
}
// WriterLogger creates a new logger from a configuration file
func ConfigLogger(filename string) (*Logger, error) {
conf, err := config.Read(filename)
if err != nil {
return new(Logger), err
}
ok := true
name, ok := conf["name"]
if !ok {
name = ""
}
slevel, ok := conf["level"]
if !ok {
slevel = "0"
}
l, err := strconv.Atoi(slevel)
if err != nil {
return new(Logger), err
}
level := Level(l)
format, ok := conf["format"]
if !ok {
format = BasicFormat
}
timeFormat, ok := conf["timeFormat"]
if !ok {
timeFormat = DefaultTimeFormat
}
ssync, ok := conf["sync"]
if !ok {
ssync = "0"
}
file, ok := conf["file"]
if !ok {
file = DefaultFileName
}
sync := true
if ssync == "0" {
sync = false
} else if ssync == "1" {
sync = true
} else {
return new(Logger), err
}
return FileLogger(name, level, format, timeFormat, file, sync)
}
// createLogger create a new logger
func createLogger(name string, level Level, format string, timeFormat string, out io.Writer, sync bool) (*Logger, error) {
logger := new(Logger)
err := logger.parseFormat(format)
if err != nil {
return logger, err
}
// asign values to logger
logger.name = name
logger.level = level
logger.out = out
logger.seqid = 0
logger.sync = sync
logger.queue = make(chan string, queueSize)
logger.request = make(chan request, reqSize)
logger.flush = make(chan bool)
logger.quit = make(chan bool)
logger.startTime = time.Now()
logger.fd = nil
logger.timeFormat = timeFormat
// start watcher to write logs if it is async or no runtime field
if !logger.sync {
go logger.watcher()
}
return logger, nil
}
// Destroy sends quit signal to watcher and releases all the resources.
func (logger *Logger) Destroy() {
if !logger.sync {
// quit watcher
logger.quit <- true
// wait for watcher quit
<-logger.quit
}
// clean up
if logger.fd != nil {
logger.fd.Close()
}
}
// Flush the writer
func (logger *Logger) Flush() {
if !logger.sync {
// send flush signal
logger.flush <- true
// wait for flush finish
<-logger.flush
}
}
// Getter functions
func (logger *Logger) Name() string {
return logger.name
}
func (logger *Logger) StartTime() int64 {
return logger.startTime.UnixNano()
}
func (logger *Logger) TimeFormat() string {
return logger.timeFormat
}
func (logger *Logger) Level() Level {
return Level(atomic.LoadInt32((*int32)(&logger.level)))
}
func (logger *Logger) RecordFormat() string {
return logger.recordFormat
}
func (logger *Logger) RecordArgs() []string {
return logger.recordArgs
}
func (logger *Logger) Writer() io.Writer {
return logger.out
}
func (logger *Logger) Sync() bool {
return logger.sync
}
// Setter functions
func (logger *Logger) SetLevel(level Level) {
atomic.StoreInt32((*int32)(&logger.level), int32(level))
}
func (logger *Logger) SetWriter(out ...io.Writer) {
logger.out = io.MultiWriter(out...)
}

View File

@ -0,0 +1,71 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
import (
"fmt"
"os"
"testing"
)
func BenchmarkSync(b *testing.B) {
logger, _ := RichLogger("main")
logger.SetLevel(NOTSET)
for i := 0; i < b.N; i++ {
logger.Error("this is a test from error")
}
logger.Flush()
logger.Destroy()
}
func BenchmarkAsync(b *testing.B) {
logger, _ := RichLogger("main")
logger.SetLevel(NOTSET)
for i := 0; i < b.N; i++ {
logger.Error("this is a test from error")
}
logger.Flush()
logger.Destroy()
}
func BenchmarkBasicSync(b *testing.B) {
logger, _ := BasicLogger("main")
logger.SetLevel(NOTSET)
for i := 0; i < b.N; i++ {
logger.Error("this is a test from error")
}
logger.Flush()
logger.Destroy()
}
func BenchmarkBasicAsync(b *testing.B) {
logger, _ := BasicLogger("main")
logger.SetLevel(NOTSET)
for i := 0; i < b.N; i++ {
logger.Error("this is a test from error")
}
logger.Flush()
logger.Destroy()
}
func BenchmarkPrintln(b *testing.B) {
out, _ := os.Create("logging.log")
for i := 0; i < b.N; i++ {
fmt.Fprintln(out, "this is a test from error")
}
out.Close()
}

View File

@ -0,0 +1,24 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
// request struct stores the logger request
type request struct {
level Level
format string
v []interface{}
}

View File

@ -0,0 +1,130 @@
// Copyright 2013, Cong Ding. 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: Cong Ding <dinggnu@gmail.com>
//
package logging
import (
"bytes"
"fmt"
"sync/atomic"
"time"
)
// watcher watches the logger.queue channel, and writes the logs to output
func (logger *Logger) watcher() {
var buf bytes.Buffer
for {
timeout := time.After(time.Second / 10)
for i := 0; i < bufSize; i++ {
select {
case msg := <-logger.queue:
fmt.Fprintln(&buf, msg)
case req := <-logger.request:
logger.flushReq(&buf, &req)
case <-timeout:
i = bufSize
case <-logger.flush:
logger.flushBuf(&buf)
logger.flush <- true
i = bufSize
case <-logger.quit:
// If quit signal received, cleans the channel
// and writes all of them to io.Writer.
for {
select {
case msg := <-logger.queue:
fmt.Fprintln(&buf, msg)
case req := <-logger.request:
logger.flushReq(&buf, &req)
case <-logger.flush:
// do nothing
default:
logger.flushBuf(&buf)
logger.quit <- true
return
}
}
}
}
logger.flushBuf(&buf)
}
}
// flushBuf flushes the content of buffer to out and reset the buffer
func (logger *Logger) flushBuf(b *bytes.Buffer) {
if len(b.Bytes()) > 0 {
logger.out.Write(b.Bytes())
b.Reset()
}
}
// flushReq handles the request and writes the result to writer
func (logger *Logger) flushReq(b *bytes.Buffer, req *request) {
if req.format == "" {
msg := fmt.Sprint(req.v...)
msg = logger.genLog(req.level, msg)
fmt.Fprintln(b, msg)
} else {
msg := fmt.Sprintf(req.format, req.v...)
msg = logger.genLog(req.level, msg)
fmt.Fprintln(b, msg)
}
}
// flushMsg is to print log to file, stdout, or others.
func (logger *Logger) flushMsg(message string) {
if logger.sync {
logger.wlock.Lock()
defer logger.wlock.Unlock()
fmt.Fprintln(logger.out, message)
} else {
logger.queue <- message
}
}
// log records log v... with level `level'.
func (logger *Logger) log(level Level, v ...interface{}) {
if int32(level) >= atomic.LoadInt32((*int32)(&logger.level)) {
if logger.runtime || logger.sync {
message := fmt.Sprint(v...)
message = logger.genLog(level, message)
logger.flushMsg(message)
} else {
r := new(request)
r.level = level
r.v = v
logger.request <- *r
}
}
}
// logf records log v... with level `level'.
func (logger *Logger) logf(level Level, format string, v ...interface{}) {
if int32(level) >= atomic.LoadInt32((*int32)(&logger.level)) {
if logger.runtime || logger.sync {
message := fmt.Sprintf(format, v...)
message = logger.genLog(level, message)
logger.flushMsg(message)
} else {
r := new(request)
r.level = level
r.format = format
r.v = v
logger.request <- *r
}
}
}