pax_global_header 0000666 0000000 0000000 00000000064 12473532116 0014516 g ustar 00root root 0000000 0000000 52 comment=4c7cbce140ca070eeb59a28f4bf9507e511711f9
golang-juju-loggo-0.0~git20150318/ 0000775 0000000 0000000 00000000000 12473532116 0016432 5 ustar 00root root 0000000 0000000 golang-juju-loggo-0.0~git20150318/.gitignore 0000664 0000000 0000000 00000000020 12473532116 0020412 0 ustar 00root root 0000000 0000000 example/example
golang-juju-loggo-0.0~git20150318/LICENSE 0000664 0000000 0000000 00000021053 12473532116 0017440 0 ustar 00root root 0000000 0000000 This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
golang-juju-loggo-0.0~git20150318/Makefile 0000664 0000000 0000000 00000000437 12473532116 0020076 0 ustar 00root root 0000000 0000000 default: check
check:
go test && go test -compiler gccgo
docs:
godoc2md github.com/juju/loggo > README.md
sed -i 's|\[godoc-link-here\]|[](https://godoc.org/github.com/juju/loggo)|' README.md
.PHONY: default check docs
golang-juju-loggo-0.0~git20150318/README.md 0000664 0000000 0000000 00000027240 12473532116 0017716 0 ustar 00root root 0000000 0000000
# loggo
import "github.com/juju/loggo"
[](https://godoc.org/github.com/juju/loggo)
### Module level logging for Go
This package provides an alternative to the standard library log package.
The actual logging functions never return errors. If you are logging
something, you really don't want to be worried about the logging
having trouble.
Modules have names that are defined by dotted strings.
"first.second.third"
There is a root module that has the name `""`. Each module
(except the root module) has a parent, identified by the part of
the name without the last dotted value.
* the parent of "first.second.third" is "first.second"
* the parent of "first.second" is "first"
* the parent of "first" is "" (the root module)
Each module can specify its own severity level. Logging calls that are of
a lower severity than the module's effective severity level are not written
out.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
By default there is one writer registered, which will write to Stderr,
and the root module, which will only emit warnings and above.
If you want to continue using the default
logger, but have it emit all logging levels you need to do the following.
writer, _, err := loggo.RemoveWriter("default")
// err is non-nil if and only if the name isn't found.
loggo.RegisterWriter("default", writer, loggo.TRACE)
## func ConfigureLoggers
``` go
func ConfigureLoggers(specification string) error
```
ConfigureLoggers configures loggers according to the given string
specification, which specifies a set of modules and their associated
logging levels. Loggers are colon- or semicolon-separated; each
module is specified as =. White space outside of
module names and levels is ignored. The root module is specified
with the name "".
An example specification:
`=ERROR; foo.bar=WARNING`
## func LoggerInfo
``` go
func LoggerInfo() string
```
LoggerInfo returns information about the configured loggers and their logging
levels. The information is returned in the format expected by
ConfigureModules. Loggers with UNSPECIFIED level will not
be included.
## func ParseConfigurationString
``` go
func ParseConfigurationString(specification string) (map[string]Level, error)
```
ParseConfigurationString parses a logger configuration string into a map of
logger names and their associated log level. This method is provided to
allow other programs to pre-validate a configuration string rather than
just calling ConfigureLoggers.
Loggers are colon- or semicolon-separated; each module is specified as
=. White space outside of module names and levels is
ignored. The root module is specified with the name "".
As a special case, a log level may be specified on its own.
This is equivalent to specifying the level of the root module,
so "DEBUG" is equivalent to `=DEBUG`
An example specification:
`=ERROR; foo.bar=WARNING`
## func RegisterWriter
``` go
func RegisterWriter(name string, writer Writer, minLevel Level) error
```
RegisterWriter adds the writer to the list of writers that get notified
when logging. When registering, the caller specifies the minimum logging
level that will be written, and a name for the writer. If there is already
a registered writer with that name, an error is returned.
## func ResetLoggers
``` go
func ResetLoggers()
```
ResetLogging iterates through the known modules and sets the levels of all
to UNSPECIFIED, except for which is set to WARNING.
## func ResetWriters
``` go
func ResetWriters()
```
ResetWriters puts the list of writers back into the initial state.
## func WillWrite
``` go
func WillWrite(level Level) bool
```
WillWrite returns whether there are any writers registered
at or above the given severity level. If it returns
false, a log message at the given level will be discarded.
## type DefaultFormatter
``` go
type DefaultFormatter struct{}
```
DefaultFormatter provides a simple concatenation of all the components.
### func (\*DefaultFormatter) Format
``` go
func (*DefaultFormatter) Format(level Level, module, filename string, line int, timestamp time.Time, message string) string
```
Format returns the parameters separated by spaces except for filename and
line which are separated by a colon. The timestamp is shown to second
resolution in UTC.
## type Formatter
``` go
type Formatter interface {
Format(level Level, module, filename string, line int, timestamp time.Time, message string) string
}
```
Formatter defines the single method Format, which takes the logging
information, and converts it to a string.
## type Level
``` go
type Level uint32
```
Level holds a severity level.
``` go
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
)
```
The severity levels. Higher values are more considered more
important.
### func ParseLevel
``` go
func ParseLevel(level string) (Level, bool)
```
ParseLevel converts a string representation of a logging level to a
Level. It returns the level and whether it was valid or not.
### func (Level) String
``` go
func (level Level) String() string
```
## type Logger
``` go
type Logger struct {
// contains filtered or unexported fields
}
```
A Logger represents a logging module. It has an associated logging
level which can be changed; messages of lesser severity will
be dropped. Loggers have a hierarchical relationship - see
the package documentation.
The zero Logger value is usable - any messages logged
to it will be sent to the root Logger.
### func GetLogger
``` go
func GetLogger(name string) Logger
```
GetLogger returns a Logger for the given module name,
creating it and its parents if necessary.
### func (Logger) Criticalf
``` go
func (logger Logger) Criticalf(message string, args ...interface{})
```
Criticalf logs the printf-formatted message at critical level.
### func (Logger) Debugf
``` go
func (logger Logger) Debugf(message string, args ...interface{})
```
Debugf logs the printf-formatted message at debug level.
### func (Logger) EffectiveLogLevel
``` go
func (logger Logger) EffectiveLogLevel() Level
```
EffectiveLogLevel returns the effective log level of
the receiver - that is, messages with a lesser severity
level will be discarded.
If the log level of the receiver is unspecified,
it will be taken from the effective log level of its
parent.
### func (Logger) Errorf
``` go
func (logger Logger) Errorf(message string, args ...interface{})
```
Errorf logs the printf-formatted message at error level.
### func (Logger) Infof
``` go
func (logger Logger) Infof(message string, args ...interface{})
```
Infof logs the printf-formatted message at info level.
### func (Logger) IsDebugEnabled
``` go
func (logger Logger) IsDebugEnabled() bool
```
IsDebugEnabled returns whether debugging is enabled
at debug level.
### func (Logger) IsErrorEnabled
``` go
func (logger Logger) IsErrorEnabled() bool
```
IsErrorEnabled returns whether debugging is enabled
at error level.
### func (Logger) IsInfoEnabled
``` go
func (logger Logger) IsInfoEnabled() bool
```
IsInfoEnabled returns whether debugging is enabled
at info level.
### func (Logger) IsLevelEnabled
``` go
func (logger Logger) IsLevelEnabled(level Level) bool
```
IsLevelEnabled returns whether debugging is enabled
for the given log level.
### func (Logger) IsTraceEnabled
``` go
func (logger Logger) IsTraceEnabled() bool
```
IsTraceEnabled returns whether debugging is enabled
at trace level.
### func (Logger) IsWarningEnabled
``` go
func (logger Logger) IsWarningEnabled() bool
```
IsWarningEnabled returns whether debugging is enabled
at warning level.
### func (Logger) LogCallf
``` go
func (logger Logger) LogCallf(calldepth int, level Level, message string, args ...interface{})
```
LogCallf logs a printf-formatted message at the given level.
The location of the call is indicated by the calldepth argument.
A calldepth of 1 means the function that called this function.
A message will be discarded if level is less than the
the effective log level of the logger.
Note that the writers may also filter out messages that
are less than their registered minimum severity level.
### func (Logger) LogLevel
``` go
func (logger Logger) LogLevel() Level
```
LogLevel returns the configured log level of the logger.
### func (Logger) Logf
``` go
func (logger Logger) Logf(level Level, message string, args ...interface{})
```
Logf logs a printf-formatted message at the given level.
A message will be discarded if level is less than the
the effective log level of the logger.
Note that the writers may also filter out messages that
are less than their registered minimum severity level.
### func (Logger) Name
``` go
func (logger Logger) Name() string
```
Name returns the logger's module name.
### func (Logger) SetLogLevel
``` go
func (logger Logger) SetLogLevel(level Level)
```
SetLogLevel sets the severity level of the given logger.
The root logger cannot be set to UNSPECIFIED level.
See EffectiveLogLevel for how this affects the
actual messages logged.
### func (Logger) Tracef
``` go
func (logger Logger) Tracef(message string, args ...interface{})
```
Tracef logs the printf-formatted message at trace level.
### func (Logger) Warningf
``` go
func (logger Logger) Warningf(message string, args ...interface{})
```
Warningf logs the printf-formatted message at warning level.
## type TestLogValues
``` go
type TestLogValues struct {
Level Level
Module string
Filename string
Line int
Timestamp time.Time
Message string
}
```
TestLogValues represents a single logging call.
## type TestWriter
``` go
type TestWriter struct {
// contains filtered or unexported fields
}
```
TestWriter is a useful Writer for testing purposes. Each component of the
logging message is stored in the Log array.
### func (\*TestWriter) Clear
``` go
func (writer *TestWriter) Clear()
```
Clear removes any saved log messages.
### func (\*TestWriter) Log
``` go
func (writer *TestWriter) Log() []TestLogValues
```
Log returns a copy of the current logged values.
### func (\*TestWriter) Write
``` go
func (writer *TestWriter) Write(level Level, module, filename string, line int, timestamp time.Time, message string)
```
Write saves the params as members in the TestLogValues struct appended to the Log array.
## type Writer
``` go
type Writer interface {
// Write writes a message to the Writer with the given
// level and module name. The filename and line hold
// the file name and line number of the code that is
// generating the log message; the time stamp holds
// the time the log message was generated, and
// message holds the log message itself.
Write(level Level, name, filename string, line int, timestamp time.Time, message string)
}
```
Writer is implemented by any recipient of log messages.
### func NewSimpleWriter
``` go
func NewSimpleWriter(writer io.Writer, formatter Formatter) Writer
```
NewSimpleWriter returns a new writer that writes
log messages to the given io.Writer formatting the
messages with the given formatter.
### func RemoveWriter
``` go
func RemoveWriter(name string) (Writer, Level, error)
```
RemoveWriter removes the Writer identified by 'name' and returns it.
If the Writer is not found, an error is returned.
### func ReplaceDefaultWriter
``` go
func ReplaceDefaultWriter(writer Writer) (Writer, error)
```
ReplaceDefaultWriter is a convenience method that does the equivalent of
RemoveWriter and then RegisterWriter with the name "default". The previous
default writer, if any is returned.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
golang-juju-loggo-0.0~git20150318/doc.go 0000664 0000000 0000000 00000002632 12473532116 0017531 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
/*
[godoc-link-here]
Module level logging for Go
This package provides an alternative to the standard library log package.
The actual logging functions never return errors. If you are logging
something, you really don't want to be worried about the logging
having trouble.
Modules have names that are defined by dotted strings.
"first.second.third"
There is a root module that has the name `""`. Each module
(except the root module) has a parent, identified by the part of
the name without the last dotted value.
* the parent of "first.second.third" is "first.second"
* the parent of "first.second" is "first"
* the parent of "first" is "" (the root module)
Each module can specify its own severity level. Logging calls that are of
a lower severity than the module's effective severity level are not written
out.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
By default there is one writer registered, which will write to Stderr,
and the root module, which will only emit warnings and above.
If you want to continue using the default
logger, but have it emit all logging levels you need to do the following.
writer, _, err := loggo.RemoveWriter("default")
// err is non-nil if and only if the name isn't found.
loggo.RegisterWriter("default", writer, loggo.TRACE)
*/
package loggo
golang-juju-loggo-0.0~git20150318/example/ 0000775 0000000 0000000 00000000000 12473532116 0020065 5 ustar 00root root 0000000 0000000 golang-juju-loggo-0.0~git20150318/example/first.go 0000664 0000000 0000000 00000000614 12473532116 0021544 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/juju/loggo"
)
var first = loggo.GetLogger("first")
func FirstCritical(message string) {
first.Criticalf(message)
}
func FirstError(message string) {
first.Errorf(message)
}
func FirstWarning(message string) {
first.Warningf(message)
}
func FirstInfo(message string) {
first.Infof(message)
}
func FirstTrace(message string) {
first.Tracef(message)
}
golang-juju-loggo-0.0~git20150318/example/main.go 0000664 0000000 0000000 00000001404 12473532116 0021337 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"github.com/juju/loggo"
)
var logger = loggo.GetLogger("main")
var rootLogger = loggo.GetLogger("")
func main() {
args := os.Args
if len(args) > 1 {
loggo.ConfigureLoggers(args[1])
} else {
fmt.Println("Add a parameter to configure the logging:")
fmt.Println("E.g. \"=INFO;first=TRACE\"")
}
fmt.Println("\nCurrent logging levels:")
fmt.Println(loggo.LoggerInfo())
fmt.Println("")
rootLogger.Infof("Start of test.")
FirstCritical("first critical")
FirstError("first error")
FirstWarning("first warning")
FirstInfo("first info")
FirstTrace("first trace")
SecondCritical("first critical")
SecondError("first error")
SecondWarning("first warning")
SecondInfo("first info")
SecondTrace("first trace")
}
golang-juju-loggo-0.0~git20150318/example/second.go 0000664 0000000 0000000 00000000630 12473532116 0021666 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/juju/loggo"
)
var second = loggo.GetLogger("second")
func SecondCritical(message string) {
second.Criticalf(message)
}
func SecondError(message string) {
second.Errorf(message)
}
func SecondWarning(message string) {
second.Warningf(message)
}
func SecondInfo(message string) {
second.Infof(message)
}
func SecondTrace(message string) {
second.Tracef(message)
}
golang-juju-loggo-0.0~git20150318/formatter.go 0000664 0000000 0000000 00000002010 12473532116 0020755 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"path/filepath"
"time"
)
// Formatter defines the single method Format, which takes the logging
// information, and converts it to a string.
type Formatter interface {
Format(level Level, module, filename string, line int, timestamp time.Time, message string) string
}
// DefaultFormatter provides a simple concatenation of all the components.
type DefaultFormatter struct{}
// Format returns the parameters separated by spaces except for filename and
// line which are separated by a colon. The timestamp is shown to second
// resolution in UTC.
func (*DefaultFormatter) Format(level Level, module, filename string, line int, timestamp time.Time, message string) string {
ts := timestamp.In(time.UTC).Format("2006-01-02 15:04:05")
// Just get the basename from the filename
filename = filepath.Base(filename)
return fmt.Sprintf("%s %s %s %s:%d %s", ts, level, module, filename, line, message)
}
golang-juju-loggo-0.0~git20150318/formatter_test.go 0000664 0000000 0000000 00000001264 12473532116 0022026 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"time"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type formatterSuite struct{}
var _ = gc.Suite(&formatterSuite{})
func (*formatterSuite) TestDefaultFormat(c *gc.C) {
location, err := time.LoadLocation("UTC")
c.Assert(err, gc.IsNil)
testTime := time.Date(2013, 5, 3, 10, 53, 24, 123456, location)
formatter := &loggo.DefaultFormatter{}
formatted := formatter.Format(loggo.WARNING, "test.module", "some/deep/filename", 42, testTime, "hello world!")
c.Assert(formatted, gc.Equals, "2013-05-03 10:53:24 WARNING test.module filename:42 hello world!")
}
golang-juju-loggo-0.0~git20150318/logger.go 0000664 0000000 0000000 00000027023 12473532116 0020244 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"runtime"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
)
// Level holds a severity level.
type Level uint32
// The severity levels. Higher values are more considered more
// important.
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
)
// A Logger represents a logging module. It has an associated logging
// level which can be changed; messages of lesser severity will
// be dropped. Loggers have a hierarchical relationship - see
// the package documentation.
//
// The zero Logger value is usable - any messages logged
// to it will be sent to the root Logger.
type Logger struct {
impl *module
}
type module struct {
name string
level Level
parent *module
}
// Initially the modules map only contains the root module.
var (
root = &module{level: WARNING}
modulesMutex sync.Mutex
modules = map[string]*module{
"": root,
}
)
func (level Level) String() string {
switch level {
case UNSPECIFIED:
return "UNSPECIFIED"
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITICAL"
}
return ""
}
// get atomically gets the value of the given level.
func (level *Level) get() Level {
return Level(atomic.LoadUint32((*uint32)(level)))
}
// set atomically sets the value of the receiver
// to the given level.
func (level *Level) set(newLevel Level) {
atomic.StoreUint32((*uint32)(level), uint32(newLevel))
}
// getLoggerInternal assumes that the modulesMutex is locked.
func getLoggerInternal(name string) Logger {
impl, found := modules[name]
if found {
return Logger{impl}
}
parentName := ""
if i := strings.LastIndex(name, "."); i >= 0 {
parentName = name[0:i]
}
parent := getLoggerInternal(parentName).impl
impl = &module{name, UNSPECIFIED, parent}
modules[name] = impl
return Logger{impl}
}
// GetLogger returns a Logger for the given module name,
// creating it and its parents if necessary.
func GetLogger(name string) Logger {
// Lowercase the module name, and look for it in the modules map.
name = strings.ToLower(name)
modulesMutex.Lock()
defer modulesMutex.Unlock()
return getLoggerInternal(name)
}
// LoggerInfo returns information about the configured loggers and their logging
// levels. The information is returned in the format expected by
// ConfigureModules. Loggers with UNSPECIFIED level will not
// be included.
func LoggerInfo() string {
output := []string{}
// output in alphabetical order.
keys := []string{}
modulesMutex.Lock()
defer modulesMutex.Unlock()
for key := range modules {
keys = append(keys, key)
}
sort.Strings(keys)
for _, name := range keys {
mod := modules[name]
severity := mod.level
if severity == UNSPECIFIED {
continue
}
output = append(output, fmt.Sprintf("%s=%s", mod.Name(), severity))
}
return strings.Join(output, ";")
}
// ParseConfigurationString parses a logger configuration string into a map of
// logger names and their associated log level. This method is provided to
// allow other programs to pre-validate a configuration string rather than
// just calling ConfigureLoggers.
//
// Loggers are colon- or semicolon-separated; each module is specified as
// =. White space outside of module names and levels is
// ignored. The root module is specified with the name "".
//
// As a special case, a log level may be specified on its own.
// This is equivalent to specifying the level of the root module,
// so "DEBUG" is equivalent to `=DEBUG`
//
// An example specification:
// `=ERROR; foo.bar=WARNING`
func ParseConfigurationString(specification string) (map[string]Level, error) {
levels := make(map[string]Level)
if level, ok := ParseLevel(specification); ok {
levels[""] = level
return levels, nil
}
values := strings.FieldsFunc(specification, func(r rune) bool { return r == ';' || r == ':' })
for _, value := range values {
s := strings.SplitN(value, "=", 2)
if len(s) < 2 {
return nil, fmt.Errorf("logger specification expected '=', found %q", value)
}
name := strings.TrimSpace(s[0])
levelStr := strings.TrimSpace(s[1])
if name == "" || levelStr == "" {
return nil, fmt.Errorf("logger specification %q has blank name or level", value)
}
if name == "" {
name = ""
}
level, ok := ParseLevel(levelStr)
if !ok {
return nil, fmt.Errorf("unknown severity level %q", levelStr)
}
levels[name] = level
}
return levels, nil
}
// ConfigureLoggers configures loggers according to the given string
// specification, which specifies a set of modules and their associated
// logging levels. Loggers are colon- or semicolon-separated; each
// module is specified as =. White space outside of
// module names and levels is ignored. The root module is specified
// with the name "".
//
// An example specification:
// `=ERROR; foo.bar=WARNING`
func ConfigureLoggers(specification string) error {
if specification == "" {
return nil
}
levels, err := ParseConfigurationString(specification)
if err != nil {
return err
}
for name, level := range levels {
GetLogger(name).SetLogLevel(level)
}
return nil
}
// ResetLogging iterates through the known modules and sets the levels of all
// to UNSPECIFIED, except for which is set to WARNING.
func ResetLoggers() {
modulesMutex.Lock()
defer modulesMutex.Unlock()
for name, module := range modules {
if name == "" {
module.level.set(WARNING)
} else {
module.level.set(UNSPECIFIED)
}
}
}
// ParseLevel converts a string representation of a logging level to a
// Level. It returns the level and whether it was valid or not.
func ParseLevel(level string) (Level, bool) {
level = strings.ToUpper(level)
switch level {
case "UNSPECIFIED":
return UNSPECIFIED, true
case "TRACE":
return TRACE, true
case "DEBUG":
return DEBUG, true
case "INFO":
return INFO, true
case "WARN", "WARNING":
return WARNING, true
case "ERROR":
return ERROR, true
case "CRITICAL":
return CRITICAL, true
}
return UNSPECIFIED, false
}
func (logger Logger) getModule() *module {
if logger.impl == nil {
return root
}
return logger.impl
}
// Name returns the logger's module name.
func (logger Logger) Name() string {
return logger.getModule().Name()
}
// LogLevel returns the configured log level of the logger.
func (logger Logger) LogLevel() Level {
return logger.getModule().level.get()
}
func (module *module) getEffectiveLogLevel() Level {
// Note: the root module is guaranteed to have a
// specified logging level, so acts as a suitable sentinel
// for this loop.
for {
if level := module.level.get(); level != UNSPECIFIED {
return level
}
module = module.parent
}
panic("unreachable")
}
func (module *module) Name() string {
if module.name == "" {
return ""
}
return module.name
}
// EffectiveLogLevel returns the effective log level of
// the receiver - that is, messages with a lesser severity
// level will be discarded.
//
// If the log level of the receiver is unspecified,
// it will be taken from the effective log level of its
// parent.
func (logger Logger) EffectiveLogLevel() Level {
return logger.getModule().getEffectiveLogLevel()
}
// SetLogLevel sets the severity level of the given logger.
// The root logger cannot be set to UNSPECIFIED level.
// See EffectiveLogLevel for how this affects the
// actual messages logged.
func (logger Logger) SetLogLevel(level Level) {
module := logger.getModule()
// The root module can't be unspecified.
if module.name == "" && level == UNSPECIFIED {
level = WARNING
}
module.level.set(level)
}
// Logf logs a printf-formatted message at the given level.
// A message will be discarded if level is less than the
// the effective log level of the logger.
// Note that the writers may also filter out messages that
// are less than their registered minimum severity level.
func (logger Logger) Logf(level Level, message string, args ...interface{}) {
logger.LogCallf(2, level, message, args...)
}
// LogCallf logs a printf-formatted message at the given level.
// The location of the call is indicated by the calldepth argument.
// A calldepth of 1 means the function that called this function.
// A message will be discarded if level is less than the
// the effective log level of the logger.
// Note that the writers may also filter out messages that
// are less than their registered minimum severity level.
func (logger Logger) LogCallf(calldepth int, level Level, message string, args ...interface{}) {
if logger.getModule().getEffectiveLogLevel() > level ||
!WillWrite(level) ||
level < TRACE ||
level > CRITICAL {
return
}
// Gather time, and filename, line number.
now := time.Now() // get this early.
// Param to Caller is the call depth. Since this method is called from
// the Logger methods, we want the place that those were called from.
_, file, line, ok := runtime.Caller(calldepth + 1)
if !ok {
file = "???"
line = 0
}
// Trim newline off format string, following usual
// Go logging conventions.
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[0 : len(message)-1]
}
// To avoid having a proliferation of Info/Infof methods,
// only use Sprintf if there are any args, and rely on the
// `go vet` tool for the obvious cases where someone has forgotten
// to provide an arg.
formattedMessage := message
if len(args) > 0 {
formattedMessage = fmt.Sprintf(message, args...)
}
writeToWriters(level, logger.impl.name, file, line, now, formattedMessage)
}
// Criticalf logs the printf-formatted message at critical level.
func (logger Logger) Criticalf(message string, args ...interface{}) {
logger.Logf(CRITICAL, message, args...)
}
// Errorf logs the printf-formatted message at error level.
func (logger Logger) Errorf(message string, args ...interface{}) {
logger.Logf(ERROR, message, args...)
}
// Warningf logs the printf-formatted message at warning level.
func (logger Logger) Warningf(message string, args ...interface{}) {
logger.Logf(WARNING, message, args...)
}
// Infof logs the printf-formatted message at info level.
func (logger Logger) Infof(message string, args ...interface{}) {
logger.Logf(INFO, message, args...)
}
// Debugf logs the printf-formatted message at debug level.
func (logger Logger) Debugf(message string, args ...interface{}) {
logger.Logf(DEBUG, message, args...)
}
// Tracef logs the printf-formatted message at trace level.
func (logger Logger) Tracef(message string, args ...interface{}) {
logger.Logf(TRACE, message, args...)
}
// IsLevelEnabled returns whether debugging is enabled
// for the given log level.
func (logger Logger) IsLevelEnabled(level Level) bool {
return logger.getModule().getEffectiveLogLevel() <= level
}
// IsErrorEnabled returns whether debugging is enabled
// at error level.
func (logger Logger) IsErrorEnabled() bool {
return logger.IsLevelEnabled(ERROR)
}
// IsWarningEnabled returns whether debugging is enabled
// at warning level.
func (logger Logger) IsWarningEnabled() bool {
return logger.IsLevelEnabled(WARNING)
}
// IsInfoEnabled returns whether debugging is enabled
// at info level.
func (logger Logger) IsInfoEnabled() bool {
return logger.IsLevelEnabled(INFO)
}
// IsDebugEnabled returns whether debugging is enabled
// at debug level.
func (logger Logger) IsDebugEnabled() bool {
return logger.IsLevelEnabled(DEBUG)
}
// IsTraceEnabled returns whether debugging is enabled
// at trace level.
func (logger Logger) IsTraceEnabled() bool {
return logger.IsLevelEnabled(TRACE)
}
golang-juju-loggo-0.0~git20150318/logger_test.go 0000664 0000000 0000000 00000035335 12473532116 0021310 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"io/ioutil"
"os"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type loggerSuite struct{}
var _ = gc.Suite(&loggerSuite{})
func (*loggerSuite) SetUpTest(c *gc.C) {
loggo.ResetLoggers()
}
func (*loggerSuite) TestRootLogger(c *gc.C) {
root := loggo.Logger{}
c.Assert(root.Name(), gc.Equals, "")
c.Assert(root.IsErrorEnabled(), gc.Equals, true)
c.Assert(root.IsWarningEnabled(), gc.Equals, true)
c.Assert(root.IsInfoEnabled(), gc.Equals, false)
c.Assert(root.IsDebugEnabled(), gc.Equals, false)
c.Assert(root.IsTraceEnabled(), gc.Equals, false)
}
func (*loggerSuite) TestModuleName(c *gc.C) {
logger := loggo.GetLogger("loggo.testing")
c.Assert(logger.Name(), gc.Equals, "loggo.testing")
}
func (*loggerSuite) TestSetLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
c.Assert(logger.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.TRACE)
c.Assert(logger.LogLevel(), gc.Equals, loggo.TRACE)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.TRACE)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, true)
c.Assert(logger.IsTraceEnabled(), gc.Equals, true)
logger.SetLogLevel(loggo.DEBUG)
c.Assert(logger.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, true)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.INFO)
c.Assert(logger.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.INFO)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.WARNING)
c.Assert(logger.LogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, true)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.ERROR)
c.Assert(logger.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(logger.IsErrorEnabled(), gc.Equals, true)
c.Assert(logger.IsWarningEnabled(), gc.Equals, false)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
// This is added for completeness, but not really expected to be used.
logger.SetLogLevel(loggo.CRITICAL)
c.Assert(logger.LogLevel(), gc.Equals, loggo.CRITICAL)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.CRITICAL)
c.Assert(logger.IsErrorEnabled(), gc.Equals, false)
c.Assert(logger.IsWarningEnabled(), gc.Equals, false)
c.Assert(logger.IsInfoEnabled(), gc.Equals, false)
c.Assert(logger.IsDebugEnabled(), gc.Equals, false)
c.Assert(logger.IsTraceEnabled(), gc.Equals, false)
logger.SetLogLevel(loggo.UNSPECIFIED)
c.Assert(logger.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(logger.EffectiveLogLevel(), gc.Equals, loggo.WARNING)
}
func (*loggerSuite) TestLevelsSharedForSameModule(c *gc.C) {
logger1 := loggo.GetLogger("testing.module")
logger2 := loggo.GetLogger("testing.module")
logger1.SetLogLevel(loggo.INFO)
c.Assert(logger1.IsInfoEnabled(), gc.Equals, true)
c.Assert(logger2.IsInfoEnabled(), gc.Equals, true)
}
func (*loggerSuite) TestModuleLowered(c *gc.C) {
logger1 := loggo.GetLogger("TESTING.MODULE")
logger2 := loggo.GetLogger("Testing")
c.Assert(logger1.Name(), gc.Equals, "testing.module")
c.Assert(logger2.Name(), gc.Equals, "testing")
}
func (*loggerSuite) TestLevelsInherited(c *gc.C) {
root := loggo.GetLogger("")
first := loggo.GetLogger("first")
second := loggo.GetLogger("first.second")
root.SetLogLevel(loggo.ERROR)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(second.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
first.SetLogLevel(loggo.DEBUG)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(second.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
second.SetLogLevel(loggo.INFO)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(second.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.INFO)
first.SetLogLevel(loggo.UNSPECIFIED)
c.Assert(root.LogLevel(), gc.Equals, loggo.ERROR)
c.Assert(root.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(first.LogLevel(), gc.Equals, loggo.UNSPECIFIED)
c.Assert(first.EffectiveLogLevel(), gc.Equals, loggo.ERROR)
c.Assert(second.LogLevel(), gc.Equals, loggo.INFO)
c.Assert(second.EffectiveLogLevel(), gc.Equals, loggo.INFO)
}
var parseLevelTests = []struct {
str string
level loggo.Level
fail bool
}{{
str: "trace",
level: loggo.TRACE,
}, {
str: "TrAce",
level: loggo.TRACE,
}, {
str: "TRACE",
level: loggo.TRACE,
}, {
str: "debug",
level: loggo.DEBUG,
}, {
str: "DEBUG",
level: loggo.DEBUG,
}, {
str: "info",
level: loggo.INFO,
}, {
str: "INFO",
level: loggo.INFO,
}, {
str: "warn",
level: loggo.WARNING,
}, {
str: "WARN",
level: loggo.WARNING,
}, {
str: "warning",
level: loggo.WARNING,
}, {
str: "WARNING",
level: loggo.WARNING,
}, {
str: "error",
level: loggo.ERROR,
}, {
str: "ERROR",
level: loggo.ERROR,
}, {
str: "critical",
level: loggo.CRITICAL,
}, {
str: "not_specified",
fail: true,
}, {
str: "other",
fail: true,
}, {
str: "",
fail: true,
}}
func (*loggerSuite) TestParseLevel(c *gc.C) {
for _, test := range parseLevelTests {
level, ok := loggo.ParseLevel(test.str)
c.Assert(level, gc.Equals, test.level)
c.Assert(ok, gc.Equals, !test.fail)
}
}
var levelStringValueTests = map[loggo.Level]string{
loggo.UNSPECIFIED: "UNSPECIFIED",
loggo.DEBUG: "DEBUG",
loggo.TRACE: "TRACE",
loggo.INFO: "INFO",
loggo.WARNING: "WARNING",
loggo.ERROR: "ERROR",
loggo.CRITICAL: "CRITICAL",
loggo.Level(42): "", // other values are unknown
}
func (*loggerSuite) TestLevelStringValue(c *gc.C) {
for level, str := range levelStringValueTests {
c.Assert(level.String(), gc.Equals, str)
}
}
type stack_error struct {
message string
stack []string
}
func (s *stack_error) Error() string {
return s.message
}
func (s *stack_error) StackTrace() []string {
return s.stack
}
func checkLastMessage(c *gc.C, writer *loggo.TestWriter, expected string) {
log := writer.Log()
writer.Clear()
obtained := log[len(log)-1].Message
c.Check(obtained, gc.Equals, expected)
}
func (*loggerSuite) TestLoggingStrings(c *gc.C) {
writer := &loggo.TestWriter{}
loggo.ReplaceDefaultWriter(writer)
logger := loggo.GetLogger("test")
logger.SetLogLevel(loggo.TRACE)
logger.Infof("simple")
checkLastMessage(c, writer, "simple")
logger.Infof("with args %d", 42)
checkLastMessage(c, writer, "with args 42")
logger.Infof("working 100%")
checkLastMessage(c, writer, "working 100%")
logger.Infof("missing %s")
checkLastMessage(c, writer, "missing %s")
}
func (*loggerSuite) TestLocationCapture(c *gc.C) {
writer := &loggo.TestWriter{}
loggo.ReplaceDefaultWriter(writer)
logger := loggo.GetLogger("test")
logger.SetLogLevel(loggo.TRACE)
logger.Criticalf("critical message") //tag critical-location
logger.Errorf("error message") //tag error-location
logger.Warningf("warning message") //tag warning-location
logger.Infof("info message") //tag info-location
logger.Debugf("debug message") //tag debug-location
logger.Tracef("trace message") //tag trace-location
log := writer.Log()
tags := []string{
"critical-location",
"error-location",
"warning-location",
"info-location",
"debug-location",
"trace-location",
}
c.Assert(log, gc.HasLen, len(tags))
for x := range tags {
assertLocation(c, log[x], tags[x])
}
}
var configureLoggersTests = []struct {
spec string
info string
err string
}{{
spec: "",
info: "=WARNING",
}, {
spec: "=UNSPECIFIED",
info: "=WARNING",
}, {
spec: "=DEBUG",
info: "=DEBUG",
}, {
spec: "TRACE",
info: "=TRACE",
}, {
spec: "test.module=debug",
info: "=WARNING;test.module=DEBUG",
}, {
spec: "module=info; sub.module=debug; other.module=warning",
info: "=WARNING;module=INFO;other.module=WARNING;sub.module=DEBUG",
}, {
spec: " foo.bar \t\r\n= \t\r\nCRITICAL \t\r\n; \t\r\nfoo \r\t\n = DEBUG",
info: "=WARNING;foo=DEBUG;foo.bar=CRITICAL",
}, {
spec: "foo;bar",
info: "=WARNING",
err: `logger specification expected '=', found "foo"`,
}, {
spec: "=foo",
info: "=WARNING",
err: `logger specification "=foo" has blank name or level`,
}, {
spec: "foo=",
info: "=WARNING",
err: `logger specification "foo=" has blank name or level`,
}, {
spec: "=",
info: "=WARNING",
err: `logger specification "=" has blank name or level`,
}, {
spec: "foo=unknown",
info: "=WARNING",
err: `unknown severity level "unknown"`,
}, {
// Test that nothing is changed even when the
// first part of the specification parses ok.
spec: "module=info; foo=unknown",
info: "=WARNING",
err: `unknown severity level "unknown"`,
}}
func (*loggerSuite) TestConfigureLoggers(c *gc.C) {
for i, test := range configureLoggersTests {
c.Logf("test %d: %q", i, test.spec)
loggo.ResetLoggers()
err := loggo.ConfigureLoggers(test.spec)
c.Check(loggo.LoggerInfo(), gc.Equals, test.info)
if test.err != "" {
c.Assert(err, gc.ErrorMatches, test.err)
continue
}
c.Assert(err, gc.IsNil)
// Test that it's idempotent.
err = loggo.ConfigureLoggers(test.spec)
c.Assert(err, gc.IsNil)
c.Assert(loggo.LoggerInfo(), gc.Equals, test.info)
// Test that calling ConfigureLoggers with the
// output of LoggerInfo works too.
err = loggo.ConfigureLoggers(test.info)
c.Assert(err, gc.IsNil)
c.Assert(loggo.LoggerInfo(), gc.Equals, test.info)
}
}
type logwriterSuite struct {
logger loggo.Logger
writer *loggo.TestWriter
}
var _ = gc.Suite(&logwriterSuite{})
func (s *logwriterSuite) SetUpTest(c *gc.C) {
loggo.ResetLoggers()
loggo.RemoveWriter("default")
s.writer = &loggo.TestWriter{}
err := loggo.RegisterWriter("test", s.writer, loggo.TRACE)
c.Assert(err, gc.IsNil)
s.logger = loggo.GetLogger("test.writer")
// Make it so the logger itself writes all messages.
s.logger.SetLogLevel(loggo.TRACE)
}
func (s *logwriterSuite) TearDownTest(c *gc.C) {
loggo.ResetWriters()
}
func (s *logwriterSuite) TestLogDoesntLogWeirdLevels(c *gc.C) {
s.logger.Logf(loggo.UNSPECIFIED, "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
s.logger.Logf(loggo.Level(42), "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
s.logger.Logf(loggo.CRITICAL+loggo.Level(1), "message")
c.Assert(s.writer.Log(), gc.HasLen, 0)
}
func (s *logwriterSuite) TestMessageFormatting(c *gc.C) {
s.logger.Logf(loggo.INFO, "some %s included", "formatting")
log := s.writer.Log()
c.Assert(log, gc.HasLen, 1)
c.Assert(log[0].Message, gc.Equals, "some formatting included")
c.Assert(log[0].Level, gc.Equals, loggo.INFO)
}
func (s *logwriterSuite) BenchmarkLoggingNoWriters(c *gc.C) {
// No writers
loggo.RemoveWriter("test")
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning for %d", i)
}
}
func (s *logwriterSuite) BenchmarkLoggingNoWritersNoFormat(c *gc.C) {
// No writers
loggo.RemoveWriter("test")
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning")
}
}
func (s *logwriterSuite) BenchmarkLoggingTestWriters(c *gc.C) {
for i := 0; i < c.N; i++ {
s.logger.Warningf("just a simple warning for %d", i)
}
c.Assert(s.writer.Log, gc.HasLen, c.N)
}
func setupTempFileWriter(c *gc.C) (logFile *os.File, cleanup func()) {
loggo.RemoveWriter("test")
logFile, err := ioutil.TempFile("", "loggo-test")
c.Assert(err, gc.IsNil)
cleanup = func() {
logFile.Close()
os.Remove(logFile.Name())
}
writer := loggo.NewSimpleWriter(logFile, &loggo.DefaultFormatter{})
err = loggo.RegisterWriter("testfile", writer, loggo.TRACE)
c.Assert(err, gc.IsNil)
return
}
func (s *logwriterSuite) BenchmarkLoggingDiskWriter(c *gc.C) {
logFile, cleanup := setupTempFileWriter(c)
defer cleanup()
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Warningf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert((offset > int64(len(msg))*int64(c.N)), gc.Equals, true,
gc.Commentf("Not enough data was written to the log file."))
}
func (s *logwriterSuite) BenchmarkLoggingDiskWriterNoMessages(c *gc.C) {
logFile, cleanup := setupTempFileWriter(c)
defer cleanup()
// Change the log level
writer, _, err := loggo.RemoveWriter("testfile")
c.Assert(err, gc.IsNil)
loggo.RegisterWriter("testfile", writer, loggo.WARNING)
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Debugf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert(offset, gc.Equals, int64(0),
gc.Commentf("Data was written to the log file."))
}
func (s *logwriterSuite) BenchmarkLoggingDiskWriterNoMessagesLogLevel(c *gc.C) {
logFile, cleanup := setupTempFileWriter(c)
defer cleanup()
// Change the log level
s.logger.SetLogLevel(loggo.WARNING)
msg := "just a simple warning for %d"
for i := 0; i < c.N; i++ {
s.logger.Debugf(msg, i)
}
offset, err := logFile.Seek(0, os.SEEK_CUR)
c.Assert(err, gc.IsNil)
c.Assert(offset, gc.Equals, int64(0),
gc.Commentf("Data was written to the log file."))
}
golang-juju-loggo-0.0~git20150318/package_test.go 0000664 0000000 0000000 00000003260 12473532116 0021414 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"io/ioutil"
"strings"
"testing"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
func Test(t *testing.T) {
gc.TestingT(t)
}
func assertLocation(c *gc.C, msg loggo.TestLogValues, tag string) {
loc := location(tag)
c.Assert(msg.Filename, gc.Equals, loc.file)
c.Assert(msg.Line, gc.Equals, loc.line)
}
// All this location stuff is to avoid having hard coded line numbers
// in the tests. Any line where as a test writer you want to capture the
// file and line number, add a comment that has `//tag name` as the end of
// the line. The name must be unique across all the tests, and the test
// will panic if it is not. This name is then used to read the actual
// file and line numbers.
func location(tag string) Location {
loc, ok := tagToLocation[tag]
if !ok {
panic(fmt.Errorf("tag %q not found", tag))
}
return loc
}
type Location struct {
file string
line int
}
func (loc Location) String() string {
return fmt.Sprintf("%s:%d", loc.file, loc.line)
}
var tagToLocation = make(map[string]Location)
func setLocationsForTags(filename string) {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if j := strings.Index(line, "//tag "); j >= 0 {
tag := line[j+len("//tag "):]
if _, found := tagToLocation[tag]; found {
panic(fmt.Errorf("tag %q already processed previously"))
}
tagToLocation[tag] = Location{file: filename, line: i + 1}
}
}
}
func init() {
setLocationsForTags("logger_test.go")
setLocationsForTags("writer_test.go")
}
golang-juju-loggo-0.0~git20150318/testwriter.go 0000664 0000000 0000000 00000002354 12473532116 0021201 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"path"
"sync"
"time"
)
// TestLogValues represents a single logging call.
type TestLogValues struct {
Level Level
Module string
Filename string
Line int
Timestamp time.Time
Message string
}
// TestWriter is a useful Writer for testing purposes. Each component of the
// logging message is stored in the Log array.
type TestWriter struct {
mu sync.Mutex
log []TestLogValues
}
// Write saves the params as members in the TestLogValues struct appended to the Log array.
func (writer *TestWriter) Write(level Level, module, filename string, line int, timestamp time.Time, message string) {
writer.mu.Lock()
defer writer.mu.Unlock()
writer.log = append(writer.log,
TestLogValues{level, module, path.Base(filename), line, timestamp, message})
}
// Clear removes any saved log messages.
func (writer *TestWriter) Clear() {
writer.mu.Lock()
defer writer.mu.Unlock()
writer.log = nil
}
// Log returns a copy of the current logged values.
func (writer *TestWriter) Log() []TestLogValues {
writer.mu.Lock()
defer writer.mu.Unlock()
v := make([]TestLogValues, len(writer.log))
copy(v, writer.log)
return v
}
golang-juju-loggo-0.0~git20150318/writer.go 0000664 0000000 0000000 00000010544 12473532116 0020301 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"io"
"os"
"sync"
"time"
)
// Writer is implemented by any recipient of log messages.
type Writer interface {
// Write writes a message to the Writer with the given
// level and module name. The filename and line hold
// the file name and line number of the code that is
// generating the log message; the time stamp holds
// the time the log message was generated, and
// message holds the log message itself.
Write(level Level, name, filename string, line int, timestamp time.Time, message string)
}
type registeredWriter struct {
writer Writer
level Level
}
// defaultName is the name of a writer that is registered
// by default that writes to stderr.
const defaultName = "default"
var (
writerMutex sync.Mutex
writers = map[string]*registeredWriter{
defaultName: ®isteredWriter{
writer: NewSimpleWriter(os.Stderr, &DefaultFormatter{}),
level: TRACE,
},
}
globalMinLevel = TRACE
)
// ResetWriters puts the list of writers back into the initial state.
func ResetWriters() {
writerMutex.Lock()
defer writerMutex.Unlock()
writers = map[string]*registeredWriter{
"default": ®isteredWriter{
writer: NewSimpleWriter(os.Stderr, &DefaultFormatter{}),
level: TRACE,
},
}
findMinLevel()
}
// ReplaceDefaultWriter is a convenience method that does the equivalent of
// RemoveWriter and then RegisterWriter with the name "default". The previous
// default writer, if any is returned.
func ReplaceDefaultWriter(writer Writer) (Writer, error) {
if writer == nil {
return nil, fmt.Errorf("Writer cannot be nil")
}
writerMutex.Lock()
defer writerMutex.Unlock()
reg, found := writers[defaultName]
if !found {
return nil, fmt.Errorf("there is no %q writer", defaultName)
}
oldWriter := reg.writer
reg.writer = writer
return oldWriter, nil
}
// RegisterWriter adds the writer to the list of writers that get notified
// when logging. When registering, the caller specifies the minimum logging
// level that will be written, and a name for the writer. If there is already
// a registered writer with that name, an error is returned.
func RegisterWriter(name string, writer Writer, minLevel Level) error {
if writer == nil {
return fmt.Errorf("Writer cannot be nil")
}
writerMutex.Lock()
defer writerMutex.Unlock()
if _, found := writers[name]; found {
return fmt.Errorf("there is already a Writer registered with the name %q", name)
}
writers[name] = ®isteredWriter{writer: writer, level: minLevel}
findMinLevel()
return nil
}
// RemoveWriter removes the Writer identified by 'name' and returns it.
// If the Writer is not found, an error is returned.
func RemoveWriter(name string) (Writer, Level, error) {
writerMutex.Lock()
defer writerMutex.Unlock()
registered, found := writers[name]
if !found {
return nil, UNSPECIFIED, fmt.Errorf("Writer %q is not registered", name)
}
delete(writers, name)
findMinLevel()
return registered.writer, registered.level, nil
}
func findMinLevel() {
// We assume the lock is already held
minLevel := CRITICAL
for _, registered := range writers {
if registered.level < minLevel {
minLevel = registered.level
}
}
globalMinLevel.set(minLevel)
}
// WillWrite returns whether there are any writers registered
// at or above the given severity level. If it returns
// false, a log message at the given level will be discarded.
func WillWrite(level Level) bool {
return level >= globalMinLevel.get()
}
func writeToWriters(level Level, module, filename string, line int, timestamp time.Time, message string) {
writerMutex.Lock()
defer writerMutex.Unlock()
for _, registered := range writers {
if level >= registered.level {
registered.writer.Write(level, module, filename, line, timestamp, message)
}
}
}
type simpleWriter struct {
writer io.Writer
formatter Formatter
}
// NewSimpleWriter returns a new writer that writes
// log messages to the given io.Writer formatting the
// messages with the given formatter.
func NewSimpleWriter(writer io.Writer, formatter Formatter) Writer {
return &simpleWriter{writer, formatter}
}
func (simple *simpleWriter) Write(level Level, module, filename string, line int, timestamp time.Time, message string) {
logLine := simple.formatter.Format(level, module, filename, line, timestamp, message)
fmt.Fprintln(simple.writer, logLine)
}
golang-juju-loggo-0.0~git20150318/writer_test.go 0000664 0000000 0000000 00000016565 12473532116 0021351 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"time"
gc "gopkg.in/check.v1"
"github.com/juju/loggo"
)
type writerBasicsSuite struct{}
var _ = gc.Suite(&writerBasicsSuite{})
func (s *writerBasicsSuite) TearDownTest(c *gc.C) {
loggo.ResetWriters()
}
func (*writerBasicsSuite) TestRemoveDefaultWriter(c *gc.C) {
defaultWriter, level, err := loggo.RemoveWriter("default")
c.Assert(err, gc.IsNil)
c.Assert(level, gc.Equals, loggo.TRACE)
c.Assert(defaultWriter, gc.NotNil)
// Trying again fails.
defaultWriter, level, err = loggo.RemoveWriter("default")
c.Assert(err, gc.ErrorMatches, `Writer "default" is not registered`)
c.Assert(level, gc.Equals, loggo.UNSPECIFIED)
c.Assert(defaultWriter, gc.IsNil)
}
func (*writerBasicsSuite) TestRegisterWriterExistingName(c *gc.C) {
err := loggo.RegisterWriter("default", &loggo.TestWriter{}, loggo.INFO)
c.Assert(err, gc.ErrorMatches, `there is already a Writer registered with the name "default"`)
}
func (*writerBasicsSuite) TestRegisterNilWriter(c *gc.C) {
err := loggo.RegisterWriter("nil", nil, loggo.INFO)
c.Assert(err, gc.ErrorMatches, `Writer cannot be nil`)
}
func (*writerBasicsSuite) TestRegisterWriterTypedNil(c *gc.C) {
// If the interface is a typed nil, we have to trust the user.
var writer *loggo.TestWriter
err := loggo.RegisterWriter("nil", writer, loggo.INFO)
c.Assert(err, gc.IsNil)
}
func (*writerBasicsSuite) TestReplaceDefaultWriter(c *gc.C) {
oldWriter, err := loggo.ReplaceDefaultWriter(&loggo.TestWriter{})
c.Assert(oldWriter, gc.NotNil)
c.Assert(err, gc.IsNil)
}
func (*writerBasicsSuite) TestReplaceDefaultWriterWithNil(c *gc.C) {
oldWriter, err := loggo.ReplaceDefaultWriter(nil)
c.Assert(oldWriter, gc.IsNil)
c.Assert(err, gc.ErrorMatches, "Writer cannot be nil")
}
func (*writerBasicsSuite) TestReplaceDefaultWriterNoDefault(c *gc.C) {
loggo.RemoveWriter("default")
oldWriter, err := loggo.ReplaceDefaultWriter(&loggo.TestWriter{})
c.Assert(oldWriter, gc.IsNil)
c.Assert(err, gc.ErrorMatches, `there is no "default" writer`)
}
func (s *writerBasicsSuite) TestWillWrite(c *gc.C) {
// By default, the root logger watches TRACE messages
c.Assert(loggo.WillWrite(loggo.TRACE), gc.Equals, true)
// Note: ReplaceDefaultWriter doesn't let us change the default log
// level :(
writer, _, err := loggo.RemoveWriter("default")
c.Assert(err, gc.IsNil)
c.Assert(writer, gc.NotNil)
err = loggo.RegisterWriter("default", writer, loggo.CRITICAL)
c.Assert(err, gc.IsNil)
c.Assert(loggo.WillWrite(loggo.TRACE), gc.Equals, false)
c.Assert(loggo.WillWrite(loggo.DEBUG), gc.Equals, false)
c.Assert(loggo.WillWrite(loggo.INFO), gc.Equals, false)
c.Assert(loggo.WillWrite(loggo.WARNING), gc.Equals, false)
c.Assert(loggo.WillWrite(loggo.CRITICAL), gc.Equals, true)
}
type writerSuite struct {
logger loggo.Logger
}
var _ = gc.Suite(&writerSuite{})
func (s *writerSuite) SetUpTest(c *gc.C) {
loggo.ResetLoggers()
loggo.RemoveWriter("default")
s.logger = loggo.GetLogger("test.writer")
// Make it so the logger itself writes all messages.
s.logger.SetLogLevel(loggo.TRACE)
}
func (s *writerSuite) TearDownTest(c *gc.C) {
loggo.ResetWriters()
}
func (s *writerSuite) TearDownSuite(c *gc.C) {
loggo.ResetLoggers()
}
func (s *writerSuite) TestWritingCapturesFileAndLineAndModule(c *gc.C) {
writer := &loggo.TestWriter{}
err := loggo.RegisterWriter("test", writer, loggo.INFO)
c.Assert(err, gc.IsNil)
s.logger.Infof("Info message") //tag capture
log := writer.Log()
c.Assert(log, gc.HasLen, 1)
assertLocation(c, log[0], "capture")
c.Assert(log[0].Module, gc.Equals, "test.writer")
}
func (s *writerSuite) TestWritingLimitWarning(c *gc.C) {
writer := &loggo.TestWriter{}
err := loggo.RegisterWriter("test", writer, loggo.WARNING)
c.Assert(err, gc.IsNil)
start := time.Now()
s.logger.Criticalf("Something critical.")
s.logger.Errorf("An error.")
s.logger.Warningf("A warning message")
s.logger.Infof("Info message")
s.logger.Tracef("Trace the function")
end := time.Now()
log := writer.Log()
c.Assert(log, gc.HasLen, 3)
c.Assert(log[0].Level, gc.Equals, loggo.CRITICAL)
c.Assert(log[0].Message, gc.Equals, "Something critical.")
c.Assert(log[0].Timestamp, Between(start, end))
c.Assert(log[1].Level, gc.Equals, loggo.ERROR)
c.Assert(log[1].Message, gc.Equals, "An error.")
c.Assert(log[1].Timestamp, Between(start, end))
c.Assert(log[2].Level, gc.Equals, loggo.WARNING)
c.Assert(log[2].Message, gc.Equals, "A warning message")
c.Assert(log[2].Timestamp, Between(start, end))
}
func (s *writerSuite) TestWritingLimitTrace(c *gc.C) {
writer := &loggo.TestWriter{}
err := loggo.RegisterWriter("test", writer, loggo.TRACE)
c.Assert(err, gc.IsNil)
start := time.Now()
s.logger.Criticalf("Something critical.")
s.logger.Errorf("An error.")
s.logger.Warningf("A warning message")
s.logger.Infof("Info message")
s.logger.Tracef("Trace the function")
end := time.Now()
log := writer.Log()
c.Assert(log, gc.HasLen, 5)
c.Assert(log[0].Level, gc.Equals, loggo.CRITICAL)
c.Assert(log[0].Message, gc.Equals, "Something critical.")
c.Assert(log[0].Timestamp, Between(start, end))
c.Assert(log[1].Level, gc.Equals, loggo.ERROR)
c.Assert(log[1].Message, gc.Equals, "An error.")
c.Assert(log[1].Timestamp, Between(start, end))
c.Assert(log[2].Level, gc.Equals, loggo.WARNING)
c.Assert(log[2].Message, gc.Equals, "A warning message")
c.Assert(log[2].Timestamp, Between(start, end))
c.Assert(log[3].Level, gc.Equals, loggo.INFO)
c.Assert(log[3].Message, gc.Equals, "Info message")
c.Assert(log[3].Timestamp, Between(start, end))
c.Assert(log[4].Level, gc.Equals, loggo.TRACE)
c.Assert(log[4].Message, gc.Equals, "Trace the function")
c.Assert(log[4].Timestamp, Between(start, end))
}
func (s *writerSuite) TestMultipleWriters(c *gc.C) {
errorWriter := &loggo.TestWriter{}
err := loggo.RegisterWriter("error", errorWriter, loggo.ERROR)
c.Assert(err, gc.IsNil)
warningWriter := &loggo.TestWriter{}
err = loggo.RegisterWriter("warning", warningWriter, loggo.WARNING)
c.Assert(err, gc.IsNil)
infoWriter := &loggo.TestWriter{}
err = loggo.RegisterWriter("info", infoWriter, loggo.INFO)
c.Assert(err, gc.IsNil)
traceWriter := &loggo.TestWriter{}
err = loggo.RegisterWriter("trace", traceWriter, loggo.TRACE)
c.Assert(err, gc.IsNil)
s.logger.Errorf("An error.")
s.logger.Warningf("A warning message")
s.logger.Infof("Info message")
s.logger.Tracef("Trace the function")
c.Assert(errorWriter.Log(), gc.HasLen, 1)
c.Assert(warningWriter.Log(), gc.HasLen, 2)
c.Assert(infoWriter.Log(), gc.HasLen, 3)
c.Assert(traceWriter.Log(), gc.HasLen, 4)
}
func Between(start, end time.Time) gc.Checker {
if end.Before(start) {
return &betweenChecker{end, start}
}
return &betweenChecker{start, end}
}
type betweenChecker struct {
start, end time.Time
}
func (checker *betweenChecker) Info() *gc.CheckerInfo {
info := gc.CheckerInfo{
Name: "Between",
Params: []string{"obtained"},
}
return &info
}
func (checker *betweenChecker) Check(params []interface{}, names []string) (result bool, error string) {
when, ok := params[0].(time.Time)
if !ok {
return false, "obtained value type must be time.Time"
}
if when.Before(checker.start) {
return false, fmt.Sprintf("obtained time %q is before start time %q", when, checker.start)
}
if when.After(checker.end) {
return false, fmt.Sprintf("obtained time %q is after end time %q", when, checker.end)
}
return true, ""
}