pax_global_header 0000666 0000000 0000000 00000000064 14612165663 0014524 g ustar 00root root 0000000 0000000 52 comment=5ffa7574bda902904d6fa06e067f82d2b61d8bc0
loggo-2.1.0/ 0000775 0000000 0000000 00000000000 14612165663 0012633 5 ustar 00root root 0000000 0000000 loggo-2.1.0/.gitignore 0000664 0000000 0000000 00000000020 14612165663 0014613 0 ustar 00root root 0000000 0000000 example/example
loggo-2.1.0/LICENSE 0000664 0000000 0000000 00000021501 14612165663 0013637 0 ustar 00root root 0000000 0000000 All files in this repository are licensed as follows. If you contribute
to this repository, it is assumed that you license your contribution
under the same license unless you state otherwise.
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
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.
loggo-2.1.0/Makefile 0000664 0000000 0000000 00000000404 14612165663 0014271 0 ustar 00root root 0000000 0000000 default: check
check:
go test
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
loggo-2.1.0/README.md 0000664 0000000 0000000 00000037342 14612165663 0014123 0 ustar 00root root 0000000 0000000
# loggo
import "github.com/juju/loggo/v2"
[](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 through their Context. There is a default global context
that is used if you just want simple use. Contexts are used where you may want
different sets of writers for different loggers. Most use cases are fine with
just using the default global context.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
The default global context has 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)
To make loggo produce colored output, you can do the following,
having imported github.com/juju/loggo/loggocolor:
loggo.ReplaceDefaultWriter(loggocolor.NewWriter(os.Stderr))
## Constants
``` go
const DefaultWriterName = "default"
```
DefaultWriterName is the name of the default writer for
a Context.
## Variables
``` go
var TimeFormat = initTimeFormat()
```
TimeFormat is the time format used for the default writer.
This can be set with the environment variable LOGGO_TIME_FORMAT.
## 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 DefaultFormatter
``` go
func DefaultFormatter(entry Entry) string
```
DefaultFormatter 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. For example:
2016-07-02 15:04:05
## 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
ConfigureLoggers. Loggers with UNSPECIFIED level will not
be included.
## func RegisterWriter
``` go
func RegisterWriter(name string, writer Writer) error
```
RegisterWriter adds the writer to the list of writers in the DefaultContext
that get notified when logging. If there is already a registered writer
with that name, an error is returned.
## func ResetLogging
``` go
func ResetLogging()
```
ResetLogging iterates through the known modules and sets the levels of all
to UNSPECIFIED, except for which is set to WARNING. The call also
removes all writers in the DefaultContext and puts the original default
writer back as the only writer.
## func ResetWriters
``` go
func ResetWriters()
```
ResetWriters puts the list of writers back into the initial state.
## type Config
``` go
type Config map[string]Level
```
Config is a mapping of logger module names to logging severity levels.
### func ParseConfigString
``` go
func ParseConfigString(specification string) (Config, error)
```
ParseConfigString 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.
Logging modules 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 (Config) String
``` go
func (c Config) String() string
```
String returns a logger configuration string that may be parsed
using ParseConfigurationString.
## type Context
``` go
type Context struct {
// contains filtered or unexported fields
}
```
Context produces loggers for a hierarchy of modules. The context holds
a collection of hierarchical loggers and their writers.
### func DefaultContext
``` go
func DefaultContext() *Context
```
DefaultContext returns the global default logging context.
### func NewContext
``` go
func NewContext(rootLevel Level) *Context
```
NewLoggers returns a new Context with no writers set.
If the root level is UNSPECIFIED, WARNING is used.
### func (\*Context) AddWriter
``` go
func (c *Context) AddWriter(name string, writer Writer) error
```
AddWriter adds a writer to the list to be called for each logging call.
The name cannot be empty, and the writer cannot be nil. If an existing
writer exists with the specified name, an error is returned.
### func (\*Context) ApplyConfig
``` go
func (c *Context) ApplyConfig(config Config)
```
ApplyConfig configures the logging modules according to the provided config.
### func (\*Context) CompleteConfig
``` go
func (c *Context) CompleteConfig() Config
```
CompleteConfig returns all the loggers and their defined levels,
even if that level is UNSPECIFIED.
### func (\*Context) Config
``` go
func (c *Context) Config() Config
```
Config returns the current configuration of the Loggers. Loggers
with UNSPECIFIED level will not be included.
### func (\*Context) GetLogger
``` go
func (c *Context) GetLogger(name string) Logger
```
GetLogger returns a Logger for the given module name, creating it and
its parents if necessary.
### func (\*Context) RemoveWriter
``` go
func (c *Context) RemoveWriter(name string) (Writer, error)
```
RemoveWriter remotes the specified writer. If a writer is not found with
the specified name an error is returned. The writer that was removed is also
returned.
### func (\*Context) ReplaceWriter
``` go
func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error)
```
ReplaceWriter is a convenience method that does the equivalent of RemoveWriter
followed by AddWriter with the same name. The replaced writer is returned.
### func (\*Context) ResetLoggerLevels
``` go
func (c *Context) ResetLoggerLevels()
```
ResetLoggerLevels iterates through the known logging modules and sets the
levels of all to UNSPECIFIED, except for which is set to WARNING.
### func (\*Context) ResetWriters
``` go
func (c *Context) ResetWriters()
```
ResetWriters is generally only used in testing and removes all the writers.
### func (\*Context) Writer
``` go
func (c *Context) Writer(name string) Writer
```
Writer returns the named writer if one exists.
If there is not a writer with the specified name, nil is returned.
## type Entry
``` go
type Entry struct {
// Level is the severity of the log message.
Level Level
// Module is the dotted module name from the logger.
Module string
// Filename is the full path the file that logged the message.
Filename string
// Line is the line number of the Filename.
Line int
// Timestamp is when the log message was created
Timestamp time.Time
// Message is the formatted string from teh log call.
Message string
}
```
Entry represents a single log message.
## 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) Short
``` go
func (level Level) Short() string
```
Short returns a five character string to use in
aligned logging output.
### func (Level) String
``` go
func (level Level) String() string
```
String implements Stringer.
## 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) Child
``` go
func (logger Logger) Child(name string) Logger
```
Child returns the Logger whose module name is the composed of this
Logger's name and the specified name.
### 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 min 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 min 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) Parent
``` go
func (logger Logger) Parent() Logger
```
Parent returns the Logger whose module name is the same
as this logger without the last period and suffix.
For example the parent of the logger that has the module
"a.b.c" is "a.b".
The Parent of the root logger is still the root logger.
### 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 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() []Entry
```
Log returns a copy of the current logged values.
### func (\*TestWriter) Write
``` go
func (writer *TestWriter) Write(entry Entry)
```
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(entry Entry)
}
```
Writer is implemented by any recipient of log messages.
### func NewMinimumLevelWriter
``` go
func NewMinimumLevelWriter(writer Writer, minLevel Level) Writer
```
NewMinLevelWriter returns a Writer that will only pass on the Write calls
to the provided writer if the log level is at or above the specified
minimum level.
### func NewSimpleWriter
``` go
func NewSimpleWriter(writer io.Writer, formatter func(entry Entry) string) 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, 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)
loggo-2.1.0/benchmarks_test.go 0000664 0000000 0000000 00000005613 14612165663 0016343 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/v2"
)
type BenchmarksSuite struct {
logger loggo.Logger
writer *writer
}
var _ = gc.Suite(&BenchmarksSuite{})
func (s *BenchmarksSuite) SetUpTest(c *gc.C) {
loggo.ResetLogging()
s.logger = loggo.GetLogger("test.writer")
s.writer = &writer{}
err := loggo.RegisterWriter("test", s.writer)
c.Assert(err, gc.IsNil)
}
func (s *BenchmarksSuite) 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 *BenchmarksSuite) 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 *BenchmarksSuite) 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 (s *BenchmarksSuite) BenchmarkLoggingDiskWriter(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
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 *BenchmarksSuite) BenchmarkLoggingDiskWriterNoMessages(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
// Change the log level
writer, err := loggo.RemoveWriter("testfile")
c.Assert(err, gc.IsNil)
err = loggo.RegisterWriter("testfile", loggo.NewMinimumLevelWriter(writer, loggo.WARNING))
c.Assert(err, gc.IsNil)
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 *BenchmarksSuite) BenchmarkLoggingDiskWriterNoMessagesLogLevel(c *gc.C) {
logFile := s.setupTempFileWriter(c)
defer logFile.Close()
// 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."))
}
func (s *BenchmarksSuite) setupTempFileWriter(c *gc.C) *os.File {
_, _ = loggo.RemoveWriter("test")
logFile, err := ioutil.TempFile(c.MkDir(), "loggo-test")
c.Assert(err, gc.IsNil)
writer := loggo.NewSimpleWriter(logFile, loggo.DefaultFormatter)
err = loggo.RegisterWriter("testfile", writer)
c.Assert(err, gc.IsNil)
return logFile
}
loggo-2.1.0/checkers_test.go 0000664 0000000 0000000 00000001753 14612165663 0016016 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"time"
gc "gopkg.in/check.v1"
)
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, ""
}
loggo-2.1.0/config.go 0000664 0000000 0000000 00000006564 14612165663 0014442 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"sort"
"strings"
)
// Config is a mapping of logger module names to logging severity levels.
type Config map[string]Level
// String returns a logger configuration string that may be parsed
// using ParseConfigurationString.
func (c Config) String() string {
if c == nil {
return ""
}
// output in alphabetical order.
var names []string
for name := range c {
names = append(names, name)
}
sort.Strings(names)
var entries []string
for _, name := range names {
level := c[name]
if name == "" {
name = rootString
}
entry := fmt.Sprintf("%s=%s", name, level)
entries = append(entries, entry)
}
return strings.Join(entries, ";")
}
func parseConfigValue(value string) (string, Level, error) {
pair := strings.SplitN(value, "=", 2)
if len(pair) < 2 {
return "", UNSPECIFIED, fmt.Errorf("config value expected '=', found %q", value)
}
name := strings.TrimSpace(pair[0])
if name == "" {
return "", UNSPECIFIED, fmt.Errorf("config value %q has missing module name", value)
}
if tag := extractConfigTag(name); tag != "" {
if strings.Contains(tag, ".") {
// Show the original name and not text potentially extracted config
// tag.
return "", UNSPECIFIED, fmt.Errorf("config tag should not contain '.', found %q", name)
}
// Ensure once the normalised extraction has happened, we put the prefix
// back on, so that we don't loose the fact that the config is a tag.
//
// Ideally we would change Config from map[string]Level to
// map[string]ConfigEntry and then we wouldn't need this step, but that
// causes lots of issues in Juju directly.
name = fmt.Sprintf("#%s", tag)
}
levelStr := strings.TrimSpace(pair[1])
level, ok := ParseLevel(levelStr)
if !ok {
return "", UNSPECIFIED, fmt.Errorf("unknown severity level %q", levelStr)
}
if name == rootString {
name = ""
}
return name, level, nil
}
// ParseConfigString 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.
//
// Logging modules 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`
// `[TAG]=ERROR`
func ParseConfigString(specification string) (Config, error) {
specification = strings.TrimSpace(specification)
if specification == "" {
return nil, nil
}
cfg := make(Config)
if level, ok := ParseLevel(specification); ok {
cfg[""] = level
return cfg, nil
}
values := strings.FieldsFunc(specification, func(r rune) bool { return r == ';' || r == ':' })
for _, value := range values {
name, level, err := parseConfigValue(value)
if err != nil {
return nil, err
}
cfg[name] = level
}
return cfg, nil
}
func extractConfigTag(s string) string {
name := strings.TrimSpace(s)
if len(s) < 2 {
return ""
}
if name[0] == '#' {
return strings.ToLower(name[1:])
}
return ""
}
loggo-2.1.0/config_test.go 0000664 0000000 0000000 00000007677 14612165663 0015507 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import gc "gopkg.in/check.v1"
type ConfigSuite struct{}
var _ = gc.Suite(&ConfigSuite{})
func (*ConfigSuite) TestParseConfigValue(c *gc.C) {
for i, test := range []struct {
value string
module string
level Level
err string
}{{
err: `config value expected '=', found ""`,
}, {
value: "WARNING",
err: `config value expected '=', found "WARNING"`,
}, {
value: "=WARNING",
err: `config value "=WARNING" has missing module name`,
}, {
value: " name = WARNING ",
module: "name",
level: WARNING,
}, {
value: "name = foo",
err: `unknown severity level "foo"`,
}, {
value: "name=DEBUG=INFO",
err: `unknown severity level "DEBUG=INFO"`,
}, {
value: " = info",
module: "",
level: INFO,
}, {
value: "#tag = info",
module: "#tag",
level: INFO,
}, {
value: "#TAG = info",
module: "#tag",
level: INFO,
}, {
value: "#tag.1 = info",
err: `config tag should not contain '.', found "#tag.1"`,
}} {
c.Logf("%d: %s", i, test.value)
module, level, err := parseConfigValue(test.value)
if test.err == "" {
c.Check(err, gc.IsNil)
c.Check(module, gc.Equals, test.module)
c.Check(level, gc.Equals, test.level)
} else {
c.Check(module, gc.Equals, "")
c.Check(level, gc.Equals, UNSPECIFIED)
c.Check(err.Error(), gc.Equals, test.err)
}
}
}
func (*ConfigSuite) TestPaarseConfigurationString(c *gc.C) {
for i, test := range []struct {
configuration string
expected Config
err string
}{{
configuration: "",
// nil Config, no error
}, {
configuration: "INFO",
expected: Config{"": INFO},
}, {
configuration: "=INFO",
err: `config value "=INFO" has missing module name`,
}, {
configuration: "=UNSPECIFIED",
expected: Config{"": UNSPECIFIED},
}, {
configuration: "=DEBUG",
expected: Config{"": DEBUG},
}, {
configuration: "#label=DEBUG",
expected: Config{"#label": DEBUG},
}, {
configuration: "test.module=debug",
expected: Config{"test.module": DEBUG},
}, {
configuration: "module=info; sub.module=debug; other.module=warning",
expected: Config{
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
}, {
// colons not semicolons
configuration: "module=info: sub.module=debug: other.module=warning",
expected: Config{
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
}, {
configuration: " foo.bar \t\r\n= \t\r\nCRITICAL \t\r\n; \t\r\nfoo \r\t\n = DEBUG",
expected: Config{
"foo": DEBUG,
"foo.bar": CRITICAL,
},
}, {
configuration: "foo;bar",
err: `config value expected '=', found "foo"`,
}, {
configuration: "foo=",
err: `unknown severity level ""`,
}, {
configuration: "foo=unknown",
err: `unknown severity level "unknown"`,
}} {
c.Logf("%d: %q", i, test.configuration)
config, err := ParseConfigString(test.configuration)
if test.err == "" {
c.Check(err, gc.IsNil)
c.Check(config, gc.DeepEquals, test.expected)
} else {
c.Check(config, gc.IsNil)
c.Check(err.Error(), gc.Equals, test.err)
}
}
}
func (*ConfigSuite) TestConfigString(c *gc.C) {
for i, test := range []struct {
config Config
expected string
}{{
config: nil,
expected: "",
}, {
config: Config{"": INFO},
expected: "=INFO",
}, {
config: Config{"": UNSPECIFIED},
expected: "=UNSPECIFIED",
}, {
config: Config{"": DEBUG},
expected: "=DEBUG",
}, {
config: Config{"test.module": DEBUG},
expected: "test.module=DEBUG",
}, {
config: Config{
"": WARNING,
"module": INFO,
"sub.module": DEBUG,
"other.module": WARNING,
},
expected: "=WARNING;module=INFO;other.module=WARNING;sub.module=DEBUG",
}} {
c.Logf("%d: %q", i, test.expected)
c.Check(test.config.String(), gc.Equals, test.expected)
}
}
loggo-2.1.0/context.go 0000664 0000000 0000000 00000021147 14612165663 0014653 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"sort"
"strings"
"sync"
)
// Context produces loggers for a hierarchy of modules. The context holds
// a collection of hierarchical loggers and their writers.
type Context struct {
root *module
// Perhaps have one mutex?
// All `modules` variables are managed by the one mutex.
modulesMutex sync.Mutex
modules map[string]*module
modulesTagConfig map[string]Level
writersMutex sync.Mutex
writers map[string]Writer
// writeMuxtex is used to serialise write operations.
writeMutex sync.Mutex
}
// NewContext returns a new Context with no writers set.
// If the root level is UNSPECIFIED, WARNING is used.
func NewContext(rootLevel Level) *Context {
if rootLevel < TRACE || rootLevel > CRITICAL {
rootLevel = WARNING
}
context := &Context{
modules: make(map[string]*module),
modulesTagConfig: make(map[string]Level),
writers: make(map[string]Writer),
}
context.root = &module{
level: rootLevel,
context: context,
}
context.root.parent = context.root
context.modules[""] = context.root
return context
}
// GetLogger returns a Logger for the given module name, creating it and
// its parents if necessary.
func (c *Context) GetLogger(name string, tags ...string) Logger {
name = strings.TrimSpace(strings.ToLower(name))
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
return Logger{
impl: c.getLoggerModule(name, tags),
}
}
// GetAllLoggerTags returns all the logger tags for a given context. The
// names are unique and sorted before returned, to improve consistency.
func (c *Context) GetAllLoggerTags() []string {
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
names := make(map[string]struct{})
for _, module := range c.modules {
for k, v := range module.tagsLookup {
names[k] = v
}
}
labels := make([]string, 0, len(names))
for name := range names {
labels = append(labels, name)
}
sort.Strings(labels)
return labels
}
func (c *Context) getLoggerModule(name string, tags []string) *module {
if name == rootString {
name = ""
}
impl, found := c.modules[name]
if found {
return impl
}
var parentName string
if i := strings.LastIndex(name, "."); i >= 0 {
parentName = name[0:i]
}
// Labels don't apply to the parent, otherwise would have all labels.
// Selection of the tag would give you all loggers again, which isn't what
// you want.
parent := c.getLoggerModule(parentName, nil)
// Ensure that we create a new logger module for the name, that includes the
// tag.
level := UNSPECIFIED
labelMap := make(map[string]struct{})
for _, tag := range tags {
labelMap[tag] = struct{}{}
// First tag wins when setting the logger tag from the config tag
// level cache. If there are no tag configs, then fallback to
// UNSPECIFIED and inherit the level correctly.
if configLevel, ok := c.modulesTagConfig[tag]; ok && level == UNSPECIFIED {
level = configLevel
}
}
// As it's not possible to modify the parent's labels, it's safe to copy
// them at the time of creation. Otherwise we have to walk the parent chain
// to get the full set of labels for every log message.
labels := make(Labels)
for k, v := range parent.labels {
labels[k] = v
}
impl = &module{
name: name,
level: level,
parent: parent,
context: c,
tags: tags,
tagsLookup: labelMap,
labels: parent.labels,
}
c.modules[name] = impl
return impl
}
// getLoggerModulesByTag returns modules that have the associated tag.
func (c *Context) getLoggerModulesByTag(label string) []*module {
var modules []*module
for _, mod := range c.modules {
if len(mod.tags) == 0 {
continue
}
if _, ok := mod.tagsLookup[label]; ok {
modules = append(modules, mod)
}
}
return modules
}
// Config returns the current configuration of the Loggers. Loggers
// with UNSPECIFIED level will not be included.
func (c *Context) Config() Config {
result := make(Config)
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, module := range c.modules {
if module.level != UNSPECIFIED {
result[name] = module.level
}
}
return result
}
// CompleteConfig returns all the loggers and their defined levels,
// even if that level is UNSPECIFIED.
func (c *Context) CompleteConfig() Config {
result := make(Config)
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, module := range c.modules {
result[name] = module.level
}
return result
}
// ApplyConfig configures the logging modules according to the provided config.
func (c *Context) ApplyConfig(config Config) {
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
for name, level := range config {
tag := extractConfigTag(name)
if tag == "" {
module := c.getLoggerModule(name, nil)
module.setLevel(level)
continue
}
// Ensure that we save the config for lazy loggers to pick up correctly.
c.modulesTagConfig[tag] = level
// Config contains a named tag, use that for selecting the loggers.
modules := c.getLoggerModulesByTag(tag)
for _, module := range modules {
module.setLevel(level)
}
}
}
// ResetLoggerLevels iterates through the known logging modules and sets the
// levels of all to UNSPECIFIED, except for which is set to WARNING.
func (c *Context) ResetLoggerLevels() {
c.modulesMutex.Lock()
defer c.modulesMutex.Unlock()
// Setting the root module to UNSPECIFIED will set it to WARNING.
for _, module := range c.modules {
module.setLevel(UNSPECIFIED)
}
// We can safely just wipe everything here.
c.modulesTagConfig = make(map[string]Level)
}
func (c *Context) write(entry Entry) {
c.writeMutex.Lock()
defer c.writeMutex.Unlock()
for _, writer := range c.getWriters() {
writer.Write(entry)
}
}
func (c *Context) getWriters() []Writer {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
var result []Writer
for _, writer := range c.writers {
result = append(result, writer)
}
return result
}
// AddWriter adds a writer to the list to be called for each logging call.
// The name cannot be empty, and the writer cannot be nil. If an existing
// writer exists with the specified name, an error is returned.
func (c *Context) AddWriter(name string, writer Writer) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
if writer == nil {
return fmt.Errorf("writer cannot be nil")
}
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
if _, found := c.writers[name]; found {
return fmt.Errorf("context already has a writer named %q", name)
}
c.writers[name] = writer
return nil
}
// Writer returns the named writer if one exists.
// If there is not a writer with the specified name, nil is returned.
func (c *Context) Writer(name string) Writer {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
return c.writers[name]
}
// RemoveWriter remotes the specified writer. If a writer is not found with
// the specified name an error is returned. The writer that was removed is also
// returned.
func (c *Context) RemoveWriter(name string) (Writer, error) {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
reg, found := c.writers[name]
if !found {
return nil, fmt.Errorf("context has no writer named %q", name)
}
delete(c.writers, name)
return reg, nil
}
// ReplaceWriter is a convenience method that does the equivalent of RemoveWriter
// followed by AddWriter with the same name. The replaced writer is returned.
func (c *Context) ReplaceWriter(name string, writer Writer) (Writer, error) {
if name == "" {
return nil, fmt.Errorf("name cannot be empty")
}
if writer == nil {
return nil, fmt.Errorf("writer cannot be nil")
}
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
reg, found := c.writers[name]
if !found {
return nil, fmt.Errorf("context has no writer named %q", name)
}
oldWriter := reg
c.writers[name] = writer
return oldWriter, nil
}
// ResetWriters is generally only used in testing and removes all the writers.
func (c *Context) ResetWriters() {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
c.writers = make(map[string]Writer)
}
// 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 (c *Context) ConfigureLoggers(specification string) error {
config, err := ParseConfigString(specification)
if err != nil {
return err
}
c.ApplyConfig(config)
return nil
}
loggo-2.1.0/context_test.go 0000664 0000000 0000000 00000035455 14612165663 0015721 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"github.com/juju/loggo/v2"
gc "gopkg.in/check.v1"
)
type ContextSuite struct{}
var _ = gc.Suite(&ContextSuite{})
func (*ContextSuite) TestNewContextRootLevel(c *gc.C) {
for i, test := range []struct {
level loggo.Level
expected loggo.Level
}{{
level: loggo.UNSPECIFIED,
expected: loggo.WARNING,
}, {
level: loggo.DEBUG,
expected: loggo.DEBUG,
}, {
level: loggo.INFO,
expected: loggo.INFO,
}, {
level: loggo.WARNING,
expected: loggo.WARNING,
}, {
level: loggo.ERROR,
expected: loggo.ERROR,
}, {
level: loggo.CRITICAL,
expected: loggo.CRITICAL,
}, {
level: loggo.Level(42),
expected: loggo.WARNING,
}} {
c.Logf("%d: %s", i, test.level)
context := loggo.NewContext(test.level)
cfg := context.Config()
c.Check(cfg, gc.HasLen, 1)
value, found := cfg[""]
c.Check(found, gc.Equals, true)
c.Check(value, gc.Equals, test.expected)
}
}
func logAllSeverities(logger loggo.Logger) {
logger.Criticalf("something critical")
logger.Errorf("an error")
logger.Warningf("a warning message")
logger.Infof("an info message")
logger.Debugf("a debug message")
logger.Tracef("a trace message")
}
func checkLogEntry(c *gc.C, entry, expected loggo.Entry) {
c.Check(entry.Level, gc.Equals, expected.Level)
c.Check(entry.Module, gc.Equals, expected.Module)
c.Check(entry.Message, gc.Equals, expected.Message)
}
func checkLogEntries(c *gc.C, obtained, expected []loggo.Entry) {
if c.Check(len(obtained), gc.Equals, len(expected)) {
for i := range obtained {
checkLogEntry(c, obtained[i], expected[i])
}
}
}
func (*ContextSuite) TestGetLoggerRoot(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
blank := context.GetLogger("")
root := context.GetLogger("")
c.Assert(blank, gc.DeepEquals, root)
}
func (*ContextSuite) TestGetLoggerCase(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
upper := context.GetLogger("TEST")
lower := context.GetLogger("test")
c.Assert(upper, gc.DeepEquals, lower)
c.Assert(upper.Name(), gc.Equals, "test")
}
func (*ContextSuite) TestGetLoggerSpace(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
space := context.GetLogger(" test ")
lower := context.GetLogger("test")
c.Assert(space, gc.DeepEquals, lower)
c.Assert(space.Name(), gc.Equals, "test")
}
func (*ContextSuite) TestNewContextNoWriter(c *gc.C) {
// Should be no output.
context := loggo.NewContext(loggo.DEBUG)
logger := context.GetLogger("test")
logAllSeverities(logger)
}
func (*ContextSuite) newContextWithTestWriter(c *gc.C, level loggo.Level) (*loggo.Context, *loggo.TestWriter) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(level)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
return context, writer
}
func (s *ContextSuite) TestNewContextRootSeverityWarning(c *gc.C) {
context, writer := s.newContextWithTestWriter(c, loggo.WARNING)
logger := context.GetLogger("test")
logAllSeverities(logger)
checkLogEntries(c, writer.Log(), []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
})
}
func (s *ContextSuite) TestNewContextRootSeverityTrace(c *gc.C) {
context, writer := s.newContextWithTestWriter(c, loggo.TRACE)
logger := context.GetLogger("test")
logAllSeverities(logger)
checkLogEntries(c, writer.Log(), []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
{Level: loggo.INFO, Module: "test", Message: "an info message"},
{Level: loggo.DEBUG, Module: "test", Message: "a debug message"},
{Level: loggo.TRACE, Module: "test", Message: "a trace message"},
})
}
func (*ContextSuite) TestNewContextConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
config := context.Config()
c.Assert(config, gc.DeepEquals, loggo.Config{"": loggo.DEBUG})
}
func (*ContextSuite) TestNewLoggerAddsConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
_ = context.GetLogger("test.module")
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{
"": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals, loggo.Config{
"": loggo.DEBUG,
"test": loggo.UNSPECIFIED,
"test.module": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestConfigureLoggers(c *gc.C) {
context := loggo.NewContext(loggo.INFO)
err := context.ConfigureLoggers("testing.module=debug")
c.Assert(err, gc.IsNil)
expected := "=INFO;testing.module=DEBUG"
c.Assert(context.Config().String(), gc.Equals, expected)
}
func (*ContextSuite) TestApplyNilConfig(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(nil)
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.DEBUG})
}
func (*ContextSuite) TestApplyConfigRootUnspecified(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(loggo.Config{"": loggo.UNSPECIFIED})
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.WARNING})
}
func (*ContextSuite) TestApplyConfigRootTrace(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"": loggo.TRACE})
c.Assert(context.Config(), gc.DeepEquals, loggo.Config{"": loggo.TRACE})
}
func (*ContextSuite) TestApplyConfigCreatesModules(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first.second": loggo.TRACE,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.TRACE,
})
}
func (*ContextSuite) TestApplyConfigAdditive(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
context.ApplyConfig(loggo.Config{"other.module": loggo.DEBUG})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first.second": loggo.TRACE,
"other.module": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.TRACE,
"other": loggo.UNSPECIFIED,
"other.module": loggo.DEBUG,
})
}
func (*ContextSuite) TestGetAllLoggerTags(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.GetLogger("a.b", "one")
context.GetLogger("c.d", "one")
context.GetLogger("e", "two")
labels := context.GetAllLoggerTags()
c.Assert(labels, gc.DeepEquals, []string{"one", "two"})
}
func (*ContextSuite) TestGetAllLoggerTagsWithApplyConfig(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
labels := context.GetAllLoggerTags()
c.Assert(labels, gc.DeepEquals, []string{})
}
func (*ContextSuite) TestApplyConfigTags(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.GetLogger("a.b", "one")
context.GetLogger("c.d", "one")
context.GetLogger("e", "two")
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
context.ApplyConfig(loggo.Config{"#two": loggo.DEBUG})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a.b": loggo.TRACE,
"c.d": loggo.TRACE,
"e": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a": loggo.UNSPECIFIED,
"a.b": loggo.TRACE,
"c": loggo.UNSPECIFIED,
"c.d": loggo.TRACE,
"e": loggo.DEBUG,
})
}
func (*ContextSuite) TestApplyConfigLabelsAppliesToNewLoggers(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
context.ApplyConfig(loggo.Config{"#two": loggo.DEBUG})
context.GetLogger("a.b", "one")
context.GetLogger("c.d", "one")
context.GetLogger("e", "two")
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a.b": loggo.TRACE,
"c.d": loggo.TRACE,
"e": loggo.DEBUG,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a": loggo.UNSPECIFIED,
"a.b": loggo.TRACE,
"c": loggo.UNSPECIFIED,
"c.d": loggo.TRACE,
"e": loggo.DEBUG,
})
}
func (*ContextSuite) TestApplyConfigLabelsAppliesToNewLoggersWithMultipleTags(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
// Invert the order here, to ensure that the config order doesn't matter,
// but the way the tags are ordered in `GetLogger`.
context.ApplyConfig(loggo.Config{"#two": loggo.DEBUG})
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
context.GetLogger("a.b", "one", "two")
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a.b": loggo.TRACE,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a": loggo.UNSPECIFIED,
"a.b": loggo.TRACE,
})
}
func (*ContextSuite) TestApplyConfigLabelsResetLoggerLevels(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
context.ApplyConfig(loggo.Config{"#two": loggo.DEBUG})
context.GetLogger("a.b", "one")
context.GetLogger("c.d", "one")
context.GetLogger("e", "two")
context.ResetLoggerLevels()
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a": loggo.UNSPECIFIED,
"a.b": loggo.UNSPECIFIED,
"c": loggo.UNSPECIFIED,
"c.d": loggo.UNSPECIFIED,
"e": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestApplyConfigTagsAddative(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.ApplyConfig(loggo.Config{"#one": loggo.TRACE})
context.ApplyConfig(loggo.Config{"#two": loggo.DEBUG})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
}
func (*ContextSuite) TestApplyConfigWithMalformedTag(c *gc.C) {
context := loggo.NewContext(loggo.WARNING)
context.GetLogger("a.b", "one")
context.ApplyConfig(loggo.Config{"#ONE.1": loggo.TRACE})
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"a": loggo.UNSPECIFIED,
"a.b": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestResetLoggerTags(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
context.ApplyConfig(loggo.Config{"first.second": loggo.TRACE})
context.ResetLoggerLevels()
c.Assert(context.Config(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
})
c.Assert(context.CompleteConfig(), gc.DeepEquals,
loggo.Config{
"": loggo.WARNING,
"first": loggo.UNSPECIFIED,
"first.second": loggo.UNSPECIFIED,
})
}
func (*ContextSuite) TestWriterNamesNone(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
writers := context.WriterNames()
c.Assert(writers, gc.HasLen, 0)
}
func (*ContextSuite) TestAddWriterNoName(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("", nil)
c.Assert(err.Error(), gc.Equals, "name cannot be empty")
}
func (*ContextSuite) TestAddWriterNil(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("foo", nil)
c.Assert(err.Error(), gc.Equals, "writer cannot be nil")
}
func (*ContextSuite) TestNamedAddWriter(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
err := context.AddWriter("foo", &writer{name: "foo"})
c.Assert(err, gc.IsNil)
err = context.AddWriter("foo", &writer{name: "foo"})
c.Assert(err.Error(), gc.Equals, `context already has a writer named "foo"`)
writers := context.WriterNames()
c.Assert(writers, gc.DeepEquals, []string{"foo"})
}
func (*ContextSuite) TestRemoveWriter(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
w, err := context.RemoveWriter("unknown")
c.Assert(err.Error(), gc.Equals, `context has no writer named "unknown"`)
c.Assert(w, gc.IsNil)
}
func (*ContextSuite) TestRemoveWriterFound(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
original := &writer{name: "foo"}
err := context.AddWriter("foo", original)
c.Assert(err, gc.IsNil)
existing, err := context.RemoveWriter("foo")
c.Assert(err, gc.IsNil)
c.Assert(existing, gc.Equals, original)
writers := context.WriterNames()
c.Assert(writers, gc.HasLen, 0)
}
func (*ContextSuite) TestReplaceWriterNoName(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("", nil)
c.Assert(err.Error(), gc.Equals, "name cannot be empty")
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestReplaceWriterNil(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("foo", nil)
c.Assert(err.Error(), gc.Equals, "writer cannot be nil")
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestReplaceWriterNotFound(c *gc.C) {
context := loggo.NewContext(loggo.DEBUG)
existing, err := context.ReplaceWriter("foo", &writer{})
c.Assert(err.Error(), gc.Equals, `context has no writer named "foo"`)
c.Assert(existing, gc.IsNil)
}
func (*ContextSuite) TestMultipleWriters(c *gc.C) {
first := &writer{}
second := &writer{}
third := &writer{}
context := loggo.NewContext(loggo.TRACE)
err := context.AddWriter("first", first)
c.Assert(err, gc.IsNil)
err = context.AddWriter("second", second)
c.Assert(err, gc.IsNil)
err = context.AddWriter("third", third)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("test")
logAllSeverities(logger)
expected := []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical"},
{Level: loggo.ERROR, Module: "test", Message: "an error"},
{Level: loggo.WARNING, Module: "test", Message: "a warning message"},
{Level: loggo.INFO, Module: "test", Message: "an info message"},
{Level: loggo.DEBUG, Module: "test", Message: "a debug message"},
{Level: loggo.TRACE, Module: "test", Message: "a trace message"},
}
checkLogEntries(c, first.Log(), expected)
checkLogEntries(c, second.Log(), expected)
checkLogEntries(c, third.Log(), expected)
}
func (*ContextSuite) TestWriter(c *gc.C) {
first := &writer{name: "first"}
second := &writer{name: "second"}
context := loggo.NewContext(loggo.TRACE)
err := context.AddWriter("first", first)
c.Assert(err, gc.IsNil)
err = context.AddWriter("second", second)
c.Assert(err, gc.IsNil)
c.Check(context.Writer("unknown"), gc.IsNil)
c.Check(context.Writer("first"), gc.Equals, first)
c.Check(context.Writer("second"), gc.Equals, second)
c.Check(first, gc.Not(gc.Equals), second)
}
type writer struct {
loggo.TestWriter
// The name exists to discriminate writer equality.
name string
}
loggo-2.1.0/doc.go 0000664 0000000 0000000 00000003527 14612165663 0013736 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 through their Context. There is a default global context
that is used if you just want simple use. Contexts are used where you may want
different sets of writers for different loggers. Most use cases are fine with
just using the default global context.
Loggers are created using the GetLogger function.
logger := loggo.GetLogger("foo.bar")
The default global context has 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)
To make loggo produce colored output, you can do the following,
having imported github.com/juju/loggo/loggocolor:
loggo.ReplaceDefaultWriter(loggocolor.NewWriter(os.Stderr))
*/
package loggo
loggo-2.1.0/entry.go 0000664 0000000 0000000 00000001234 14612165663 0014323 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import "time"
// Entry represents a single log message.
type Entry struct {
// Level is the severity of the log message.
Level Level
// Module is the dotted module name from the logger.
Module string
// Filename is the full path the file that logged the message.
Filename string
// Line is the line number of the Filename.
Line int
// Timestamp is when the log message was created
Timestamp time.Time
// Message is the formatted string from teh log call.
Message string
// Labels are the labels associated with the log message.
Labels Labels
}
loggo-2.1.0/example/ 0000775 0000000 0000000 00000000000 14612165663 0014266 5 ustar 00root root 0000000 0000000 loggo-2.1.0/example/first.go 0000664 0000000 0000000 00000000713 14612165663 0015745 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/juju/loggo/v2"
)
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 FirstDebug(message string) {
first.Debugf(message)
}
func FirstTrace(message string) {
first.Tracef(message)
}
loggo-2.1.0/example/main.go 0000664 0000000 0000000 00000001524 14612165663 0015543 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"log"
"os"
"github.com/juju/loggo/v2"
)
var rootLogger = loggo.GetLogger("")
func main() {
args := os.Args
if len(args) > 1 {
if err := loggo.ConfigureLoggers(args[1]); err != nil {
log.Fatal(err)
}
} 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")
FirstDebug("first debug")
FirstTrace("first trace")
SecondCritical("second critical")
SecondError("second error")
SecondWarning("second warning")
SecondInfo("second info")
SecondDebug("second debug")
SecondTrace("second trace")
}
loggo-2.1.0/example/second.go 0000664 0000000 0000000 00000000730 14612165663 0016070 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/juju/loggo/v2"
)
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 SecondDebug(message string) {
second.Debugf(message)
}
func SecondTrace(message string) {
second.Tracef(message)
}
loggo-2.1.0/export_test.go 0000664 0000000 0000000 00000000772 14612165663 0015550 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
// WriterNames returns the names of the context's writers for testing purposes.
func (c *Context) WriterNames() []string {
c.writersMutex.Lock()
defer c.writersMutex.Unlock()
var result []string
for name := range c.writers {
result = append(result, name)
}
return result
}
func ResetDefaultContext() {
ResetLogging()
_ = DefaultContext().AddWriter(DefaultWriterName, defaultWriter())
}
loggo-2.1.0/formatter.go 0000664 0000000 0000000 00000001732 14612165663 0015170 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"os"
"path/filepath"
"time"
)
// DefaultFormatter 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. For example:
// 2016-07-02 15:04:05
func DefaultFormatter(entry Entry) string {
ts := entry.Timestamp.In(time.UTC).Format("2006-01-02 15:04:05")
// Just get the basename from the filename
filename := filepath.Base(entry.Filename)
return fmt.Sprintf("%s %s %s %s:%d %s", ts, entry.Level, entry.Module, filename, entry.Line, entry.Message)
}
// TimeFormat is the time format used for the default writer.
// This can be set with the environment variable LOGGO_TIME_FORMAT.
var TimeFormat = initTimeFormat()
func initTimeFormat() string {
format := os.Getenv("LOGGO_TIME_FORMAT")
if format != "" {
return format
}
return "15:04:05"
}
loggo-2.1.0/formatter_test.go 0000664 0000000 0000000 00000001404 14612165663 0016223 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/v2"
)
type formatterSuite struct{}
var _ = gc.Suite(&formatterSuite{})
func (*formatterSuite) TestDefaultFormat(c *gc.C) {
location, err := time.LoadLocation("UTC")
testTime := time.Date(2013, 5, 3, 10, 53, 24, 123456, location)
c.Assert(err, gc.IsNil)
entry := loggo.Entry{
Level: loggo.WARNING,
Module: "test.module",
Filename: "some/deep/filename",
Line: 42,
Timestamp: testTime,
Message: "hello world!",
}
formatted := loggo.DefaultFormatter(entry)
c.Assert(formatted, gc.Equals, "2013-05-03 10:53:24 WARNING test.module filename:42 hello world!")
}
loggo-2.1.0/global.go 0000664 0000000 0000000 00000006005 14612165663 0014423 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
var (
defaultContext = newDefaultContxt()
)
func newDefaultContxt() *Context {
ctx := NewContext(WARNING)
if err := ctx.AddWriter(DefaultWriterName, defaultWriter()); err != nil {
panic(err)
}
return ctx
}
// DefaultContext returns the global default logging context.
func DefaultContext() *Context {
return defaultContext
}
// LoggerInfo returns information about the configured loggers and their
// logging levels. The information is returned in the format expected by
// ConfigureLoggers. Loggers with UNSPECIFIED level will not
// be included.
func LoggerInfo() string {
return defaultContext.Config().String()
}
// GetLogger returns a Logger for the given module name,
// creating it and its parents if necessary.
func GetLogger(name string) Logger {
return defaultContext.GetLogger(name)
}
// GetLoggerWithTags returns a Logger for the given module name with the correct
// associated tags, creating it and its parents if necessary.
func GetLoggerWithTags(name string, tags ...string) Logger {
return defaultContext.GetLogger(name, tags...)
}
// ResetLogging iterates through the known modules and sets the levels of all
// to UNSPECIFIED, except for which is set to WARNING. The call also
// removes all writers in the DefaultContext and puts the original default
// writer back as the only writer.
func ResetLogging() {
defaultContext.ResetLoggerLevels()
defaultContext.ResetWriters()
}
// ResetWriters puts the list of writers back into the initial state.
func ResetWriters() {
defaultContext.ResetWriters()
}
// 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) {
return defaultContext.ReplaceWriter(DefaultWriterName, writer)
}
// RegisterWriter adds the writer to the list of writers in the DefaultContext
// that get notified when logging. If there is already a registered writer
// with that name, an error is returned.
func RegisterWriter(name string, writer Writer) error {
return defaultContext.AddWriter(name, writer)
}
// 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, error) {
return defaultContext.RemoveWriter(name)
}
// ConfigureLoggers configures loggers on the default context 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 {
return defaultContext.ConfigureLoggers(specification)
}
loggo-2.1.0/global_test.go 0000664 0000000 0000000 00000005206 14612165663 0015464 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/loggo/v2"
)
type GlobalSuite struct{}
var _ = gc.Suite(&GlobalSuite{})
func (*GlobalSuite) SetUpTest(c *gc.C) {
loggo.ResetDefaultContext()
}
func (*GlobalSuite) TestRootLogger(c *gc.C) {
var root loggo.Logger
got := loggo.GetLogger("")
c.Check(got.Name(), gc.Equals, root.Name())
c.Check(got.LogLevel(), gc.Equals, root.LogLevel())
}
func (*GlobalSuite) TestModuleName(c *gc.C) {
logger := loggo.GetLogger("loggo.testing")
c.Check(logger.Name(), gc.Equals, "loggo.testing")
}
func (*GlobalSuite) TestLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
level := logger.LogLevel()
c.Check(level, gc.Equals, loggo.UNSPECIFIED)
}
func (*GlobalSuite) TestEffectiveLevel(c *gc.C) {
logger := loggo.GetLogger("testing")
level := logger.EffectiveLogLevel()
c.Check(level, gc.Equals, loggo.WARNING)
}
func (*GlobalSuite) 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 (*GlobalSuite) 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 (s *GlobalSuite) TestConfigureLoggers(c *gc.C) {
err := loggo.ConfigureLoggers("testing.module=debug")
c.Assert(err, gc.IsNil)
expected := "=WARNING;testing.module=DEBUG"
c.Assert(loggo.DefaultContext().Config().String(), gc.Equals, expected)
c.Assert(loggo.LoggerInfo(), gc.Equals, expected)
}
func (*GlobalSuite) TestRegisterWriterExistingName(c *gc.C) {
err := loggo.RegisterWriter("default", &writer{})
c.Assert(err, gc.ErrorMatches, `context already has a writer named "default"`)
}
func (*GlobalSuite) TestReplaceDefaultWriter(c *gc.C) {
oldWriter, err := loggo.ReplaceDefaultWriter(&writer{})
c.Assert(oldWriter, gc.NotNil)
c.Assert(err, gc.IsNil)
c.Assert(loggo.DefaultContext().WriterNames(), gc.DeepEquals, []string{"default"})
}
func (*GlobalSuite) TestRemoveWriter(c *gc.C) {
oldWriter, err := loggo.RemoveWriter("default")
c.Assert(oldWriter, gc.NotNil)
c.Assert(err, gc.IsNil)
c.Assert(loggo.DefaultContext().WriterNames(), gc.HasLen, 0)
}
func (s *GlobalSuite) TestGetLoggerWithTags(c *gc.C) {
logger := loggo.GetLoggerWithTags("parent", "labela", "labelb")
c.Check(logger.Tags(), gc.DeepEquals, []string{"labela", "labelb"})
}
loggo-2.1.0/go.mod 0000664 0000000 0000000 00000000647 14612165663 0013750 0 ustar 00root root 0000000 0000000 module github.com/juju/loggo/v2
go 1.21
require (
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2
)
require (
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6 // indirect
github.com/mattn/go-colorable v0.0.6 // indirect
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c // indirect
golang.org/x/sys v0.16.0 // indirect
)
loggo-2.1.0/go.sum 0000664 0000000 0000000 00000002306 14612165663 0013767 0 ustar 00root root 0000000 0000000 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6 h1:yjdywwaxd8vTEXuA4EdgUBkiCQEQG7YAY3k9S1PaZKg=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mattn/go-colorable v0.0.6 h1:jGqlOoCjqVR4hfTO9H1qrR2xi0xZNYmX2T1xlw7P79c=
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
loggo-2.1.0/labels.go 0000664 0000000 0000000 00000000521 14612165663 0014422 0 ustar 00root root 0000000 0000000 // Copyright 2024 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
const (
// LoggerTags is the name of the label used to record the
// logger tags for a log entry.
LoggerTags = "logger-tags"
)
// Labels represents key values which are assigned to a log entry.
type Labels map[string]string
loggo-2.1.0/level.go 0000664 0000000 0000000 00000003603 14612165663 0014273 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"strings"
"sync/atomic"
)
// The severity levels. Higher values are more considered more
// important.
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
)
// Level holds a severity level.
type Level uint32
// 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
default:
return UNSPECIFIED, false
}
}
// String implements Stringer.
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"
default:
return ""
}
}
// Short returns a five character string to use in
// aligned logging output.
func (level Level) Short() string {
switch level {
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO "
case WARNING:
return "WARN "
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITC"
default:
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))
}
loggo-2.1.0/level_test.go 0000664 0000000 0000000 00000003303 14612165663 0015327 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/loggo/v2"
)
type LevelSuite struct{}
var _ = gc.Suite(&LevelSuite{})
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 (s *LevelSuite) 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 (s *LevelSuite) TestLevelStringValue(c *gc.C) {
for level, str := range levelStringValueTests {
c.Assert(level.String(), gc.Equals, str)
}
}
loggo-2.1.0/logger.go 0000664 0000000 0000000 00000022712 14612165663 0014445 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"fmt"
"runtime"
"strings"
"time"
)
// 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
labels Labels
}
// WithLabels returns a logger whose module is the same as this logger and
// the returned logger will add the specified labels to each log entry.
// WithLabels only target a specific logger with labels. Children of the logger
// will not inherit the labels.
// To add labels to all child loggers, use ChildWithLabels.
func (logger Logger) WithLabels(labels Labels) Logger {
if len(labels) == 0 {
return logger
}
result := logger
result.labels = make(Labels)
for k, v := range labels {
result.labels[k] = v
}
return result
}
func (logger Logger) getModule() *module {
if logger.impl == nil {
return defaultContext.root
}
return logger.impl
}
// Root returns the root logger for the Logger's context.
func (logger Logger) Root() Logger {
module := logger.getModule()
return module.context.GetLogger("")
}
// Parent returns the Logger whose module name is the same
// as this logger without the last period and suffix.
// For example the parent of the logger that has the module
// "a.b.c" is "a.b".
// The Parent of the root logger is still the root logger.
func (logger Logger) Parent() Logger {
return Logger{impl: logger.getModule().parent}
}
// Child returns the Logger whose module name is the composed of this
// Logger's name and the specified name.
func (logger Logger) Child(name string) Logger {
module := logger.getModule()
path := module.name
if path == "" {
path = name
} else {
path += "." + name
}
return module.context.GetLogger(path)
}
// ChildWithTags returns the Logger whose module name is the composed of this
// Logger's name and the specified name with the correct associated tags.
func (logger Logger) ChildWithTags(name string, tags ...string) Logger {
module := logger.getModule()
path := module.name
if path == "" {
path = name
} else {
path += "." + name
}
return module.context.GetLogger(path, tags...)
}
// ChildWithLabels returns the Logger whose module name is the composed of this
// Logger's name and the specified name with the correct associated labels.
// Adding labels to the child logger will cause all child loggers to also
// inherit the labels of the parent(s) loggers.
// For targeting a singular logger with labels, use WithLabels which are not
// inherited by child loggers.
func (logger Logger) ChildWithLabels(name string, labels Labels) Logger {
module := logger.getModule()
path := module.name
if path == "" {
path = name
} else {
path += "." + name
}
merged := make(Labels)
for k, v := range logger.impl.labels {
merged[k] = v
}
for k, v := range labels {
merged[k] = v
}
result := module.context.GetLogger(path)
result.impl.labels = merged
return result
}
// Name returns the logger's module name.
func (logger Logger) Name() string {
return logger.getModule().Name()
}
// LogLevel returns the configured min log level of the logger.
func (logger Logger) LogLevel() Level {
return logger.getModule().level
}
// Tags returns the configured tags of the logger's module.
func (logger Logger) Tags() []string {
return logger.getModule().tags
}
// EffectiveLogLevel returns the effective min 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) {
logger.getModule().setLevel(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...)
}
// LogWithlabelsf logs a printf-formatted message at the given level with extra
// labels. The given labels will be added to the log entry.
// 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) LogWithLabelsf(level Level, message string, extraLabels map[string]string, args ...interface{}) {
logger.logCallf(2, level, message, extraLabels, 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{}) {
logger.logCallf(calldepth+1, level, message, nil, args...)
}
// logCallf is a private method for logging a printf-formatted message at the
// given level. Used by LogWithLabelsf and LogCallf.
func (logger Logger) logCallf(calldepth int, level Level, message string, extraLabels map[string]string, args ...interface{}) {
module := logger.getModule()
if !module.willWrite(level) {
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...)
}
entry := Entry{
Level: level,
Filename: file,
Line: line,
Timestamp: now,
Message: formattedMessage,
}
entry.Labels = make(Labels)
if len(module.tags) > 0 {
entry.Labels[LoggerTags] = strings.Join(module.tags, ",")
}
for k, v := range logger.impl.labels {
entry.Labels[k] = v
}
for k, v := range logger.labels {
entry.Labels[k] = v
}
// Add extra labels if there's any given.
for k, v := range extraLabels {
entry.Labels[k] = v
}
module.write(entry)
}
// 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...)
}
// InfoWithLabelsf logs the printf-formatted message at info level with extra
// labels.
func (logger Logger) InfoWithLabelsf(message string, extraLabels map[string]string, args ...interface{}) {
logger.LogWithLabelsf(INFO, message, extraLabels, 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().willWrite(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)
}
loggo-2.1.0/logger_test.go 0000664 0000000 0000000 00000035071 14612165663 0015506 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
gc "gopkg.in/check.v1"
"github.com/juju/loggo/v2"
)
type LoggerSuite struct{}
var _ = gc.Suite(&LoggerSuite{})
func (*LoggerSuite) SetUpTest(c *gc.C) {
loggo.ResetDefaultContext()
}
func (s *LoggerSuite) TestRootLogger(c *gc.C) {
root := loggo.Logger{}
c.Check(root.Name(), gc.Equals, "")
c.Check(root.LogLevel(), gc.Equals, loggo.WARNING)
c.Check(root.IsErrorEnabled(), gc.Equals, true)
c.Check(root.IsWarningEnabled(), gc.Equals, true)
c.Check(root.IsInfoEnabled(), gc.Equals, false)
c.Check(root.IsDebugEnabled(), gc.Equals, false)
c.Check(root.IsTraceEnabled(), gc.Equals, false)
}
func (s *LoggerSuite) TestWithLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
loggerWithLabels := logger.WithLabels(loggo.Labels{"foo": "bar"})
loggerWithTagsAndLabels := logger.
ChildWithTags("withTags", "tag1", "tag2").
WithLabels(loggo.Labels{"hello": "world"})
logger.Logf(loggo.INFO, "without labels")
loggerWithLabels.Logf(loggo.INFO, "with labels")
loggerWithTagsAndLabels.Logf(loggo.INFO, "with tags and labels")
logs := writer.Log()
c.Assert(logs, gc.HasLen, 3)
c.Check(logs[0].Message, gc.Equals, "without labels")
c.Check(logs[0].Labels, gc.HasLen, 0)
c.Check(logs[1].Message, gc.Equals, "with labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{"foo": "bar"})
c.Check(logs[2].Message, gc.Equals, "with tags and labels")
c.Check(logs[2].Labels, gc.DeepEquals, loggo.Labels{
"logger-tags": "tag1,tag2",
"hello": "world",
})
}
func (s *LoggerSuite) TestNonInheritedLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing").
WithLabels(loggo.Labels{"hello": "world"})
inheritedLoggerWithLabels := logger.
ChildWithLabels("inherited", loggo.Labels{"foo": "bar"})
logger.Logf(loggo.INFO, "with labels")
inheritedLoggerWithLabels.Logf(loggo.INFO, "with inherited labels")
logs := writer.Log()
c.Assert(logs, gc.HasLen, 2)
// The second log message should _only_ have the inherited labels.
c.Check(logs[0].Message, gc.Equals, "with labels")
c.Check(logs[0].Labels, gc.DeepEquals, loggo.Labels{"hello": "world"})
c.Check(logs[1].Message, gc.Equals, "with inherited labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{"foo": "bar"})
}
func (s *LoggerSuite) TestNonInheritedWithInheritedLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
inheritedLoggerWithLabels := logger.
ChildWithLabels("inherited", loggo.Labels{"foo": "bar"})
scopedLoggerWithLabels := inheritedLoggerWithLabels.
WithLabels(loggo.Labels{"hello": "world"})
inheritedLoggerWithLabels.Logf(loggo.INFO, "with inherited labels")
scopedLoggerWithLabels.Logf(loggo.INFO, "with scoped labels")
logs := writer.Log()
c.Assert(logs, gc.HasLen, 2)
// The second log message should have both the inherited labels and
// scoped labels.
c.Check(logs[0].Message, gc.Equals, "with inherited labels")
c.Check(logs[0].Labels, gc.DeepEquals, loggo.Labels{"foo": "bar"})
c.Check(logs[1].Message, gc.Equals, "with scoped labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{
"foo": "bar",
"hello": "world",
})
}
func (s *LoggerSuite) TestInheritedLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
nestedLoggerWithLabels := logger.
ChildWithLabels("nested", loggo.Labels{"foo": "bar"})
deepNestedLoggerWithLabels := nestedLoggerWithLabels.
ChildWithLabels("nested", loggo.Labels{"foo": "bar"}).
ChildWithLabels("deepnested", loggo.Labels{"fred": "tim"})
loggerWithTagsAndLabels := logger.
ChildWithLabels("nested-labels", loggo.Labels{"hello": "world"}).
ChildWithTags("nested-tag", "tag1", "tag2")
logger.Logf(loggo.INFO, "without labels")
nestedLoggerWithLabels.Logf(loggo.INFO, "with nested labels")
deepNestedLoggerWithLabels.Logf(loggo.INFO, "with deep nested labels")
loggerWithTagsAndLabels.Logf(loggo.INFO, "with tags and labels")
logs := writer.Log()
c.Assert(logs, gc.HasLen, 4)
c.Check(logs[0].Message, gc.Equals, "without labels")
c.Check(logs[0].Labels, gc.HasLen, 0)
c.Check(logs[1].Message, gc.Equals, "with nested labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{"foo": "bar"})
c.Check(logs[2].Message, gc.Equals, "with deep nested labels")
c.Check(logs[2].Labels, gc.DeepEquals, loggo.Labels{
"foo": "bar",
"fred": "tim",
})
c.Check(logs[3].Message, gc.Equals, "with tags and labels")
c.Check(logs[3].Labels, gc.DeepEquals, loggo.Labels{
"logger-tags": "tag1,tag2",
"hello": "world",
})
}
func (s *LoggerSuite) TestLogWithStaticAndDynamicLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
loggerWithLabels := logger.WithLabels(loggo.Labels{"foo": "bar"})
loggerWithLabels.LogWithLabelsf(loggo.INFO, "no extra labels", nil)
loggerWithLabels.LogWithLabelsf(loggo.INFO, "with extra labels", map[string]string{
"domain": "status",
"kind": "machine",
"id": "0",
"value": "idle",
})
logs := writer.Log()
c.Assert(logs, gc.HasLen, 2)
c.Check(logs[0].Message, gc.Equals, "no extra labels")
c.Check(logs[0].Labels, gc.DeepEquals, loggo.Labels{"foo": "bar"})
c.Check(logs[1].Message, gc.Equals, "with extra labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{
"foo": "bar", "domain": "status", "id": "0", "kind": "machine", "value": "idle"})
}
func (s *LoggerSuite) TestLogWithExtraLabels(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
logger.LogWithLabelsf(loggo.INFO, "no extra labels", nil)
logger.LogWithLabelsf(loggo.INFO, "with extra labels", map[string]string{
"domain": "status",
"kind": "machine",
"id": "0",
"value": "idle",
})
logs := writer.Log()
c.Assert(logs, gc.HasLen, 2)
c.Check(logs[0].Message, gc.Equals, "no extra labels")
c.Check(logs[0].Labels, gc.HasLen, 0)
c.Check(logs[1].Message, gc.Equals, "with extra labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{
"domain": "status", "id": "0", "kind": "machine", "value": "idle"})
}
func (s *LoggerSuite) TestInfoWithLabelsf(c *gc.C) {
writer := &loggo.TestWriter{}
context := loggo.NewContext(loggo.INFO)
err := context.AddWriter("test", writer)
c.Assert(err, gc.IsNil)
logger := context.GetLogger("testing")
logger.SetLogLevel(loggo.INFO)
c.Assert(logger.LogLevel(), gc.Equals, loggo.INFO)
logger.InfoWithLabelsf("no extra labels", nil)
logger.InfoWithLabelsf("with extra labels", map[string]string{
"domain": "status",
"kind": "machine",
"id": "0",
"value": "idle",
})
logs := writer.Log()
c.Assert(logs, gc.HasLen, 2)
c.Check(logs[0].Message, gc.Equals, "no extra labels")
c.Check(logs[0].Labels, gc.HasLen, 0)
c.Check(logs[0].Level, gc.Equals, loggo.INFO)
c.Check(logs[1].Message, gc.Equals, "with extra labels")
c.Check(logs[1].Labels, gc.DeepEquals, loggo.Labels{
"domain": "status", "id": "0", "kind": "machine", "value": "idle"})
c.Check(logs[1].Level, gc.Equals, loggo.INFO)
}
func (s *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 (s *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 (s *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)
}
func (s *LoggerSuite) TestParent(c *gc.C) {
logger := loggo.GetLogger("a.b.c")
b := logger.Parent()
a := b.Parent()
root := a.Parent()
c.Check(b.Name(), gc.Equals, "a.b")
c.Check(a.Name(), gc.Equals, "a")
c.Check(root.Name(), gc.Equals, "")
c.Check(root.Parent(), gc.DeepEquals, root)
}
func (s *LoggerSuite) TestParentSameContext(c *gc.C) {
ctx := loggo.NewContext(loggo.DEBUG)
logger := ctx.GetLogger("a.b.c")
b := logger.Parent()
c.Check(b, gc.DeepEquals, ctx.GetLogger("a.b"))
c.Check(b, gc.Not(gc.DeepEquals), loggo.GetLogger("a.b"))
}
func (s *LoggerSuite) TestChild(c *gc.C) {
root := loggo.GetLogger("")
a := root.Child("a")
logger := a.Child("b.c")
c.Check(a.Name(), gc.Equals, "a")
c.Check(logger.Name(), gc.Equals, "a.b.c")
c.Check(logger.Parent(), gc.DeepEquals, a.Child("b"))
}
func (s *LoggerSuite) TestChildSameContext(c *gc.C) {
ctx := loggo.NewContext(loggo.DEBUG)
logger := ctx.GetLogger("a")
b := logger.Child("b")
c.Check(b, gc.DeepEquals, ctx.GetLogger("a.b"))
c.Check(b, gc.Not(gc.DeepEquals), loggo.GetLogger("a.b"))
}
func (s *LoggerSuite) TestChildSameContextWithTags(c *gc.C) {
ctx := loggo.NewContext(loggo.DEBUG)
logger := ctx.GetLogger("a", "parent")
b := logger.ChildWithTags("b", "child")
c.Check(ctx.GetAllLoggerTags(), gc.DeepEquals, []string{"child", "parent"})
c.Check(logger.Tags(), gc.DeepEquals, []string{"parent"})
c.Check(b.Tags(), gc.DeepEquals, []string{"child"})
}
func (s *LoggerSuite) TestRoot(c *gc.C) {
logger := loggo.GetLogger("a.b.c")
root := logger.Root()
c.Check(root.Name(), gc.Equals, "")
c.Check(root.Child("a.b.c"), gc.DeepEquals, logger)
}
func (s *LoggerSuite) TestRootSameContext(c *gc.C) {
ctx := loggo.NewContext(loggo.DEBUG)
logger := ctx.GetLogger("a.b.c")
root := logger.Root()
c.Check(root.Name(), gc.Equals, "")
c.Check(root.Child("a.b.c"), gc.DeepEquals, logger)
c.Check(root.Child("a.b.c"), gc.Not(gc.DeepEquals), loggo.GetLogger("a.b.c"))
}
loggo-2.1.0/logging_test.go 0000664 0000000 0000000 00000005476 14612165663 0015663 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/v2"
)
type LoggingSuite struct {
context *loggo.Context
writer *writer
logger loggo.Logger
// Test that labels get outputted to loggo.Entry
Labels map[string]string
}
var _ = gc.Suite(&LoggingSuite{})
var _ = gc.Suite(&LoggingSuite{Labels: loggo.Labels{"logger-tags": "ONE,TWO"}})
func (s *LoggingSuite) SetUpTest(c *gc.C) {
s.writer = &writer{}
s.context = loggo.NewContext(loggo.TRACE)
err := s.context.AddWriter("test", s.writer)
c.Assert(err, gc.IsNil)
s.logger = s.context.GetLogger("test", "ONE,TWO")
}
func (s *LoggingSuite) TestLoggingStrings(c *gc.C) {
s.logger.Infof("simple")
s.logger.Infof("with args %d", 42)
s.logger.Infof("working 100%")
s.logger.Infof("missing %s")
checkLogEntries(c, s.writer.Log(), []loggo.Entry{
{Level: loggo.INFO, Module: "test", Message: "simple", Labels: s.Labels},
{Level: loggo.INFO, Module: "test", Message: "with args 42", Labels: s.Labels},
{Level: loggo.INFO, Module: "test", Message: "working 100%", Labels: s.Labels},
{Level: loggo.INFO, Module: "test", Message: "missing %s", Labels: s.Labels},
})
}
func (s *LoggingSuite) TestLoggingLimitWarning(c *gc.C) {
s.logger.SetLogLevel(loggo.WARNING)
start := time.Now()
logAllSeverities(s.logger)
end := time.Now()
entries := s.writer.Log()
checkLogEntries(c, entries, []loggo.Entry{
{Level: loggo.CRITICAL, Module: "test", Message: "something critical", Labels: s.Labels},
{Level: loggo.ERROR, Module: "test", Message: "an error", Labels: s.Labels},
{Level: loggo.WARNING, Module: "test", Message: "a warning message", Labels: s.Labels},
})
for _, entry := range entries {
c.Check(entry.Timestamp, Between(start, end))
}
}
func (s *LoggingSuite) TestLocationCapture(c *gc.C) {
s.logger.Criticalf("critical message") //tag critical-location
s.logger.Errorf("error message") //tag error-location
s.logger.Warningf("warning message") //tag warning-location
s.logger.Infof("info message") //tag info-location
s.logger.Debugf("debug message") //tag debug-location
s.logger.Tracef("trace message") //tag trace-location
log := s.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])
}
}
func (s *LoggingSuite) 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)
}
loggo-2.1.0/loggocolor/ 0000775 0000000 0000000 00000000000 14612165663 0015001 5 ustar 00root root 0000000 0000000 loggo-2.1.0/loggocolor/writer.go 0000664 0000000 0000000 00000003320 14612165663 0016642 0 ustar 00root root 0000000 0000000 package loggocolor
import (
"fmt"
"io"
"path/filepath"
"github.com/juju/ansiterm"
"github.com/juju/loggo/v2"
)
var (
// SeverityColor defines the colors for the levels output by the ColorWriter.
SeverityColor = map[loggo.Level]*ansiterm.Context{
loggo.TRACE: ansiterm.Foreground(ansiterm.Default),
loggo.DEBUG: ansiterm.Foreground(ansiterm.Green),
loggo.INFO: ansiterm.Foreground(ansiterm.BrightBlue),
loggo.WARNING: ansiterm.Foreground(ansiterm.Yellow),
loggo.ERROR: ansiterm.Foreground(ansiterm.BrightRed),
loggo.CRITICAL: {
Foreground: ansiterm.White,
Background: ansiterm.Red,
},
}
// LocationColor defines the colors for the location output by the ColorWriter.
LocationColor = ansiterm.Foreground(ansiterm.BrightBlue)
)
type colorWriter struct {
writer *ansiterm.Writer
}
// NewColorWriter will write out colored severity levels if the writer is
// outputting to a terminal.
func NewWriter(writer io.Writer) loggo.Writer {
return &colorWriter{ansiterm.NewWriter(writer)}
}
// NewcolorWriter will write out colored severity levels whether or not the
// writer is outputting to a terminal.
func NewColorWriter(writer io.Writer) loggo.Writer {
w := ansiterm.NewWriter(writer)
w.SetColorCapable(true)
return &colorWriter{w}
}
// Write implements Writer.
func (w *colorWriter) Write(entry loggo.Entry) {
ts := entry.Timestamp.Format(loggo.TimeFormat)
// Just get the basename from the filename
filename := filepath.Base(entry.Filename)
fmt.Fprintf(w.writer, "%s ", ts)
SeverityColor[entry.Level].Fprintf(w.writer, entry.Level.Short())
fmt.Fprintf(w.writer, " %s ", entry.Module)
LocationColor.Fprintf(w.writer, "%s:%d ", filename, entry.Line)
fmt.Fprintln(w.writer, entry.Message)
}
loggo-2.1.0/module.go 0000664 0000000 0000000 00000002427 14612165663 0014454 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
// Do not change rootName: modules.resolve() will misbehave if it isn't "".
const (
rootString = ""
)
type module struct {
name string
level Level
parent *module
context *Context
tags []string
tagsLookup map[string]struct{}
labels Labels
}
// Name returns the module's name.
func (m *module) Name() string {
if m.name == "" {
return rootString
}
return m.name
}
func (m *module) willWrite(level Level) bool {
if level < TRACE || level > CRITICAL {
return false
}
return level >= m.getEffectiveLogLevel()
}
func (m *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 := m.level.get(); level != UNSPECIFIED {
return level
}
m = m.parent
}
}
// setLevel sets the severity level of the given module.
// The root module cannot be set to UNSPECIFIED level.
func (m *module) setLevel(level Level) {
// The root module can't be unspecified.
if m.name == "" && level == UNSPECIFIED {
level = WARNING
}
m.level.set(level)
}
func (m *module) write(entry Entry) {
entry.Module = m.name
m.context.write(entry)
}
loggo-2.1.0/package_test.go 0000664 0000000 0000000 00000000316 14612165663 0015614 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"testing"
gc "gopkg.in/check.v1"
)
func Test(t *testing.T) {
gc.TestingT(t)
}
loggo-2.1.0/testwriter.go 0000664 0000000 0000000 00000001653 14612165663 0015403 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"path"
"sync"
)
// 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 []Entry
}
// Write saves the params as members in the TestLogValues struct appended to the Log array.
func (writer *TestWriter) Write(entry Entry) {
writer.mu.Lock()
defer writer.mu.Unlock()
entry.Filename = path.Base(entry.Filename)
writer.log = append(writer.log, entry)
}
// 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() []Entry {
writer.mu.Lock()
defer writer.mu.Unlock()
v := make([]Entry, len(writer.log))
copy(v, writer.log)
return v
}
loggo-2.1.0/util_test.go 0000664 0000000 0000000 00000003171 14612165663 0015200 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo_test
import (
"fmt"
"io/ioutil"
"strings"
"github.com/juju/loggo/v2"
gc "gopkg.in/check.v1"
)
func init() {
setLocationsForTags("logging_test.go")
setLocationsForTags("writer_test.go")
}
func assertLocation(c *gc.C, msg loggo.Entry, 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", tag))
}
tagToLocation[tag] = Location{file: filename, line: i + 1}
}
}
}
loggo-2.1.0/writer.go 0000664 0000000 0000000 00000003376 14612165663 0014507 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"
)
// DefaultWriterName is the name of the default writer for
// a Context.
const DefaultWriterName = "default"
// 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(entry Entry)
}
// NewMinLevelWriter returns a Writer that will only pass on the Write calls
// to the provided writer if the log level is at or above the specified
// minimum level.
func NewMinimumLevelWriter(writer Writer, minLevel Level) Writer {
return &minLevelWriter{
writer: writer,
level: minLevel,
}
}
type minLevelWriter struct {
writer Writer
level Level
}
// Write writes the log record.
func (w minLevelWriter) Write(entry Entry) {
if entry.Level < w.level {
return
}
w.writer.Write(entry)
}
type simpleWriter struct {
writer io.Writer
formatter func(entry Entry) string
}
// 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 func(entry Entry) string) Writer {
if formatter == nil {
formatter = DefaultFormatter
}
return &simpleWriter{writer, formatter}
}
func (simple *simpleWriter) Write(entry Entry) {
logLine := simple.formatter(entry)
fmt.Fprintln(simple.writer, logLine)
}
func defaultWriter() Writer {
return NewSimpleWriter(os.Stderr, DefaultFormatter)
}
loggo-2.1.0/writer_test.go 0000664 0000000 0000000 00000002247 14612165663 0015542 0 ustar 00root root 0000000 0000000 // Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package loggo
import (
"bytes"
"time"
gc "gopkg.in/check.v1"
)
type SimpleWriterSuite struct{}
var _ = gc.Suite(&SimpleWriterSuite{})
func (s *SimpleWriterSuite) TestNewSimpleWriter(c *gc.C) {
now := time.Now()
formatter := func(entry Entry) string {
return "<< " + entry.Message + " >>"
}
buf := &bytes.Buffer{}
writer := NewSimpleWriter(buf, formatter)
writer.Write(Entry{
Level: INFO,
Module: "test",
Filename: "somefile.go",
Line: 12,
Timestamp: now,
Message: "a message",
Labels: nil,
})
c.Check(buf.String(), gc.Equals, "<< a message >>\n")
}
func (s *SimpleWriterSuite) TestNewSimpleWriterWithLabels(c *gc.C) {
now := time.Now()
formatter := func(entry Entry) string {
return "<< " + entry.Message + " >>"
}
buf := &bytes.Buffer{}
writer := NewSimpleWriter(buf, formatter)
writer.Write(Entry{
Level: INFO,
Module: "test",
Filename: "somefile.go",
Line: 12,
Timestamp: now,
Message: "a message",
Labels: Labels{LoggerTags: "ONE,TWO"},
})
c.Check(buf.String(), gc.Equals, "<< a message >>\n")
}