pax_global_header00006660000000000000000000000064145606567740014536gustar00rootroot0000000000000052 comment=f61e4991d4657d57e237cc7ca5a6239f397f3c76 loggo-2.0.0/000077500000000000000000000000001456065677400126445ustar00rootroot00000000000000loggo-2.0.0/.gitignore000066400000000000000000000000201456065677400146240ustar00rootroot00000000000000example/example loggo-2.0.0/LICENSE000066400000000000000000000215011456065677400136500ustar00rootroot00000000000000All 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.0.0/Makefile000066400000000000000000000004041456065677400143020ustar00rootroot00000000000000default: check check: go test docs: godoc2md github.com/juju/loggo > README.md sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/loggo?status.svg)](https://godoc.org/github.com/juju/loggo)|' README.md .PHONY: default check docs loggo-2.0.0/README.md000066400000000000000000000373421456065677400141340ustar00rootroot00000000000000 # loggo import "github.com/juju/loggo/v2" [![GoDoc](https://godoc.org/github.com/juju/loggo?status.svg)](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.0.0/benchmarks_test.go000066400000000000000000000056131456065677400163540ustar00rootroot00000000000000// 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.0.0/checkers_test.go000066400000000000000000000017531456065677400160270ustar00rootroot00000000000000// 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.0.0/config.go000066400000000000000000000066051456065677400144470ustar00rootroot00000000000000// 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 label := extractConfigLabel(name); label != "" { if strings.Contains(label, ".") { // Show the original name and not text potentially extracted config // label. return "", UNSPECIFIED, fmt.Errorf("config label 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 label. // // 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", label) } 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` // `[LABEL]=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 extractConfigLabel(s string) string { name := strings.TrimSpace(s) if len(s) < 2 { return "" } if name[0] == '#' { return strings.ToLower(name[1:]) } return "" } loggo-2.0.0/config_test.go000066400000000000000000000077151456065677400155110ustar00rootroot00000000000000// 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: "#label = info", module: "#label", level: INFO, }, { value: "#LABEL = info", module: "#label", level: INFO, }, { value: "#label.1 = info", err: `config label should not contain '.', found "#label.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.0.0/context.go000066400000000000000000000205421456065677400146620ustar00rootroot00000000000000// 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 modulesLabelConfig 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), modulesLabelConfig: 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, labels ...string) Logger { name = strings.TrimSpace(strings.ToLower(name)) c.modulesMutex.Lock() defer c.modulesMutex.Unlock() return Logger{ impl: c.getLoggerModule(name, labels), } } // GetAllLoggerLabels returns all the logger labels for a given context. The // names are unique and sorted before returned, to improve consistency. func (c *Context) GetAllLoggerLabels() []string { c.modulesMutex.Lock() defer c.modulesMutex.Unlock() names := make(map[string]struct{}) for _, module := range c.modules { for k, v := range module.labelsLookup { 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.modulesLabelConfig[tag]; ok && level == UNSPECIFIED { level = configLevel } } impl = &module{ name: name, level: level, parent: parent, context: c, tags: tags, labelsLookup: labelMap, } 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.labelsLookup[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 { label := extractConfigLabel(name) if label == "" { module := c.getLoggerModule(name, nil) module.setLevel(level) continue } // Ensure that we save the config for lazy loggers to pick up correctly. c.modulesLabelConfig[label] = level // Config contains a named label, use that for selecting the loggers. modules := c.getLoggerModulesByTag(label) 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.modulesLabelConfig = 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.0.0/context_test.go000066400000000000000000000354751456065677400157340ustar00rootroot00000000000000// 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) TestGetAllLoggerLabels(c *gc.C) { context := loggo.NewContext(loggo.WARNING) context.GetLogger("a.b", "one") context.GetLogger("c.d", "one") context.GetLogger("e", "two") labels := context.GetAllLoggerLabels() c.Assert(labels, gc.DeepEquals, []string{"one", "two"}) } func (*ContextSuite) TestGetAllLoggerLabelsWithApplyConfig(c *gc.C) { context := loggo.NewContext(loggo.WARNING) context.ApplyConfig(loggo.Config{"#one": loggo.TRACE}) labels := context.GetAllLoggerLabels() c.Assert(labels, gc.DeepEquals, []string{}) } func (*ContextSuite) TestApplyConfigLabels(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) TestApplyConfigLabelsAddative(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) TestApplyConfigWithMalformedLabel(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) TestResetLoggerLevels(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.0.0/dependencies.tsv000066400000000000000000000007141456065677400160320ustar00rootroot00000000000000github.com/juju/ansiterm git 720a0952cc2ac777afc295d9861263e2a4cf96a1 2018-01-09T21:29:12Z github.com/lunixbochs/vtclean git 4fbf7632a2c6d3fbdb9931439bdbbeded02cbe36 2016-01-25T03:51:06Z github.com/mattn/go-colorable git ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8 2016-07-31T23:54:17Z github.com/mattn/go-isatty git 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8 2016-08-06T12:27:52Z gopkg.in/check.v1 git 4f90aeace3a26ad7021961c297b22c42160c7b25 2016-01-05T16:49:36Z loggo-2.0.0/doc.go000066400000000000000000000035271456065677400137470ustar00rootroot00000000000000// 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.0.0/entry.go000066400000000000000000000012341456065677400143340ustar00rootroot00000000000000// 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.0.0/example/000077500000000000000000000000001456065677400142775ustar00rootroot00000000000000loggo-2.0.0/example/first.go000066400000000000000000000007131456065677400157560ustar00rootroot00000000000000package 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.0.0/example/main.go000066400000000000000000000015241456065677400155540ustar00rootroot00000000000000package 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.0.0/example/second.go000066400000000000000000000007301456065677400161010ustar00rootroot00000000000000package 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.0.0/export_test.go000066400000000000000000000007721456065677400155610ustar00rootroot00000000000000// 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.0.0/formatter.go000066400000000000000000000017321456065677400152010ustar00rootroot00000000000000// 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.0.0/formatter_test.go000066400000000000000000000014041456065677400162340ustar00rootroot00000000000000// 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.0.0/global.go000066400000000000000000000060051456065677400144340ustar00rootroot00000000000000// 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.0.0/global_test.go000066400000000000000000000052061456065677400154750ustar00rootroot00000000000000// 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.0.0/go.mod000066400000000000000000000006471456065677400137610ustar00rootroot00000000000000module 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.0.0/go.sum000066400000000000000000000023061456065677400140000ustar00rootroot00000000000000github.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.0.0/labels.go000066400000000000000000000005211456065677400144330ustar00rootroot00000000000000// 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.0.0/level.go000066400000000000000000000036031456065677400143040ustar00rootroot00000000000000// 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.0.0/level_test.go000066400000000000000000000033031456065677400153400ustar00rootroot00000000000000// 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.0.0/logger.go000066400000000000000000000165411456065677400144610ustar00rootroot00000000000000// 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. 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...) } // 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...) } // 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{}) { 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, } if len(module.tags) > 0 || len(logger.labels) > 0 { entry.Labels = make(Labels) if len(module.tags) > 0 { entry.Labels = make(Labels) entry.Labels[LoggerTags] = strings.Join(module.tags, ",") } for k, v := range logger.labels { 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...) } // 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.0.0/logger_test.go000066400000000000000000000212311456065677400155100ustar00rootroot00000000000000// 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) 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) TestChildSameContextWithLabels(c *gc.C) { ctx := loggo.NewContext(loggo.DEBUG) logger := ctx.GetLogger("a", "parent") b := logger.ChildWithTags("b", "child") c.Check(ctx.GetAllLoggerLabels(), 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.0.0/logging_test.go000066400000000000000000000054761456065677400156740ustar00rootroot00000000000000// 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.0.0/loggocolor/000077500000000000000000000000001456065677400150125ustar00rootroot00000000000000loggo-2.0.0/loggocolor/writer.go000066400000000000000000000033201456065677400166530ustar00rootroot00000000000000package 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.0.0/module.go000066400000000000000000000024131456065677400144600ustar00rootroot00000000000000// 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 labelsLookup map[string]struct{} } // 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.0.0/package_test.go000066400000000000000000000003161456065677400156250ustar00rootroot00000000000000// 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.0.0/testwriter.go000066400000000000000000000016531456065677400154140ustar00rootroot00000000000000// 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.0.0/util_test.go000066400000000000000000000031711456065677400152110ustar00rootroot00000000000000// 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.0.0/writer.go000066400000000000000000000033761456065677400145200ustar00rootroot00000000000000// 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.0.0/writer_test.go000066400000000000000000000022471456065677400155530ustar00rootroot00000000000000// 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") }