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,22 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,226 @@
#go-logging
go-logging is a high-performance logging library for golang.
* Simple: It supports only necessary operations and easy to get start.
* Fast: Asynchronous logging without runtime-related fields has an extremely
low delay of about 800 nano-seconds.
## Getting Started
The stable version is under the `stable` branch, which does never revert and
is fully tested. The tags in `stable` branch indicate the version numbers.
However, `master` branch is unstable version, and `dev` branch is development
branch. `master` branch merges `dev` branch periodically.
Btw, all the pull request should be sent to the `dev` branch.
### Installation
The step below will download the library source code to
`${GOPATH}/src/github.com/ccding/go-logging`.
```bash
go get github.com/ccding/go-logging/logging
```
Given the source code downloaded, it makes you be able to run the examples,
tests, and benchmarks.
```bash
cd ${GOPATH}/src/github.com/ccding/go-logging/logging
go get
go run ../example.go
go test -v -bench .
```
### Example
go-logging is used like any other Go libraries. You can simply use the library
in this way.
```go
import "github.com/ccding/go-logging/logging"
```
Here is a simple example.
```go
package main
import (
"github.com/ccding/go-logging/logging"
)
func main() {
logger, _ := logging.SimpleLogger("main")
logger.SetLevel(logging.DEBUG)
logger.Error("this is a test from error")
logger.Destroy()
}
```
### Configuration
#### Construction Functions
It has the following functions to create a logger.
```go
// with BasicFormat and writing to stdout
SimpleLogger(name string) (*Logger, error)
// with BasicFormat and writing to DefaultFileName
BasicLogger(name string) (*Logger, error)
// with RichFormatand writing to DefaultFileName
RichLogger(name string) (*Logger, error)
// with detailed configuration and writing to file
FileLogger(name string, level Level, format string, timeFormat string, file string, sync bool) (*Logger, error)
// with detailed configuration and writing to a writer
WriterLogger(name string, level Level, format string, timeFormat string, out io.Writer, sync bool) (*Logger, error)
// read configurations from a config file
ConfigLogger(filename string) (*Logger, error)
```
The meanings of these fields are
```go
name string // logger name
level Level // record level higher than this will be printed
format string // format configuration
timeFormat string // format for time
file string // file name for logging
out io.Writer // writer for logging
sync bool // use sync or async way to record logs
```
The detailed description of these fields will be presented later.
#### Logging Functions
It supports the following functions for logging. All of these functions are
thread-safe.
```go
(*Logger) Logf(level Level, format string, v ...interface{})
(*Logger) Log(level Level, v ...interface{})
(*Logger) Criticalf(format string, v ...interface{})
(*Logger) Critical(v ...interface{})
(*Logger) Fatalf(format string, v ...interface{})
(*Logger) Fatal(v ...interface{})
(*Logger) Errorf(format string, v ...interface{})
(*Logger) Error(v ...interface{})
(*Logger) Warningf(format string, v ...interface{})
(*Logger) Warning(v ...interface{})
(*Logger) Warnf(format string, v ...interface{})
(*Logger) Warn(v ...interface{})
(*Logger) Infof(format string, v ...interface{})
(*Logger) Info(v ...interface{})
(*Logger) Debugf(format string, v ...interface{})
(*Logger) Debug(v ...interface{})
(*Logger) Notsetf(format string, v ...interface{})
(*Logger) Notset(v ...interface{})
```
#### Logger Operations
The logger supports the following operations. In these functions, `SetWriter`
and `Destroy` are not thread-safe, while others are. All these functions are
running in a synchronous way.
```go
// Getter functions
(*Logger) Name() string // get name
(*Logger) TimeFormat() string // get time format
(*Logger) Level() Level // get level [this function is thread safe]
(*Logger) RecordFormat() string // get the first part of the format
(*Logger) RecordArgs() []string // get the second part of the format
(*Logger) Writer() io.Writer // get writer
(*Logger) Sync() bool // get sync or async
// Setter functions
(*Logger) SetLevel(level Level) // set level [this function is thread safe]
(*Logger) SetWriter(out ...io.Writer) // set multiple writers
// Other functions
(*Logger) Flush() // flush the writer
(*Logger) Destroy() // destroy the logger
```
#### Fields Description
##### Name
Name field is a string, which can be written to the logging and used to
separate multiple loggers. It allows two logger having the same name. There
is not any default value for name.
##### Logging Levels
There are these levels in logging.
```go
CRITICAL 50
FATAL CRITICAL
ERROR 40
WARNING 30
WARN WARNING
INFO 20
DEBUG 10
NOTSET 0
```
##### Record Format
The record format is described by a string, which has two parts separated by
`\n`. The first part describes the format of the log, and the second part
lists all the fields to be shown in the log. In other word, the first part is
the first parameter `format` of `fmt.Printf(format string, v ...interface{})`,
and the second part describes the second parameter `v` of it. It is not
allowed to have `\n` in the first part. The fields in the second part are
separated by comma `,`, while extra blank spaces are allowed. An example of
the format string is
```go
const BasicFormat = "%s [%6s] %30s - %s\n name,levelname,time,message"
```
which is the pre-defined `BasicFormat` used by `BasicLogger()` and
`SimpleLogger()`.
It supports the following fields for the second part of the format.
```go
"name" string %s // name of the logger
"seqid" uint64 %d // sequence number
"levelno" int32 %d // level number
"levelname" string %s // level name
"created" int64 %d // starting time of the logger
"nsecs" int64 %d // nanosecond of the starting time
"time" string %s // record created time
"timestamp" int64 %d // timestamp of record
"rtime" int64 %d // relative time since started
"filename" string %s // source filename of the caller
"pathname" string %s // filename with path
"module" string %s // executable filename
"lineno" int %d // line number in source code
"funcname" string %s // function name of the caller
"thread" int32 %d // thread id
"process" int %d // process id
"message" string %d // logger message
```
The following runtime-related fields is extremely expensive and slow, please
be careful when using them.
```go
"filename" string %s // source filename of the caller
"pathname" string %s // filename with path
"lineno" int %d // line number in source code
"funcname" string %s // function name of the caller
"thread" int32 %d // thread id
```
There are a few pre-defined values for record format.
```go
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"
```
##### Time Format
We use the same time format as golang. The default time format is
```go
DefaultTimeFormat = "2006-01-02 15:04:05.999999999" // default time format
```
##### File Name, Writer, and Sync
The meaning of these fields are obvious. Filename is used to create writer.
We also allow the user create a writer by herself and pass it to the logger.
Sync describes whether the user would like to use synchronous or asynchronous
method to write logs. `true` value means synchronous method, and `false` value
means asynchronous way. We suggest you use asynchronous way because it causes
extremely low extra delay by the logging functions.
## Contributors
In alphabetical order
* Cong Ding ([ccding][ccding])
* Xiang Li ([xiangli-cmu][xiangli])
* Zifei Tong ([5kg][5kg])
[ccding]: //github.com/ccding
[xiangli]: //github.com/xiangli-cmu
[5kg]: //github.com/5kg
## TODO List
1. logging server

View File

@ -0,0 +1,3 @@
name = example
sync = 0

View File

@ -0,0 +1,45 @@
// 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 main
import (
"github.com/ccding/go-logging/logging"
"time"
)
func main() {
logger1, _ := logging.SimpleLogger("main")
logger1.SetLevel(logging.NOTSET)
logger1.Error("this is a test from error")
logger1.Debug("this is a test from debug")
logger1.Notset("orz", time.Now().UnixNano())
logger1.Destroy()
logger2, _ := logging.RichLogger("main")
logger2.SetLevel(logging.DEBUG)
logger2.Error("this is a test from error")
logger2.Debug("this is a test from debug")
logger2.Notset("orz", time.Now().UnixNano())
logger2.Destroy()
logger3, _ := logging.ConfigLogger("example.conf")
logger3.SetLevel(logging.DEBUG)
logger3.Error("this is a test from error")
logger3.Debug("this is a test from debug")
logger3.Notset("orz", time.Now().UnixNano())
logger3.Destroy()
}

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
}
}
}