pax_global_header00006660000000000000000000000064136620047450014521gustar00rootroot0000000000000052 comment=084e0db177737a969d41f5b524445670eb83dca6 golang-blitiri-go-log-1.1.0/000077500000000000000000000000001366200474500156055ustar00rootroot00000000000000golang-blitiri-go-log-1.1.0/.gitignore000066400000000000000000000000171366200474500175730ustar00rootroot00000000000000.* !.gitignore golang-blitiri-go-log-1.1.0/.travis.yml000066400000000000000000000003531366200474500177170ustar00rootroot00000000000000# Configuration for https://travis-ci.org/ language: go dist: bionic go_import_path: blitiri.com.ar/go/log go: - 1.10.x - stable - master script: - go test ./... - go test -race ./... - go test -bench ./... golang-blitiri-go-log-1.1.0/LICENSE000066400000000000000000000022041366200474500166100ustar00rootroot00000000000000 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-1.1.0/README.md000066400000000000000000000024121366200474500170630ustar00rootroot00000000000000 # blitiri.com.ar/go/log [![GoDoc](https://godoc.org/blitiri.com.ar/go/log?status.svg)](https://godoc.org/blitiri.com.ar/go/log) [![Build Status](https://travis-ci.org/albertito/log.svg?branch=master)](https://travis-ci.org/albertito/log) [![Go Report Card](https://goreportcard.com/badge/github.com/albertito/log)](https://goreportcard.com/report/github.com/albertito/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 generally stable, and no backwards-incompatible changes are expected. Some specific symbols are experimental, and are marked as such in their documentation. Those might see backwards-incompatible changes, including removing them entirely. ## Contact If you have any questions, comments or patches please send them to albertito@blitiri.com.ar. golang-blitiri-go-log-1.1.0/example_test.go000066400000000000000000000005061366200474500206270ustar00rootroot00000000000000package 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-1.1.0/go.mod000066400000000000000000000000461366200474500167130ustar00rootroot00000000000000module blitiri.com.ar/go/log go 1.14 golang-blitiri-go-log-1.1.0/log.go000066400000000000000000000205541366200474500167230ustar00rootroot00000000000000// 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") ) // Level is the type of the logging level constants, 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 // Include timestamp in the log message. // The use of this field should be considered EXPERIMENTAL, the API for it // could change in the future. LogTime bool // Include the log level in the log message. // The use of this field should be considered EXPERIMENTAL, the API for it // could change in the future. LogLevel bool // Include the caller in the log message. // The use of this field should be considered EXPERIMENTAL, the API for it // could change in the future. LogCaller bool // File name, if this logger is backed by a file. It's used to implement // reopening. fname string 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, LogLevel: true, LogCaller: true, } } // NewFile creates a new Logger, which writes logs to the given file. func NewFile(path string) (*Logger, error) { // Make path absolute, so Reopen continues to work if the program changes // its working directory later. path, err := filepath.Abs(path) if err != nil { return nil, err } 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.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{}) error { if !l.V(level) { return nil } b := strings.Builder{} // Time. if l.LogTime { b.WriteString(time.Now().Format("2006-01-02 15:04:05.000000 ")) } // Level. if l.LogLevel { letter, ok := levelToLetter[level] if !ok { letter = strconv.Itoa(int(level)) } b.WriteString(letter + " ") } // Caller. if l.LogCaller { _, 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:] } fmt.Fprintf(&b, "%-18s ", fl) } // Message. if !strings.HasSuffix(format, "\n") { format += "\n" } fmt.Fprintf(&b, format, a...) l.Lock() _, err := l.w.Write([]byte(b.String())) l.Unlock() return err } // 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(Fatal, 1, format, a...) // TODO: Log traceback? os.Exit(1) } // Default logger, used by the top-level functions below. var Default = &Logger{ w: os.Stderr, Level: Info, callerSkip: 1, LogCaller: true, LogLevel: true, LogTime: false, } // Init 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-1.1.0/log_test.go000066400000000000000000000110041366200474500177500ustar00rootroot00000000000000package 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{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`) // Test some combinations of options. cases := []struct { name string logTime bool logLevel bool logCaller bool expected string }{ { "show everything", true, true, true, `^....-..-.. ..:..:..\.\d{6} _ log_test.go:.... message 1\n`, }, { "caller+level", false, true, true, `^_ log_test.go:.... message 1\n`, }, { "time", true, false, false, `^....-..-.. ..:..:..\.\d{6} message 1\n`, }, { "none", false, false, false, `message 1\n`, }, } for _, c := range cases { os.Truncate(fname, 0) l.LogTime = c.logTime l.LogLevel = c.logLevel l.LogCaller = c.logCaller l.Infof("message %d", 1) checkContentsMatch(t, c.name, fname, c.expected) } } 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`) // NewFile with an absolute path should resolve it internally to a full // one, so reopen can work. l, err := NewFile("test-relative-file") defer l.Close() defer os.Remove("test-relative-file") if err != nil { t.Fatalf("failed to open file for testing: %v", err) } if l.fname[0] != '/' { t.Fatalf("internal fname is not absolute: %q", l.fname) } } type nopCloser struct { io.Writer } func (nopCloser) Close() error { return nil } func TestReopenNull(t *testing.T) { l := New(nopCloser{ioutil.Discard}) defer l.Close() if err := l.Reopen(); err != nil { t.Errorf("reopen: %v", err) } } // Benchmark a call below the verbosity level. func BenchmarkDebugf(b *testing.B) { l := New(nopCloser{ioutil.Discard}) defer l.Close() for i := 0; i < b.N; i++ { l.Debugf("test %d", i) } } // Benchmark a normal call. func BenchmarkInfof(b *testing.B) { l := New(nopCloser{ioutil.Discard}) defer l.Close() for i := 0; i < b.N; i++ { l.Infof("test %d", i) } }