pax_global_header00006660000000000000000000000064131552205030014506gustar00rootroot0000000000000052 comment=2b2e1b6c354fdf14182bd859b88420989fbed2c9 golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/000077500000000000000000000000001315522050300202355ustar00rootroot00000000000000golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/LICENSE000066400000000000000000000022041315522050300212400ustar00rootroot00000000000000 Licensed under the MIT licence, which is reproduced below (from https://opensource.org/licenses/MIT). ----- Copyright (c) 2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/README.md000066400000000000000000000016301315522050300215140ustar00rootroot00000000000000 # blitiri.com.ar/go/log [![GoDoc](https://godoc.org/blitiri.com.ar/go/log?status.svg)](https://godoc.org/blitiri.com.ar/go/log) [log](https://godoc.org/blitiri.com.ar/go/log) is a Go package implementing a simple logger. It implements an API somewhat similar to [glog](github.com/google/glog), with a focus towards simplicity and integration with standard tools such as systemd. ## Examples ```go log.Init() // only needed once. log.Debugf("debugging information: %v", x) log.Infof("something normal happened") log.Errorf("something bad happened") log.Fatalf("tragic") if log.V(3) { // only entered if -v was >= 3. expensiveDebugging() } ``` ## Status The API should be considered stable. Branch v1 will only have backwards-compatible changes made to it. There are no plans for v2 at the moment. ## Contact If you have any questions, comments or patches please send them to albertito@blitiri.com.ar. golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/example_test.go000066400000000000000000000005061315522050300232570ustar00rootroot00000000000000package log_test import "blitiri.com.ar/go/log" func Example() { log.Init() // only needed once. log.Debugf("debugging information: %v %v %v", 1, 2, 3) log.Infof("something normal happened") log.Errorf("something bad happened") if log.V(3) { // only entered if -v was >= 3. //expensiveDebugging() } // Output: } golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/log.go000066400000000000000000000170341315522050300213520ustar00rootroot00000000000000// Package log implements a simple logger. // // It implements an API somewhat similar to "github.com/google/glog" with a // focus towards simplicity and integration with standard tools such as // systemd. // // There are command line flags (defined using the flag package) to control // the behaviour of the default logger. By default, it will write to stderr // without timestamps; this is suitable for systemd (or equivalent) logging. // // Command-line flags: // // -alsologtostderr // also log to stderr, in addition to the file // -logfile string // file to log to (enables logtime) // -logtime // include the time when writing the log to stderr // -logtosyslog string // log to syslog, with the given tag // -v int // verbosity level (1 = debug) package log // import "blitiri.com.ar/go/log" import ( "flag" "fmt" "io" "log/syslog" "os" "path/filepath" "runtime" "strconv" "strings" "sync" "time" ) // Flags that control the default logging. var ( vLevel = flag.Int("v", 0, "verbosity level (1 = debug)") logFile = flag.String("logfile", "", "file to log to (enables logtime)") logToSyslog = flag.String("logtosyslog", "", "log to syslog, with the given tag") logTime = flag.Bool("logtime", false, "include the time when writing the log to stderr") alsoLogToStderr = flag.Bool("alsologtostderr", false, "also log to stderr, in addition to the file") ) // Type of a logging level, to prevent confusion. type Level int // Standard logging levels. const ( Fatal = Level(-2) Error = Level(-1) Info = Level(0) Debug = Level(1) ) var levelToLetter = map[Level]string{ Fatal: "☠", Error: "E", Info: "_", Debug: ".", } // A Logger represents a logging object that writes logs to a writer. type Logger struct { // Minimum level to log. Messages below this level will be dropped. // Note this field is NOT thread safe, if you change it, it is strongly // recommended to do so right after creating the logger, and before it is // used. // The use of this field should be considered EXPERIMENTAL, the API for it // could change in the future. Level Level // File name, if this logger is backed by a file. It's used to implement // reopening. fname string logTime bool callerSkip int w io.WriteCloser sync.Mutex } // New creates a new Logger, which writes logs to w. func New(w io.WriteCloser) *Logger { return &Logger{ w: w, callerSkip: 0, Level: Info, logTime: true, } } // NewFile creates a new Logger, which writes logs to the given file. func NewFile(path string) (*Logger, error) { f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } l := New(f) l.logTime = true l.fname = path return l, nil } // NewSyslog creates a new Logger, which writes logs to syslog, using the // given priority and tag. func NewSyslog(priority syslog.Priority, tag string) (*Logger, error) { w, err := syslog.New(priority, tag) if err != nil { return nil, err } l := New(w) l.logTime = false return l, nil } // Close the writer behind the logger. func (l *Logger) Close() { l.w.Close() } // Reopen the file behind the logger, if any. This can be used to implement // log rotation. // // Only works for loggers created via NewFile, otherwise it is a no-op. // // EXPERIMENTAL, this API could change in the future. func (l *Logger) Reopen() error { if l.fname == "" { return nil } f, err := os.OpenFile(l.fname, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return err } l.Lock() l.Close() l.w = f l.Unlock() return nil } // V returns true if the logger's level is >= the one given, false otherwise. // It can be used to decide whether to use or gather debugging information // only at a certain level, to avoid computing it needlessly. func (l *Logger) V(level Level) bool { return level <= l.Level } // Log the message into the logger, at the given level. This is low-level and // should rarely be needed, but it's available to allow the caller to have // more complex logic if needed. skip is the number of frames to skip when // computing the file name and line number. func (l *Logger) Log(level Level, skip int, format string, a ...interface{}) { if !l.V(level) { return } // Message. msg := fmt.Sprintf(format, a...) // Caller. _, file, line, ok := runtime.Caller(1 + l.callerSkip + skip) if !ok { file = "unknown" } fl := fmt.Sprintf("%s:%-4d", filepath.Base(file), line) if len(fl) > 18 { fl = fl[len(fl)-18:] } msg = fmt.Sprintf("%-18s", fl) + " " + msg // Level. letter, ok := levelToLetter[level] if !ok { letter = strconv.Itoa(int(level)) } msg = letter + " " + msg // Time. if l.logTime { msg = time.Now().Format("20060102 15:04:05.000000 ") + msg } if !strings.HasSuffix(msg, "\n") { msg += "\n" } l.Lock() l.w.Write([]byte(msg)) l.Unlock() } // Debugf logs information at a Debug level. func (l *Logger) Debugf(format string, a ...interface{}) { l.Log(Debug, 1, format, a...) } // Infof logs information at a Info level. func (l *Logger) Infof(format string, a ...interface{}) { l.Log(Info, 1, format, a...) } // Errorf logs information at an Error level. It also returns an error // constructed with the given message, in case it's useful for the caller. func (l *Logger) Errorf(format string, a ...interface{}) error { l.Log(Error, 1, format, a...) return fmt.Errorf(format, a...) } // Fatalf logs information at a Fatal level, and then exits the program with a // non-0 exit code. func (l *Logger) Fatalf(format string, a ...interface{}) { l.Log(-2, 1, format, a...) // TODO: Log traceback? os.Exit(1) } // The default logger, used by the top-level functions below. var Default = &Logger{ w: os.Stderr, callerSkip: 1, Level: Info, logTime: false, } // Initialize the default logger, based on the command-line flags. func Init() { flag.Parse() var err error if *logToSyslog != "" { Default, err = NewSyslog(syslog.LOG_DAEMON|syslog.LOG_INFO, *logToSyslog) if err != nil { panic(err) } } else if *logFile != "" { Default, err = NewFile(*logFile) if err != nil { panic(err) } *logTime = true } if *alsoLogToStderr && Default.w != os.Stderr { Default.w = multiWriteCloser(Default.w, os.Stderr) } Default.callerSkip = 1 Default.Level = Level(*vLevel) Default.logTime = *logTime } // V is a convenient wrapper to Default.V. func V(level Level) bool { return Default.V(level) } // Log is a convenient wrapper to Default.Log. func Log(level Level, skip int, format string, a ...interface{}) { Default.Log(level, skip, format, a...) } // Debugf is a convenient wrapper to Default.Debugf. func Debugf(format string, a ...interface{}) { Default.Debugf(format, a...) } // Infof is a convenient wrapper to Default.Infof. func Infof(format string, a ...interface{}) { Default.Infof(format, a...) } // Errorf is a convenient wrapper to Default.Errorf. func Errorf(format string, a ...interface{}) error { return Default.Errorf(format, a...) } // Fatalf is a convenient wrapper to Default.Fatalf. func Fatalf(format string, a ...interface{}) { Default.Fatalf(format, a...) } // multiWriteCloser creates a WriteCloser that duplicates its writes and // closes to all the provided writers. func multiWriteCloser(wc ...io.WriteCloser) io.WriteCloser { return mwc(wc) } type mwc []io.WriteCloser func (m mwc) Write(p []byte) (n int, err error) { for _, w := range m { if n, err = w.Write(p); err != nil { return } } return } func (m mwc) Close() error { for _, w := range m { if err := w.Close(); err != nil { return err } } return nil } golang-blitiri-go-log-0.0+git20170910.0.2b2e1b6/log_test.go000066400000000000000000000060761315522050300224150ustar00rootroot00000000000000package log import ( "io" "io/ioutil" "os" "regexp" "testing" ) func mustNewFile(t *testing.T) (string, *Logger) { f, err := ioutil.TempFile("", "log_test-") if err != nil { t.Fatalf("failed to create temp file: %v", err) } l, err := NewFile(f.Name()) if err != nil { t.Fatalf("failed to open new log file: %v", err) } return f.Name(), l } func checkContentsMatch(t *testing.T, name, path, expected string) { content, err := ioutil.ReadFile(path) if err != nil { panic(err) } got := string(content) if !regexp.MustCompile(expected).Match(content) { t.Errorf("%s: regexp %q did not match %q", name, expected, got) } } func testLogger(t *testing.T, fname string, l *Logger) { l.logTime = false l.Infof("message %d", 1) checkContentsMatch(t, "info-no-time", fname, "^_ log_test.go:.... message 1\n") os.Truncate(fname, 0) l.Infof("message %d\n", 1) checkContentsMatch(t, "info-with-newline", fname, "^_ log_test.go:.... message 1\n") os.Truncate(fname, 0) l.logTime = true l.Infof("message %d", 1) checkContentsMatch(t, "info-with-time", fname, `^\d{8} ..:..:..\.\d{6} _ log_test.go:.... message 1\n`) os.Truncate(fname, 0) l.logTime = false l.Errorf("error %d", 1) checkContentsMatch(t, "error", fname, `^E log_test.go:.... error 1\n`) if l.V(Debug) { t.Fatalf("Debug level enabled by default (level: %v)", l.Level) } os.Truncate(fname, 0) l.logTime = false l.Debugf("debug %d", 1) checkContentsMatch(t, "debug-no-log", fname, `^$`) os.Truncate(fname, 0) l.Level = Debug l.Debugf("debug %d", 1) checkContentsMatch(t, "debug", fname, `^\. log_test.go:.... debug 1\n`) if !l.V(Debug) { t.Errorf("l.Level = Debug, but V(Debug) = false") } os.Truncate(fname, 0) l.Level = Info l.Log(Debug, 0, "log debug %d", 1) l.Log(Info, 0, "log info %d", 1) checkContentsMatch(t, "log", fname, `^_ log_test.go:.... log info 1\n`) os.Truncate(fname, 0) l.Level = Info l.Log(Fatal, 0, "log fatal %d", 1) checkContentsMatch(t, "log", fname, `^☠ log_test.go:.... log fatal 1\n`) } func TestBasic(t *testing.T) { fname, l := mustNewFile(t) defer l.Close() defer os.Remove(fname) testLogger(t, fname, l) } func TestDefaultFile(t *testing.T) { fname, l := mustNewFile(t) l.Close() defer os.Remove(fname) *logFile = fname Init() testLogger(t, fname, Default) } func TestReopen(t *testing.T) { fname, l := mustNewFile(t) defer l.Close() defer os.Remove(fname) l.logTime = false l.Infof("pre rename") checkContentsMatch(t, "r", fname, `^_ log_test.go:.... pre rename\n`) os.Rename(fname, fname+"-m") defer os.Remove(fname + "-m") l.Infof("post rename") checkContentsMatch(t, "r", fname+"-m", `pre rename\n.* post rename`) if err := l.Reopen(); err != nil { t.Errorf("reopen: %v", err) } l.Infof("post reopen") checkContentsMatch(t, "r", fname, `^_ log_test.go:.... post reopen\n`) } type nopCloser struct { io.Writer } func (nopCloser) Close() error { return nil } func TestReopenNull(t *testing.T) { l := New(nopCloser{ioutil.Discard}) if err := l.Reopen(); err != nil { t.Errorf("reopen: %v", err) } }