├── .gitignore ├── examples ├── ConsoleLogWriter_Manual.go ├── Compatibility.go ├── SocketLogWriter_Manual.go ├── config.json ├── ConfigurationExample.go ├── SimpleNetLogServer.go ├── FileLogWriter_Manual.go └── config.xml ├── README.md ├── LICENSE.md ├── socklog.go ├── Changlog.md ├── termlog.go ├── pattlog.go ├── filelog.go ├── config.go ├── wrapper.go ├── log4go.go └── log4go_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | trace*.* 3 | *.log 4 | *.out 5 | _* 6 | 7 | -------------------------------------------------------------------------------- /examples/ConsoleLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import l4g "github.com/ccpaging/log4go" 8 | 9 | func main() { 10 | log := l4g.NewLogger() 11 | defer log.Close() 12 | 13 | log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter()) 14 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 15 | 16 | // This makes sure the filters is running 17 | // time.Sleep(200 * time.Millisecond) 18 | } 19 | -------------------------------------------------------------------------------- /examples/Compatibility.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | log "github.com/ccpaging/log4go" 6 | ) 7 | 8 | func main() { 9 | log.Print("This is Print()\n") 10 | log.Println("This is Println()") 11 | log.Printf("The time is now: %s\n", time.Now().Format("15:04:05 MST 2006/01/02")) 12 | log.Stderr("This is Stderr\n") 13 | log.Stderrf("The time is now: %s\n", time.Now().Format("15:04:05 MST 2006/01/02")) 14 | log.Panicf("The time is now: %s\n", time.Now().Format("15:04:05 MST 2006/01/02")) 15 | } 16 | -------------------------------------------------------------------------------- /examples/SocketLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import l4g "github.com/ccpaging/log4go" 8 | 9 | func main() { 10 | log := l4g.NewLogger() 11 | log.AddFilter("network", l4g.FINEST, l4g.NewSocketLogWriter("udp", "127.0.0.1:12124")) 12 | log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter()) 13 | 14 | // Run `nc -u -l -p 12124` or similar before you run this to see the following message 15 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 16 | log.Log(l4g.DEBUG, "myApp", "Send a log message with manual level, source, and message.") 17 | 18 | for i := 0; i < 5; i++ { 19 | time.Sleep(3 * time.Second) 20 | log.Log(l4g.DEBUG, "myApp", "Send a log message with manual level, source, and message.") 21 | } 22 | // This makes sure the output stream buffer is written 23 | log.Close() 24 | } 25 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logconfig": { 3 | "filters": [ 4 | { 5 | "enabled": "true", 6 | "tag": "stderr", 7 | "type": "console", 8 | "level": "DEBUG", 9 | "properties": [ 10 | { 11 | "name": "color", 12 | "value": "false" 13 | }, 14 | { 15 | "name": "format", 16 | "value": "[%D %T] [%L] (%S) %M" 17 | } 18 | ] 19 | }, 20 | { 21 | "enabled": "true", 22 | "tag": "file", 23 | "type": "file", 24 | "level": "FINEST", 25 | "properties": [ 26 | { 27 | "name": "filename", 28 | "value": "test.log" 29 | } 30 | ] 31 | }, 32 | { 33 | "enabled": "true", 34 | "tag": "syslog", 35 | "type": "socket", 36 | "level": "FINEST", 37 | "properties": [ 38 | { 39 | "name": "protocol", 40 | "value": "udp" 41 | }, 42 | { 43 | "name": "endpoint", 44 | "value": "127.0.0.1:12124" 45 | } 46 | ] 47 | } 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # log4go colored 2 | 3 | Please see http://log4go.googlecode.com/ 4 | 5 | Installation: 6 | 7 | - Run `go get github.com/ccpaging/log4go` 8 | 9 | - Run `go get github.com/daviddengcn/go-colortext` 10 | 11 | OR 12 | 13 | - Run `go install github.com/ccpaging/log4go` 14 | 15 | - Run `go install github.com/daviddengcn/go-colortext` 16 | 17 | Usage: 18 | 19 | - Add the following import: 20 | 21 | import log "github.com/ccpaging/log4go" 22 | 23 | - Sample 24 | 25 | ``` 26 | package main 27 | 28 | import ( 29 | log "github.com/ccpaging/log4go" 30 | ) 31 | 32 | func main() { 33 | log.Debug("This is Debug") 34 | log.Info("This is Info") 35 | 36 | // Compatibility with `log` 37 | log.Print("This is Print()") 38 | log.Println("This is Println()") 39 | log.Panic("This is Panic()") 40 | } 41 | ``` 42 | 43 | Acknowledgements: 44 | 45 | - ccpaging 46 | For providing awesome patches to bring colored log4go up to the latest Go spec 47 | 48 | Reference: 49 | 50 | 1. 51 | 2. 52 | 3. 53 | 4. 54 | -------------------------------------------------------------------------------- /examples/ConfigurationExample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | l4g "github.com/ccpaging/log4go" 5 | "encoding/json" 6 | "os" 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | // Load the configuration (isn't this easy?) 12 | l4g.LoadConfiguration("config.xml") 13 | 14 | // And now we're ready! 15 | l4g.Finest("This will only go to those of you really cool UDP kids! If you change enabled=true.") 16 | l4g.Debug("Oh no! %d + %d = %d!", 2, 2, 2+2) 17 | l4g.Info("About that time, eh chaps?") 18 | 19 | // l4g.Close() 20 | 21 | filename := "config.json" 22 | fd, err := os.Open(filename) 23 | if err != nil { 24 | panic(fmt.Sprintf("Can't load json config file: %s %v", filename, err)) 25 | } 26 | defer fd.Close() 27 | 28 | type Config struct { 29 | LogConfig json.RawMessage 30 | } 31 | 32 | c := Config{} 33 | err = json.NewDecoder(fd).Decode(&c) 34 | if err != nil { 35 | panic(fmt.Sprintf("Can't parse json config file: %s %v", filename, err)) 36 | } 37 | 38 | l4g.LoadConfigBuf("config.json", c.LogConfig) 39 | //l4g.LoadConfiguration("config.json") 40 | 41 | // And now we're ready! 42 | l4g.Finest("This will only go to those of you really cool UDP kids! If you change enabled=true.") 43 | l4g.Debug("Oh no! %d + %d = %d!", 2, 2, 2+2) 44 | l4g.Info("About that time, eh chaps?") 45 | 46 | l4g.Close() 47 | } 48 | 49 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Kyle Lemons . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 5 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the 6 | documentation and/or other materials provided with the distribution. 7 | 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 9 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 10 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 11 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 12 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 13 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /socklog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "net" 9 | "os" 10 | ) 11 | 12 | // This log writer sends output to a socket 13 | type SocketLogWriter struct { 14 | sock net.Conn 15 | proto string 16 | hostport string 17 | } 18 | 19 | func (w *SocketLogWriter) Close() { 20 | if w.sock != nil { 21 | w.sock.Close() 22 | } 23 | } 24 | 25 | func NewSocketLogWriter(proto, hostport string) *SocketLogWriter { 26 | s := &SocketLogWriter{ 27 | sock: nil, 28 | proto: proto, 29 | hostport: hostport, 30 | } 31 | return s 32 | } 33 | 34 | func (s *SocketLogWriter) LogWrite(rec *LogRecord) { 35 | 36 | // Marshall into JSON 37 | js, err := json.Marshal(rec) 38 | if err != nil { 39 | fmt.Fprintf(os.Stderr, "SocketLogWriter(%s): %v\n", s.hostport, err) 40 | return 41 | } 42 | 43 | if s.sock == nil { 44 | s.sock, err = net.Dial(s.proto, s.hostport) 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "SocketLogWriter(%s): %v\n", s.hostport, err) 47 | if s.sock != nil { 48 | s.sock.Close() 49 | s.sock = nil 50 | } 51 | return 52 | } 53 | } 54 | 55 | _, err = s.sock.Write(js) 56 | if err == nil { 57 | return 58 | } 59 | 60 | fmt.Fprintf(os.Stderr, "SocketLogWriter(%s): %v\n", s.hostport, err) 61 | s.sock.Close() 62 | s.sock = nil 63 | } 64 | 65 | -------------------------------------------------------------------------------- /examples/SimpleNetLogServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | "encoding/json" 9 | log "github.com/ccpaging/log4go" 10 | ) 11 | 12 | 13 | 14 | var ( 15 | port = flag.String("p", "12124", "Port number to listen on") 16 | ) 17 | 18 | func handleListener(listener *net.UDPConn){ 19 | var buffer [4096]byte 20 | 21 | // read into a new buffer 22 | buflen, addr, err := listener.ReadFrom(buffer[0:]) 23 | if err != nil{ 24 | fmt.Println("[Error] [", addr, "] ", err) 25 | return 26 | } 27 | 28 | if buflen <= 0{ 29 | fmt.Println("[Error] [", addr, "] ", "Empty packet") 30 | return 31 | } 32 | 33 | // fmt.Println(string(buffer[:buflen])) 34 | 35 | rec, err := Decode(buffer[:buflen]) 36 | if err != nil { 37 | fmt.Printf("Err: %v, [%s]\n", err, string(buffer[:buflen])) 38 | } 39 | // fmt.Println(rec) 40 | log.Log(rec.Level, rec.Source, rec.Message) 41 | } 42 | 43 | func Decode(data []byte) (*log.LogRecord, error) { 44 | var rec log.LogRecord 45 | 46 | // Make the log record 47 | err := json.Unmarshal(data, &rec) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return &rec, nil 53 | } 54 | 55 | func checkError(err error) { 56 | if err != nil { 57 | fmt.Printf("Erroring out: %s\n", err) 58 | os.Exit(1) 59 | } 60 | } 61 | 62 | func main() { 63 | flag.Parse() 64 | 65 | // Bind to the port 66 | bind, err := net.ResolveUDPAddr("udp4", "0.0.0.0:" + *port) 67 | checkError(err) 68 | 69 | fmt.Printf("Listening to port %s...\n", *port) 70 | 71 | // Create listener 72 | listener, err := net.ListenUDP("udp", bind) 73 | checkError(err) 74 | 75 | for { 76 | handleListener(listener) 77 | } 78 | 79 | // This makes sure the output stream buffer is written 80 | log.Close() 81 | } 82 | -------------------------------------------------------------------------------- /Changlog.md: -------------------------------------------------------------------------------- 1 | 2017-07-12 2 | 3 | * Fix bug: Initial FileLogWriter.maxbackup = 999 4 | 5 | * Restore function parameter: NewFileLogWriter(fname string, rotate bool) 6 | 7 | * Campatable https://golang.org/pkg/log function like log.Print(), log.Println() etc. 8 | 9 | 2017-05-23 10 | 11 | * Change const DefaultFileDepth as var DefaultCallerSkip 12 | 13 | 2016-03-03 14 | 15 | * start goroutine to delete expired log files. Merge from 16 | 17 | 2016-02-17 18 | 19 | * Append log record to current filelog if not oversized 20 | 21 | * Fixed Bug: filelog's rename 22 | 23 | 2015-12-08 24 | 25 | * Add maxbackup to filelog 26 | 27 | 2015-06-09 28 | 29 | * Sleeping at most one second and let go routine running drain the log channel before closing 30 | 31 | 2015-06-01 32 | 33 | * Migrate log variables (rec, closeq, closing, etc.) into Filters 34 | 35 | * Add new method for Filter include NewFilter(), Close(), run(), WriteToChan() 36 | 37 | * When closing, Filter: 38 | 39 | + Drain all left log records 40 | 41 | + Write them by LogWriter interface 42 | 43 | + Then close interface 44 | 45 | * Every Filter run a routine to recv rec and call LogWriter to write 46 | 47 | * LogWrite can be call directly, see log4go_test.go 48 | 49 | * Add new method to Logger include skip(), dispatch() 50 | 51 | Some ideas come from . Thanks. 52 | 53 | 2015-05-12 54 | 55 | * Add termlog format. Merge from 56 | 57 | 2015-04-30 58 | 59 | * Add closing and wait group. No ugly sleep code. 60 | 61 | 2015-01-06 62 | 63 | * Support json config 64 | 65 | * Fixed Bug: lost record in termlog and filelog 66 | 67 | 2015-01-05 support console color print 68 | 69 | * NewConsoleLogWriter() change to NewConsoleLogWriter(color bool) 70 | -------------------------------------------------------------------------------- /examples/FileLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | "path/filepath" 10 | ) 11 | 12 | import l4g "github.com/ccpaging/log4go" 13 | 14 | const ( 15 | filename = "flw.log" 16 | ) 17 | 18 | func main() { 19 | // Get a new logger instance 20 | log := l4g.NewLogger() 21 | 22 | // Create a default logger that is logging messages of FINE or higher 23 | log.AddFilter("file", l4g.FINE, l4g.NewFileLogWriter(filename, false)) 24 | log.Close() 25 | 26 | /* Can also specify manually via the following: (these are the defaults) */ 27 | flw := l4g.NewFileLogWriter(filename, true) 28 | flw.SetFormat("[%D %T] [%L] (%S) %M") 29 | flw.SetRotateSize(1024) 30 | flw.SetRotateLines(10) 31 | flw.SetRotateDaily(true) 32 | flw.SetRotateBackup(10) 33 | log.AddFilter("file", l4g.FINE, flw) 34 | 35 | // Log some experimental messages 36 | for cnt := 0; cnt < 100; cnt++ { 37 | log.Finest("Everything is created now (notice that I will not be printing to the file)") 38 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 39 | log.Critical("Time to close out!") 40 | } 41 | // Close the log 42 | log.Close() 43 | 44 | // Print what was logged to the file (yes, I know I'm skipping error checking) 45 | fd, _ := os.Open(filename) 46 | in := bufio.NewReader(fd) 47 | fmt.Print("Messages logged to file were: (line numbers not included)\n") 48 | for lineno := 1; ; lineno++ { 49 | line, err := in.ReadString('\n') 50 | if err == io.EOF { 51 | break 52 | } 53 | fmt.Printf("%3d:\t%s", lineno, line) 54 | } 55 | fd.Close() 56 | 57 | // Remove the file so it's not lying around 58 | os.Remove(filename) 59 | 60 | files, _ := filepath.Glob(filename + ".*") 61 | fmt.Printf("%d files match %s.*\n", len(files), filename) // contains a list of all files in the current directory 62 | 63 | for _, f := range files { 64 | fmt.Printf("Remove %s\n", f) 65 | os.Remove(f) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /termlog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "os" 9 | ) 10 | 11 | var stdout io.Writer = os.Stdout 12 | 13 | var isColorful = (os.Getenv("TERM") != "" && os.Getenv("TERM") != "dumb") || 14 | os.Getenv("ConEmuANSI") == "ON" 15 | 16 | // 0, Black; 1, Red; 2, Green; 3, Yellow; 4, Blue; 5, Purple; 6, Cyan; 7, White 17 | var ColorBytes = [...][]byte{ 18 | []byte("\x1b[0;34m"), // FINEST, Blue 19 | []byte("\x1b[0;36m"), // FINE, Cyan 20 | []byte("\x1b[0;32m"), // DEBUG, Green 21 | []byte("\x1b[0;35m"), // TRACE, Purple 22 | nil, // INFO, Default 23 | []byte("\x1b[1;33m"), // WARNING, Yellow 24 | []byte("\x1b[0;31m"), // ERROR, Red 25 | []byte("\x1b[0;31m;47m"), // CRITICAL, Red - White 26 | } 27 | var ColorReset = []byte("\x1b[0m") 28 | 29 | // This is the standard writer that prints to standard output. 30 | type ConsoleLogWriter struct { 31 | out io.Writer 32 | color bool 33 | format string 34 | } 35 | 36 | // This creates a new ConsoleLogWriter 37 | func NewConsoleLogWriter() *ConsoleLogWriter { 38 | c := &ConsoleLogWriter{ 39 | out: stdout, 40 | color: false, 41 | format: "[%T %D %Z] [%L] (%S) %M", 42 | } 43 | return c 44 | } 45 | 46 | // Must be called before the first log message is written. 47 | func (c *ConsoleLogWriter) SetColor(color bool) *ConsoleLogWriter { 48 | c.color = color 49 | return c 50 | } 51 | 52 | // Set the logging format (chainable). Must be called before the first log 53 | // message is written. 54 | func (c *ConsoleLogWriter) SetFormat(format string) *ConsoleLogWriter { 55 | c.format = format 56 | return c 57 | } 58 | 59 | func (c *ConsoleLogWriter) Close() { 60 | } 61 | 62 | func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) { 63 | if c.color { 64 | c.out.Write(ColorBytes[rec.Level]) 65 | defer c.out.Write(ColorReset) 66 | } 67 | fmt.Fprint(c.out, FormatLogRecord(c.format, rec)) 68 | } 69 | -------------------------------------------------------------------------------- /examples/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | true 8 | [%D %T] [%L] (%S) %M 9 | 10 | 11 | file 12 | file 13 | FINEST 14 | test.log 15 | 26 | [%D %T] [%L] (%S) %M 27 | false 28 | 0M 29 | 0K 30 | true 31 | 32 | 33 | xmllog 34 | xml 35 | TRACE 36 | trace.xml 37 | true 38 | 100M 39 | 6K 40 | false 41 | 42 | 43 | donotopen 44 | socket 45 | FINEST 46 | 192.168.1.255:12124 47 | udp 48 | 49 | 50 | -------------------------------------------------------------------------------- /pattlog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | FORMAT_DEFAULT = "[%D %T %z] [%L] (%S) %M" 13 | FORMAT_SHORT = "[%t %d] [%L] %M" 14 | FORMAT_ABBREV = "[%L] %M" 15 | ) 16 | 17 | type formatCacheType struct { 18 | LastUpdateSeconds int64 19 | longTime, shortTime string 20 | longZone, shortZone string 21 | longDate, shortDate string 22 | } 23 | 24 | var formatCache = &formatCacheType{} 25 | 26 | // Known format codes: 27 | // %T - Time (15:04:05) 28 | // %t - Time (15:04) 29 | // %Z - Zone (-0700) 30 | // %z - Zone (MST) 31 | // %D - Date (2006/01/02) 32 | // %d - Date (01/02/06) 33 | // %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT) 34 | // %S - Source 35 | // %s - Short Source 36 | // %M - Message 37 | // Ignores unknown formats 38 | // Recommended: "[%D %T] [%L] (%S) %M" 39 | func FormatLogRecord(format string, rec *LogRecord) string { 40 | if rec == nil { 41 | return "" 42 | } 43 | if len(format) == 0 { 44 | return "" 45 | } 46 | 47 | out := bytes.NewBuffer(make([]byte, 0, 64)) 48 | secs := rec.Created.UnixNano() / 1e9 49 | 50 | cache := *formatCache 51 | if cache.LastUpdateSeconds != secs { 52 | month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year() 53 | hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second() 54 | updated := &formatCacheType{ 55 | LastUpdateSeconds: secs, 56 | shortTime: fmt.Sprintf("%02d:%02d", hour, minute), 57 | longTime: fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), 58 | shortZone: rec.Created.Format("MST"), 59 | longZone: rec.Created.Format("-0700"), 60 | shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100), 61 | longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day), 62 | } 63 | cache = *updated 64 | formatCache = updated 65 | } 66 | 67 | // Split the string into pieces by % signs 68 | pieces := bytes.Split([]byte(format), []byte{'%'}) 69 | 70 | // Iterate over the pieces, replacing known formats 71 | for i, piece := range pieces { 72 | if i > 0 && len(piece) > 0 { 73 | switch piece[0] { 74 | case 'T': 75 | out.WriteString(cache.longTime) 76 | case 't': 77 | out.WriteString(cache.shortTime) 78 | case 'Z': 79 | out.WriteString(cache.longZone) 80 | case 'z': 81 | out.WriteString(cache.shortZone) 82 | case 'D': 83 | out.WriteString(cache.longDate) 84 | case 'd': 85 | out.WriteString(cache.shortDate) 86 | case 'L': 87 | out.WriteString(levelStrings[rec.Level]) 88 | case 'S': 89 | out.WriteString(rec.Source) 90 | case 's': 91 | slice := strings.Split(rec.Source, "/") 92 | out.WriteString(slice[len(slice)-1]) 93 | case 'M': 94 | out.WriteString(rec.Message) 95 | } 96 | if len(piece) > 1 { 97 | out.Write(piece[1:]) 98 | } 99 | } else if len(piece) > 0 { 100 | out.Write(piece) 101 | } 102 | } 103 | out.WriteByte('\n') 104 | 105 | return out.String() 106 | } 107 | 108 | -------------------------------------------------------------------------------- /filelog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // This log writer sends output to a file 14 | type FileLogWriter struct { 15 | // The opened file 16 | filename string 17 | file *os.File 18 | 19 | // The logging format 20 | format string 21 | 22 | // File header/trailer 23 | header, trailer string 24 | 25 | // Rotate at linecount 26 | maxlines int 27 | maxlines_curlines int 28 | 29 | // Rotate at size 30 | maxsize int 31 | maxsize_cursize int 32 | 33 | // Max days for log file storage 34 | maxdays int 35 | 36 | // Rotate daily 37 | daily bool 38 | daily_opendate time.Time 39 | 40 | // Keep old logfiles (.001, .002, etc) 41 | rotate bool 42 | maxbackup int 43 | } 44 | 45 | func (w *FileLogWriter) Close() { 46 | if w.file == nil { 47 | return 48 | } 49 | fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) 50 | w.file.Sync() 51 | w.file.Close() 52 | } 53 | 54 | // NewFileLogWriter creates a new LogWriter which writes to the given file and 55 | // has rotation enabled if rotate is true. 56 | // 57 | // If rotate is true, any time a new log file is opened, the old one is renamed 58 | // with a .### extension to preserve it. The various Set* methods can be used 59 | // to configure log rotation based on lines, size, and daily. 60 | // 61 | // The standard log-line format is: 62 | // [%D %T] [%L] (%S) %M 63 | func NewFileLogWriter(fname string, rotate bool) *FileLogWriter { 64 | w := &FileLogWriter{ 65 | filename: fname, 66 | format: "[%D %z %T] [%L] (%S) %M", 67 | rotate: rotate, 68 | maxbackup: 999, 69 | } 70 | 71 | // open the file for the first time 72 | if err := w.intRotate(); err != nil { 73 | fmt.Fprintf(os.Stderr, "FileLogWriter(%s): %s\n", w.filename, err) 74 | return nil 75 | } 76 | return w 77 | } 78 | 79 | func (w *FileLogWriter) LogWrite(rec *LogRecord) { 80 | now := time.Now() 81 | 82 | if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) || 83 | (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) || 84 | (w.daily && now.Day() != w.daily_opendate.Day()) { 85 | // open the file for the first time 86 | if err := w.intRotate(); err != nil { 87 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 88 | return 89 | } 90 | } 91 | 92 | if w.file == nil { 93 | return 94 | } 95 | 96 | // Perform the write 97 | n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec)) 98 | if err != nil { 99 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 100 | return 101 | } 102 | 103 | // Update the counts 104 | w.maxlines_curlines++ 105 | w.maxsize_cursize += n 106 | } 107 | 108 | // If this is called in a threaded context, it MUST be synchronized 109 | func (w *FileLogWriter) intRotate() error { 110 | // Close any log file that may be open 111 | if w.file != nil { 112 | fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) 113 | w.file.Close() 114 | } 115 | 116 | // fmt.Fprintf(os.Stderr, "FileLogWriter: %v\n", w) 117 | now := time.Now() 118 | if w.rotate { 119 | _, err := os.Lstat(w.filename) 120 | if err == nil { 121 | // We are keeping log files, move it to the next available number 122 | todate := now.Format("2006-01-02") 123 | if w.daily && now.Day() != w.daily_opendate.Day() { 124 | // rename as opendate 125 | todate = w.daily_opendate.Format("2006-01-02") 126 | } 127 | 128 | renameto := "" 129 | for num := 1; err == nil && num <= w.maxbackup; num++ { 130 | renameto = w.filename + fmt.Sprintf(".%s.%03d", todate, num) 131 | _, err = os.Lstat(renameto) 132 | } 133 | 134 | if err != nil { // Rename the file to its new 135 | os.Rename(w.filename, renameto) 136 | // Continue even failed 137 | } // else no free log file name to rotate 138 | 139 | } 140 | } 141 | 142 | if w.maxdays > 0 { 143 | go w.deleteOldLog() 144 | } 145 | 146 | if fstatus, err := os.Lstat(w.filename); err == nil { 147 | // Set the daily open date to file last modify 148 | w.daily_opendate = fstatus.ModTime() 149 | // initialize rotation values 150 | w.maxsize_cursize = int(fstatus.Size()) 151 | // fmt.Fprintf(os.Stderr, "FileLogWriter(%q): set cursize %d\n", w.filename, w.maxsize_cursize) 152 | // fmt.Fprintf(os.Stderr, "FileLogWriter(%q): set open date %v\n", w.filename, w.daily_opendate) 153 | } else { 154 | // Set the daily open date to the current date 155 | w.daily_opendate = now 156 | w.maxsize_cursize = 0 157 | } 158 | // initialize other rotation values 159 | w.maxlines_curlines = 0 160 | 161 | // Open the log file 162 | fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) 163 | if err != nil { 164 | w.file = nil 165 | return err 166 | } 167 | w.file = fd 168 | 169 | fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now})) 170 | return nil 171 | } 172 | 173 | // Delete old log files which were expired. 174 | func (w *FileLogWriter) deleteOldLog() { 175 | if w.maxdays <= 0 { 176 | return 177 | } 178 | dir := filepath.Dir(w.filename) 179 | base := filepath.Base(w.filename) 180 | modtime := time.Now().Unix() - int64(60*60*24*w.maxdays) 181 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { 182 | defer func() { 183 | if r := recover(); r != nil { 184 | fmt.Fprintf(os.Stderr, "FileLogWriter: Unable to remove old log '%s', error: %+v\n", path, err) 185 | } 186 | }() 187 | 188 | if !info.IsDir() && info.ModTime().Unix() < modtime { 189 | if strings.HasPrefix(filepath.Base(path), base) { 190 | os.Remove(path) 191 | } 192 | } 193 | return 194 | }) 195 | } 196 | 197 | // Set the logging format (chainable). Must be called before the first log 198 | // message is written. 199 | func (w *FileLogWriter) SetFormat(format string) *FileLogWriter { 200 | w.format = format 201 | return w 202 | } 203 | 204 | // Set the logfile header and footer (chainable). Must be called before the first log 205 | // message is written. These are formatted similar to the FormatLogRecord (e.g. 206 | // you can use %D and %T in your header/footer for date and time). 207 | func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter { 208 | w.header, w.trailer = head, foot 209 | if w.maxlines_curlines == 0 { 210 | fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()})) 211 | } 212 | return w 213 | } 214 | 215 | // Set rotate at linecount (chainable). Must be called before the first log 216 | // message is written. 217 | func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter { 218 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines) 219 | w.maxlines = maxlines 220 | return w 221 | } 222 | 223 | // Set rotate at size (chainable). Must be called before the first log message 224 | // is written. 225 | func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter { 226 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize) 227 | w.maxsize = maxsize 228 | return w 229 | } 230 | 231 | // Set max expire days. 232 | func (w *FileLogWriter) SetRotateDays(maxdays int) *FileLogWriter { 233 | w.maxdays = maxdays 234 | return w 235 | } 236 | 237 | // Set rotate daily (chainable). Must be called before the first log message is 238 | // written. 239 | func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter { 240 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily) 241 | w.daily = daily 242 | return w 243 | } 244 | 245 | // SetRotate changes whether or not the old logs are kept. (chainable) Must be 246 | // called before the first log message is written. If rotate is false, the 247 | // files are overwritten; otherwise, they are rotated to another file before the 248 | // new log is opened. 249 | func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter { 250 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate) 251 | w.rotate = rotate 252 | return w 253 | } 254 | 255 | // Set max backup files. Must be called before the first log message 256 | // is written. 257 | func (w *FileLogWriter) SetRotateBackup(maxbackup int) *FileLogWriter { 258 | w.maxbackup = maxbackup 259 | return w 260 | } 261 | 262 | // NewXMLLogWriter is a utility method for creating a FileLogWriter set up to 263 | // output XML record log messages instead of line-based ones. 264 | func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter { 265 | return NewFileLogWriter(fname, rotate).SetFormat( 266 | ` 267 | %D %T 268 | %S 269 | %M 270 | `).SetHeadFoot("", "") 271 | } 272 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "encoding/xml" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "path" 13 | "encoding/json" 14 | ) 15 | 16 | type kvProperty struct { 17 | Name string `xml:"name,attr"` 18 | Value string `xml:",chardata"` 19 | } 20 | 21 | type kvFilter struct { 22 | Enabled string `xml:"enabled,attr"` 23 | Tag string `xml:"tag"` 24 | Level string `xml:"level"` 25 | Type string `xml:"type"` 26 | Properties []kvProperty `xml:"property"` 27 | } 28 | 29 | type Config struct { 30 | Filters []kvFilter `xml:"filter"` 31 | } 32 | 33 | func (log Logger) LoadConfig(filename string) { 34 | if len(filename) <= 0 { 35 | return 36 | } 37 | 38 | // Open the configuration file 39 | fd, err := os.Open(filename) 40 | if err != nil { 41 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Could not open %q for reading: %s\n", filename, err) 42 | os.Exit(1) 43 | } 44 | 45 | buf, err := ioutil.ReadAll(fd) 46 | if err != nil { 47 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Could not read %q: %s\n", filename, err) 48 | os.Exit(1) 49 | } 50 | 51 | log.LoadConfigBuf(filename, buf) 52 | return 53 | } 54 | 55 | func (log Logger) LoadConfigBuf(filename string, buf []byte) { 56 | ext := path.Ext(filename) 57 | ext = ext[1:] 58 | 59 | switch ext { 60 | case "xml": 61 | log.LoadXMLConfig(filename, buf) 62 | break 63 | case "json": 64 | log.LoadJSONConfig(filename, buf) 65 | break 66 | default: 67 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Unknown config file type %v. XML or JSON are supported types\n", ext) 68 | } 69 | } 70 | 71 | // Parse Json configuration; see examples/example.json for documentation 72 | func (log Logger) LoadJSONConfig(filename string, contents []byte) { 73 | log.Close() 74 | 75 | jc := new(Config) 76 | if err := json.Unmarshal(contents, jc); err != nil { 77 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Could not parse Json configuration in %q: %s\n", filename, err) 78 | os.Exit(1) 79 | } 80 | 81 | log.ConfigToLogWriter(filename, jc) 82 | } 83 | 84 | // Parse XML configuration; see examples/example.xml for documentation 85 | func (log Logger) LoadXMLConfig(filename string, contents []byte) { 86 | log.Close() 87 | 88 | xc := new(Config) 89 | if err := xml.Unmarshal(contents, xc); err != nil { 90 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Could not parse XML configuration in %q: %s\n", filename, err) 91 | os.Exit(1) 92 | } 93 | 94 | log.ConfigToLogWriter(filename, xc) 95 | } 96 | 97 | func (log Logger) ConfigToLogWriter(filename string, cfg *Config) { 98 | for _, kvfilt := range cfg.Filters { 99 | var lw LogWriter 100 | var lvl Level 101 | bad, good, enabled := false, true, false 102 | 103 | // Check required children 104 | if len(kvfilt.Enabled) == 0 { 105 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required attribute %s for filter missing in %s\n", "enabled", filename) 106 | bad = true 107 | } else { 108 | enabled = kvfilt.Enabled != "false" 109 | } 110 | if len(kvfilt.Tag) == 0 { 111 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required child <%s> for filter missing in %s\n", "tag", filename) 112 | bad = true 113 | } 114 | if len(kvfilt.Type) == 0 { 115 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required child <%s> for filter missing in %s\n", "type", filename) 116 | bad = true 117 | } 118 | if len(kvfilt.Level) == 0 { 119 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required child <%s> for filter missing in %s\n", "level", filename) 120 | bad = true 121 | } 122 | 123 | switch kvfilt.Level { 124 | case "FINEST": 125 | lvl = FINEST 126 | case "FINE": 127 | lvl = FINE 128 | case "DEBUG": 129 | lvl = DEBUG 130 | case "TRACE": 131 | lvl = TRACE 132 | case "INFO": 133 | lvl = INFO 134 | case "WARNING": 135 | lvl = WARNING 136 | case "ERROR": 137 | lvl = ERROR 138 | case "CRITICAL": 139 | lvl = CRITICAL 140 | default: 141 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, kvfilt.Level) 142 | bad = true 143 | } 144 | 145 | // Just so all of the required attributes are errored at the same time if missing 146 | if bad { 147 | os.Exit(1) 148 | } 149 | 150 | switch kvfilt.Type { 151 | case "console": 152 | lw, good = propToConsoleLogWriter(filename, kvfilt.Properties, enabled) 153 | case "file": 154 | lw, good = propToFileLogWriter(filename, kvfilt.Properties, enabled) 155 | case "xml": 156 | lw, good = propToXMLLogWriter(filename, kvfilt.Properties, enabled) 157 | case "socket": 158 | lw, good = propToSocketLogWriter(filename, kvfilt.Properties, enabled) 159 | default: 160 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Could not load configuration in %s: unknown filter type \"%s\"\n", filename, kvfilt.Type) 161 | os.Exit(1) 162 | } 163 | 164 | // Just so all of the required params are errored at the same time if wrong 165 | if !good { 166 | os.Exit(1) 167 | } 168 | 169 | // If we're disabled (syntax and correctness checks only), don't add to logger 170 | if !enabled { 171 | continue 172 | } 173 | 174 | log[kvfilt.Tag] = NewFilter(lvl, lw) 175 | } 176 | } 177 | 178 | func propToConsoleLogWriter(filename string, props []kvProperty, enabled bool) (*ConsoleLogWriter, bool) { 179 | color := true 180 | format := "[%D %T] [%L] (%S) %M" 181 | // Parse properties 182 | for _, prop := range props { 183 | switch prop.Name { 184 | case "color": 185 | color = strings.Trim(prop.Value, " \r\n") != "false" 186 | case "format": 187 | format = strings.Trim(prop.Value, " \r\n") 188 | default: 189 | fmt.Fprintf(os.Stderr, "LoadConfig: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename) 190 | } 191 | } 192 | 193 | // If it's disabled, we're just checking syntax 194 | if !enabled { 195 | return nil, true 196 | } 197 | 198 | clw := NewConsoleLogWriter() 199 | clw.SetColor(color) 200 | clw.SetFormat(format) 201 | return clw, true 202 | } 203 | 204 | // Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024) 205 | func strToNumSuffix(str string, mult int) int { 206 | num := 1 207 | if len(str) > 1 { 208 | switch str[len(str)-1] { 209 | case 'G', 'g': 210 | num *= mult 211 | fallthrough 212 | case 'M', 'm': 213 | num *= mult 214 | fallthrough 215 | case 'K', 'k': 216 | num *= mult 217 | str = str[0 : len(str)-1] 218 | } 219 | } 220 | parsed, _ := strconv.Atoi(str) 221 | return parsed * num 222 | } 223 | 224 | func propToFileLogWriter(filename string, props []kvProperty, enabled bool) (*FileLogWriter, bool) { 225 | file := "" 226 | format := "[%D %T] [%L] (%S) %M" 227 | maxlines := 0 228 | maxsize := 0 229 | daily := false 230 | rotate := false 231 | maxbackup := 999 232 | maxdays := 0 233 | 234 | // Parse properties 235 | for _, prop := range props { 236 | switch prop.Name { 237 | case "filename": 238 | file = strings.Trim(prop.Value, " \r\n") 239 | case "format": 240 | format = strings.Trim(prop.Value, " \r\n") 241 | case "maxlines": 242 | maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) 243 | case "maxsize": 244 | maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) 245 | case "maxdays": 246 | maxdays = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1) 247 | case "daily": 248 | daily = strings.Trim(prop.Value, " \r\n") != "false" 249 | case "rotate": 250 | rotate = strings.Trim(prop.Value, " \r\n") != "false" 251 | case "maxBackup": 252 | maxbackup = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1) 253 | default: 254 | fmt.Fprintf(os.Stderr, "LoadConfig: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) 255 | } 256 | } 257 | 258 | // Check properties 259 | if len(file) == 0 { 260 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename) 261 | return nil, false 262 | } 263 | 264 | // If it's disabled, we're just checking syntax 265 | if !enabled { 266 | return nil, true 267 | } 268 | 269 | flw := NewFileLogWriter(file, rotate) 270 | if flw == nil { 271 | return nil, false 272 | } 273 | flw.SetFormat(format) 274 | flw.SetRotateLines(maxlines) 275 | flw.SetRotateSize(maxsize) 276 | flw.SetRotateDays(maxdays) 277 | flw.SetRotateDaily(daily) 278 | flw.SetRotateBackup(maxbackup) 279 | return flw, true 280 | } 281 | 282 | func propToXMLLogWriter(filename string, props []kvProperty, enabled bool) (*FileLogWriter, bool) { 283 | file := "" 284 | maxrecords := 0 285 | maxsize := 0 286 | daily := false 287 | rotate := false 288 | 289 | // Parse properties 290 | for _, prop := range props { 291 | switch prop.Name { 292 | case "filename": 293 | file = strings.Trim(prop.Value, " \r\n") 294 | case "maxrecords": 295 | maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) 296 | case "maxsize": 297 | maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) 298 | case "daily": 299 | daily = strings.Trim(prop.Value, " \r\n") != "false" 300 | case "rotate": 301 | rotate = strings.Trim(prop.Value, " \r\n") != "false" 302 | default: 303 | fmt.Fprintf(os.Stderr, "LoadConfig: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename) 304 | } 305 | } 306 | 307 | // Check properties 308 | if len(file) == 0 { 309 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename) 310 | return nil, false 311 | } 312 | 313 | // If it's disabled, we're just checking syntax 314 | if !enabled { 315 | return nil, true 316 | } 317 | 318 | xlw := NewXMLLogWriter(file, rotate) 319 | xlw.SetRotateLines(maxrecords) 320 | xlw.SetRotateSize(maxsize) 321 | xlw.SetRotateDaily(daily) 322 | return xlw, true 323 | } 324 | 325 | func propToSocketLogWriter(filename string, props []kvProperty, enabled bool) (*SocketLogWriter, bool) { 326 | endpoint := "" 327 | protocol := "udp" 328 | 329 | // Parse properties 330 | for _, prop := range props { 331 | switch prop.Name { 332 | case "endpoint": 333 | endpoint = strings.Trim(prop.Value, " \r\n") 334 | case "protocol": 335 | protocol = strings.Trim(prop.Value, " \r\n") 336 | default: 337 | fmt.Fprintf(os.Stderr, "LoadConfig: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) 338 | } 339 | } 340 | 341 | // Check properties 342 | if len(endpoint) == 0 { 343 | fmt.Fprintf(os.Stderr, "LoadConfig: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename) 344 | return nil, false 345 | } 346 | 347 | // If it's disabled, we're just checking syntax 348 | if !enabled { 349 | return nil, true 350 | } 351 | 352 | return NewSocketLogWriter(protocol, endpoint), true 353 | } 354 | -------------------------------------------------------------------------------- /wrapper.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | "strings" 10 | "runtime" 11 | "path/filepath" 12 | ) 13 | 14 | var ( 15 | Global Logger 16 | ) 17 | 18 | func init() { 19 | Global = Logger{ 20 | "stdout": NewFilter(DEBUG, NewConsoleLogWriter().SetColor(true).SetFormat("%T %L %s %M")), 21 | } 22 | } 23 | 24 | // Wrapper for (*Logger).LoadConfiguration 25 | func LoadConfiguration(filename string) { 26 | Global.LoadConfig(filename) 27 | } 28 | 29 | func LoadConfigBuf(filename string, buf []byte) { 30 | Global.LoadConfigBuf(filename, buf) 31 | } 32 | 33 | // Wrapper for (*Logger).AddFilter 34 | func AddFilter(name string, lvl Level, writer LogWriter) { 35 | Global.AddFilter(name, lvl, writer) 36 | } 37 | 38 | // Wrapper for (*Logger).Close (closes and removes all logwriters) 39 | func Close() { 40 | Global.Close() 41 | } 42 | 43 | // Compatibility with `log` 44 | func compat(lvl Level, calldepth int, args ...interface{}) { 45 | // Determine caller func 46 | pc, _, lineno, ok := runtime.Caller(calldepth) 47 | src := "" 48 | if ok { 49 | src = fmt.Sprintf("%s:%d", filepath.Base(runtime.FuncForPC(pc).Name()), lineno) 50 | } 51 | 52 | msg := "" 53 | if len(args) > 0 { 54 | msg = fmt.Sprintf(strings.Repeat(" %v", len(args))[1:], args...) 55 | } 56 | msg = strings.TrimRight(msg, "\r\n") 57 | 58 | Global.Log(lvl, src, msg) 59 | if lvl == ERROR { 60 | Global.Close() 61 | os.Exit(0) 62 | } else if lvl == CRITICAL { 63 | Global.Close() 64 | panic(msg) 65 | } 66 | } 67 | 68 | func compatf(lvl Level, calldepth int, format string, args ...interface{}) { 69 | // Determine caller func 70 | pc, _, lineno, ok := runtime.Caller(calldepth) 71 | src := "" 72 | if ok { 73 | src = fmt.Sprintf("%s:%d", filepath.Base(runtime.FuncForPC(pc).Name()), lineno) 74 | } 75 | 76 | msg := fmt.Sprintf(format, args...) 77 | msg = strings.TrimRight(msg, "\r\n") 78 | 79 | Global.Log(lvl, src, msg) 80 | if lvl == ERROR { 81 | Global.Close() 82 | os.Exit(0) 83 | } else if lvl == CRITICAL { 84 | Global.Close() 85 | panic(msg) 86 | } 87 | } 88 | 89 | func Crash(args ...interface{}) { 90 | compat(CRITICAL, DefaultCallerSkip, args ...) 91 | } 92 | 93 | // Logs the given message and crashes the program 94 | func Crashf(format string, args ...interface{}) { 95 | compatf(CRITICAL, DefaultCallerSkip, format, args ...) 96 | } 97 | 98 | // Compatibility with `log` 99 | func Exit(args ...interface{}) { 100 | compat(ERROR, DefaultCallerSkip, args ...) 101 | } 102 | 103 | // Compatibility with `log` 104 | func Exitf(format string, args ...interface{}) { 105 | compatf(ERROR, DefaultCallerSkip, format, args ...) 106 | } 107 | 108 | // Compatibility with `log` 109 | func Stderr(args ...interface{}) { 110 | compat(WARNING, DefaultCallerSkip, args ...) 111 | } 112 | 113 | // Compatibility with `log` 114 | func Stderrf(format string, args ...interface{}) { 115 | compatf(WARNING, DefaultCallerSkip, format, args ...) 116 | } 117 | 118 | // Compatibility with `log` 119 | func Stdout(args ...interface{}) { 120 | compat(INFO, DefaultCallerSkip, args ...) 121 | } 122 | 123 | // Compatibility with `log` 124 | func Stdoutf(format string, args ...interface{}) { 125 | compatf(INFO, DefaultCallerSkip, format, args ...) 126 | } 127 | 128 | // Compatibility with `log` 129 | func Fatal(v ...interface{}) { 130 | compat(ERROR, DefaultCallerSkip, v ...) 131 | } 132 | 133 | func Fatalf(format string, v ...interface{}) { 134 | compatf(ERROR, DefaultCallerSkip, format, v ...) 135 | } 136 | 137 | func Fatalln(v ...interface{}) { 138 | compat(ERROR, DefaultCallerSkip, v ...) 139 | } 140 | 141 | func Output(calldepth int, s string) error { 142 | compat(INFO, calldepth, s) 143 | return nil 144 | } 145 | 146 | func Panic(v ...interface{}) { 147 | compat(CRITICAL, DefaultCallerSkip, v ...) 148 | } 149 | 150 | func Panicf(format string, v ...interface{}) { 151 | compatf(CRITICAL, DefaultCallerSkip, format, v ...) 152 | } 153 | 154 | func Panicln(v ...interface{}) { 155 | compat(CRITICAL, DefaultCallerSkip, v ...) 156 | } 157 | 158 | func Print(v ...interface{}) { 159 | compat(INFO, DefaultCallerSkip, v ...) 160 | } 161 | 162 | func Printf(format string, v ...interface{}) { 163 | compatf(INFO, DefaultCallerSkip, format, v ...) 164 | } 165 | 166 | func Println(v ...interface{}) { 167 | compat(INFO, DefaultCallerSkip, v ...) 168 | } 169 | 170 | // Send a log message manually 171 | // Wrapper for (*Logger).Log 172 | func Log(lvl Level, source, message string) { 173 | Global.Log(lvl, source, message) 174 | } 175 | 176 | // Send a formatted log message easily 177 | // Wrapper for (*Logger).Logf 178 | func Logf(lvl Level, format string, args ...interface{}) { 179 | Global.intLogf(lvl, format, args...) 180 | } 181 | 182 | // Send a closure log message 183 | // Wrapper for (*Logger).Logc 184 | func Logc(lvl Level, closure func() string) { 185 | Global.intLogc(lvl, closure) 186 | } 187 | 188 | // Utility for finest log messages (see Debug() for parameter explanation) 189 | // Wrapper for (*Logger).Finest 190 | func Finest(arg0 interface{}, args ...interface{}) { 191 | const ( 192 | lvl = FINEST 193 | ) 194 | switch first := arg0.(type) { 195 | case string: 196 | // Use the string as a format string 197 | Global.intLogf(lvl, first, args...) 198 | case func() string: 199 | // Log the closure (no other arguments used) 200 | Global.intLogc(lvl, first) 201 | default: 202 | // Build a format string so that it will be similar to Sprint 203 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 204 | } 205 | } 206 | 207 | // Utility for fine log messages (see Debug() for parameter explanation) 208 | // Wrapper for (*Logger).Fine 209 | func Fine(arg0 interface{}, args ...interface{}) { 210 | const ( 211 | lvl = FINE 212 | ) 213 | switch first := arg0.(type) { 214 | case string: 215 | // Use the string as a format string 216 | Global.intLogf(lvl, first, args...) 217 | case func() string: 218 | // Log the closure (no other arguments used) 219 | Global.intLogc(lvl, first) 220 | default: 221 | // Build a format string so that it will be similar to Sprint 222 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 223 | } 224 | } 225 | 226 | // Utility for debug log messages 227 | // When given a string as the first argument, this behaves like Logf but with the DEBUG log level (e.g. the first argument is interpreted as a format for the latter arguments) 228 | // When given a closure of type func()string, this logs the string returned by the closure iff it will be logged. The closure runs at most one time. 229 | // When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint). 230 | // Wrapper for (*Logger).Debug 231 | func Debug(arg0 interface{}, args ...interface{}) { 232 | const ( 233 | lvl = DEBUG 234 | ) 235 | switch first := arg0.(type) { 236 | case string: 237 | // Use the string as a format string 238 | Global.intLogf(lvl, first, args...) 239 | case func() string: 240 | // Log the closure (no other arguments used) 241 | Global.intLogc(lvl, first) 242 | default: 243 | // Build a format string so that it will be similar to Sprint 244 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 245 | } 246 | } 247 | 248 | // Utility for trace log messages (see Debug() for parameter explanation) 249 | // Wrapper for (*Logger).Trace 250 | func Trace(arg0 interface{}, args ...interface{}) { 251 | const ( 252 | lvl = TRACE 253 | ) 254 | switch first := arg0.(type) { 255 | case string: 256 | // Use the string as a format string 257 | Global.intLogf(lvl, first, args...) 258 | case func() string: 259 | // Log the closure (no other arguments used) 260 | Global.intLogc(lvl, first) 261 | default: 262 | // Build a format string so that it will be similar to Sprint 263 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 264 | } 265 | } 266 | 267 | // Utility for info log messages (see Debug() for parameter explanation) 268 | // Wrapper for (*Logger).Info 269 | func Info(arg0 interface{}, args ...interface{}) { 270 | const ( 271 | lvl = INFO 272 | ) 273 | switch first := arg0.(type) { 274 | case string: 275 | // Use the string as a format string 276 | Global.intLogf(lvl, first, args...) 277 | case func() string: 278 | // Log the closure (no other arguments used) 279 | Global.intLogc(lvl, first) 280 | default: 281 | // Build a format string so that it will be similar to Sprint 282 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 283 | } 284 | } 285 | 286 | // Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 287 | // These functions will execute a closure exactly once, to build the error message for the return 288 | // Wrapper for (*Logger).Warn 289 | func Warn(arg0 interface{}, args ...interface{}) error { 290 | const ( 291 | lvl = WARNING 292 | ) 293 | switch first := arg0.(type) { 294 | case string: 295 | // Use the string as a format string 296 | Global.intLogf(lvl, first, args...) 297 | return errors.New(fmt.Sprintf(first, args...)) 298 | case func() string: 299 | // Log the closure (no other arguments used) 300 | str := first() 301 | Global.intLogf(lvl, "%s", str) 302 | return errors.New(str) 303 | default: 304 | // Build a format string so that it will be similar to Sprint 305 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 306 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 307 | } 308 | return nil 309 | } 310 | 311 | // Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 312 | // These functions will execute a closure exactly once, to build the error message for the return 313 | // Wrapper for (*Logger).Error 314 | func Error(arg0 interface{}, args ...interface{}) error { 315 | const ( 316 | lvl = ERROR 317 | ) 318 | switch first := arg0.(type) { 319 | case string: 320 | // Use the string as a format string 321 | Global.intLogf(lvl, first, args...) 322 | return errors.New(fmt.Sprintf(first, args...)) 323 | case func() string: 324 | // Log the closure (no other arguments used) 325 | str := first() 326 | Global.intLogf(lvl, "%s", str) 327 | return errors.New(str) 328 | default: 329 | // Build a format string so that it will be similar to Sprint 330 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 331 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 332 | } 333 | return nil 334 | } 335 | 336 | // Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 337 | // These functions will execute a closure exactly once, to build the error message for the return 338 | // Wrapper for (*Logger).Critical 339 | func Critical(arg0 interface{}, args ...interface{}) error { 340 | const ( 341 | lvl = CRITICAL 342 | ) 343 | switch first := arg0.(type) { 344 | case string: 345 | // Use the string as a format string 346 | Global.intLogf(lvl, first, args...) 347 | return errors.New(fmt.Sprintf(first, args...)) 348 | case func() string: 349 | // Log the closure (no other arguments used) 350 | str := first() 351 | Global.intLogf(lvl, "%s", str) 352 | return errors.New(str) 353 | default: 354 | // Build a format string so that it will be similar to Sprint 355 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 356 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 357 | } 358 | return nil 359 | } 360 | -------------------------------------------------------------------------------- /log4go.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | // Package log4go provides level-based and highly configurable logging. 4 | // 5 | // Enhanced Logging 6 | // 7 | // This is inspired by the logging functionality in Java. Essentially, you create a Logger 8 | // object and create output filters for it. You can send whatever you want to the Logger, 9 | // and it will filter that based on your settings and send it to the outputs. This way, you 10 | // can put as much debug code in your program as you want, and when you're done you can filter 11 | // out the mundane messages so only the important ones show up. 12 | // 13 | // Utility functions are provided to make life easier. Here is some example code to get started: 14 | // 15 | // log := log4go.NewLogger() 16 | // log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter()) 17 | // log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) 18 | // log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) 19 | // 20 | // The first two lines can be combined with the utility NewDefaultLogger: 21 | // 22 | // log := log4go.NewDefaultLogger(log4go.DEBUG) 23 | // log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true)) 24 | // log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02")) 25 | // 26 | // Usage notes: 27 | // - The ConsoleLogWriter does not display the source of the message to standard 28 | // output, but the FileLogWriter does. 29 | // - The utility functions (Info, Debug, Warn, etc) derive their source from the 30 | // calling function, and this incurs extra overhead. 31 | // 32 | // Changes from 2.0: 33 | // - The external interface has remained mostly stable, but a lot of the 34 | // internals have been changed, so if you depended on any of this or created 35 | // your own LogWriter, then you will probably have to update your code. In 36 | // particular, Logger is now a map and ConsoleLogWriter is now a channel 37 | // behind-the-scenes, and the LogWrite method no longer has return values. 38 | // 39 | // Future work: (please let me know if you think I should work on any of these particularly) 40 | // - Log file rotation 41 | // - Logging configuration files ala log4j 42 | // - Have the ability to remove filters? 43 | // - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows 44 | // for another method of logging 45 | // - Add an XML filter type 46 | package log4go 47 | 48 | import ( 49 | "errors" 50 | "fmt" 51 | "os" 52 | "path/filepath" 53 | "runtime" 54 | "strings" 55 | "time" 56 | ) 57 | 58 | // Version information 59 | const ( 60 | L4G_VERSION = "log4go-v4.0.2" 61 | L4G_MAJOR = 4 62 | L4G_MINOR = 0 63 | L4G_BUILD = 1 64 | ) 65 | 66 | /****** Constants ******/ 67 | 68 | // These are the integer logging levels used by the logger 69 | type Level int 70 | 71 | const ( 72 | FINEST Level = iota 73 | FINE 74 | DEBUG 75 | TRACE 76 | INFO 77 | WARNING 78 | ERROR 79 | CRITICAL 80 | ) 81 | 82 | // Logging level strings 83 | var ( 84 | levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} 85 | ) 86 | 87 | func (l Level) String() string { 88 | if l < 0 || int(l) > len(levelStrings) { 89 | return "UNKNOWN" 90 | } 91 | return levelStrings[int(l)] 92 | } 93 | 94 | /****** Variables ******/ 95 | var ( 96 | // Default skip passed to runtime.Caller to get file name/line 97 | // May require tweaking if you want to wrap the logger 98 | DefaultCallerSkip = 2 99 | 100 | // LogBufferLength specifies how many log messages a particular log4go 101 | // logger can buffer at a time before writing them. 102 | DefaultBufferLength = 32 103 | ) 104 | 105 | /****** LogRecord ******/ 106 | 107 | // A LogRecord contains all of the pertinent information for each message 108 | type LogRecord struct { 109 | Level Level // The log level 110 | Created time.Time // The time at which the log message was created (nanoseconds) 111 | Source string // The message source 112 | Message string // The log message 113 | } 114 | 115 | /****** LogWriter ******/ 116 | 117 | // This is an interface for anything that should be able to write logs 118 | type LogWriter interface { 119 | // This will be called to log a LogRecord message. 120 | LogWrite(rec *LogRecord) 121 | 122 | // This should clean up anything lingering about the LogWriter, as it is called before 123 | // the LogWriter is removed. LogWrite should not be called after Close. 124 | Close() 125 | } 126 | 127 | /****** Logger ******/ 128 | 129 | // A Filter represents the log level below which no log records are written to 130 | // the associated LogWriter. 131 | type Filter struct { 132 | Level Level 133 | 134 | rec chan *LogRecord // write queue 135 | closed bool // true if Socket was closed at API level 136 | 137 | LogWriter 138 | } 139 | 140 | func NewFilter(lvl Level, writer LogWriter) *Filter { 141 | f := &Filter { 142 | Level: lvl, 143 | 144 | rec: make(chan *LogRecord, DefaultBufferLength), 145 | closed: false, 146 | 147 | LogWriter: writer, 148 | } 149 | 150 | go f.run() 151 | return f 152 | } 153 | 154 | func (f *Filter) WriteToChan(rec *LogRecord) { 155 | if f.closed { 156 | fmt.Fprintf(os.Stderr, "LogWriter: channel has been closed. Message is [%s]\n", rec.Message) 157 | return 158 | } 159 | f.rec <- rec 160 | } 161 | 162 | func (f *Filter) run() { 163 | for { 164 | select { 165 | case rec, ok := <-f.rec: 166 | if !ok { 167 | return 168 | } 169 | f.LogWrite(rec) 170 | } 171 | } 172 | } 173 | 174 | func (f *Filter) Close() { 175 | if f.closed { 176 | return 177 | } 178 | // sleep at most one second and let go routine running 179 | // drain the log channel before closing 180 | for i := 10; i > 0; i-- { 181 | time.Sleep(100 * time.Millisecond) 182 | if len(f.rec) <= 0 { 183 | break 184 | } 185 | } 186 | 187 | // block write channel 188 | f.closed = true 189 | 190 | defer f.LogWriter.Close() 191 | 192 | close(f.rec) 193 | 194 | if len(f.rec) <= 0 { 195 | return 196 | } 197 | // drain the log channel and write driect 198 | for rec := range f.rec { 199 | f.LogWrite(rec) 200 | } 201 | } 202 | 203 | // A Logger represents a collection of Filters through which log messages are 204 | // written. 205 | type Logger map[string]*Filter 206 | 207 | // Create a new logger. 208 | // 209 | // DEPRECATED: Use make(Logger) instead. 210 | func NewLogger() Logger { 211 | os.Stderr.WriteString("warning: use of deprecated NewLogger\n") 212 | return make(Logger) 213 | } 214 | 215 | // Create a new logger with a "stdout" filter configured to send log messages at 216 | // or above lvl to standard output. 217 | // 218 | // DEPRECATED: use NewDefaultLogger instead. 219 | func NewConsoleLogger(lvl Level) Logger { 220 | os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") 221 | return Logger{ 222 | "stdout": NewFilter(lvl, NewConsoleLogWriter()), 223 | } 224 | } 225 | 226 | // Create a new logger with a "stdout" filter configured to send log messages at 227 | // or above lvl to standard output. 228 | func NewDefaultLogger(lvl Level) Logger { 229 | return Logger{ 230 | "stdout": NewFilter(lvl, NewConsoleLogWriter()), 231 | } 232 | } 233 | 234 | // Closes all log writers in preparation for exiting the program or a 235 | // reconfiguration of logging. Calling this is not really imperative, unless 236 | // you want to guarantee that all log messages are written. Close removes 237 | // all filters (and thus all LogWriters) from the logger. 238 | func (log Logger) Close() { 239 | // Close all open loggers 240 | for name, filt := range log { 241 | filt.Close() 242 | delete(log, name) 243 | } 244 | } 245 | 246 | // Add a new LogWriter to the Logger which will only log messages at lvl or 247 | // higher. This function should not be called from multiple goroutines. 248 | // Returns the logger for chaining. 249 | func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { 250 | log[name] = NewFilter(lvl, writer) 251 | return log 252 | } 253 | 254 | /******* Logging *******/ 255 | 256 | // Determine if any logging will be done 257 | func (log Logger) skip(lvl Level) bool { 258 | for _, filt := range log { 259 | if lvl >= filt.Level { 260 | return false 261 | } 262 | } 263 | return true 264 | } 265 | 266 | // Dispatch the logs 267 | func (log Logger) dispatch(rec *LogRecord) { 268 | for _, filt := range log { 269 | if rec.Level < filt.Level { 270 | continue 271 | } 272 | filt.WriteToChan(rec) 273 | } 274 | } 275 | 276 | // Send a formatted log message internally 277 | func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { 278 | if log.skip(lvl) { 279 | return 280 | } 281 | 282 | // Determine caller func 283 | pc, _, lineno, ok := runtime.Caller(DefaultCallerSkip) 284 | src := "" 285 | if ok { 286 | src = fmt.Sprintf("%s:%d", filepath.Base(runtime.FuncForPC(pc).Name()), lineno) 287 | } 288 | 289 | msg := format 290 | if len(args) > 0 { 291 | msg = fmt.Sprintf(format, args...) 292 | } 293 | 294 | // Make the log record 295 | rec := &LogRecord{ 296 | Level: lvl, 297 | Created: time.Now(), 298 | Source: src, 299 | Message: msg, 300 | } 301 | 302 | log.dispatch(rec) 303 | } 304 | 305 | // Send a closure log message internally 306 | func (log Logger) intLogc(lvl Level, closure func() string) { 307 | if log.skip(lvl) { 308 | return 309 | } 310 | 311 | // Determine caller func 312 | pc, _, lineno, ok := runtime.Caller(DefaultCallerSkip) 313 | src := "" 314 | if ok { 315 | src = fmt.Sprintf("%s:%d", filepath.Base(runtime.FuncForPC(pc).Name()), lineno) 316 | } 317 | 318 | // Make the log record 319 | rec := &LogRecord{ 320 | Level: lvl, 321 | Created: time.Now(), 322 | Source: src, 323 | Message: closure(), 324 | } 325 | 326 | log.dispatch(rec) 327 | } 328 | 329 | // Send a log message with manual level, source, and message. 330 | func (log Logger) Log(lvl Level, source, message string) { 331 | if log.skip(lvl) { 332 | return 333 | } 334 | 335 | // Make the log record 336 | rec := &LogRecord{ 337 | Level: lvl, 338 | Created: time.Now(), 339 | Source: source, 340 | Message: message, 341 | } 342 | 343 | log.dispatch(rec) 344 | } 345 | 346 | // Logf logs a formatted log message at the given log level, using the caller as 347 | // its source. 348 | func (log Logger) Logf(lvl Level, format string, args ...interface{}) { 349 | log.intLogf(lvl, format, args...) 350 | } 351 | 352 | // Logc logs a string returned by the closure at the given log level, using the caller as 353 | // its source. If no log message would be written, the closure is never called. 354 | func (log Logger) Logc(lvl Level, closure func() string) { 355 | log.intLogc(lvl, closure) 356 | } 357 | 358 | // Finest logs a message at the finest log level. 359 | // See Debug for an explanation of the arguments. 360 | func (log Logger) Finest(arg0 interface{}, args ...interface{}) { 361 | const ( 362 | lvl = FINEST 363 | ) 364 | switch first := arg0.(type) { 365 | case string: 366 | // Use the string as a format string 367 | log.intLogf(lvl, first, args...) 368 | case func() string: 369 | // Log the closure (no other arguments used) 370 | log.intLogc(lvl, first) 371 | default: 372 | // Build a format string so that it will be similar to Sprint 373 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 374 | } 375 | } 376 | 377 | // Fine logs a message at the fine log level. 378 | // See Debug for an explanation of the arguments. 379 | func (log Logger) Fine(arg0 interface{}, args ...interface{}) { 380 | const ( 381 | lvl = FINE 382 | ) 383 | switch first := arg0.(type) { 384 | case string: 385 | // Use the string as a format string 386 | log.intLogf(lvl, first, args...) 387 | case func() string: 388 | // Log the closure (no other arguments used) 389 | log.intLogc(lvl, first) 390 | default: 391 | // Build a format string so that it will be similar to Sprint 392 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 393 | } 394 | } 395 | 396 | // Debug is a utility method for debug log messages. 397 | // The behavior of Debug depends on the first argument: 398 | // - arg0 is a string 399 | // When given a string as the first argument, this behaves like Logf but with 400 | // the DEBUG log level: the first argument is interpreted as a format for the 401 | // latter arguments. 402 | // - arg0 is a func()string 403 | // When given a closure of type func()string, this logs the string returned by 404 | // the closure iff it will be logged. The closure runs at most one time. 405 | // - arg0 is interface{} 406 | // When given anything else, the log message will be each of the arguments 407 | // formatted with %v and separated by spaces (ala Sprint). 408 | func (log Logger) Debug(arg0 interface{}, args ...interface{}) { 409 | const ( 410 | lvl = DEBUG 411 | ) 412 | switch first := arg0.(type) { 413 | case string: 414 | // Use the string as a format string 415 | log.intLogf(lvl, first, args...) 416 | case func() string: 417 | // Log the closure (no other arguments used) 418 | log.intLogc(lvl, first) 419 | default: 420 | // Build a format string so that it will be similar to Sprint 421 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 422 | } 423 | } 424 | 425 | // Trace logs a message at the trace log level. 426 | // See Debug for an explanation of the arguments. 427 | func (log Logger) Trace(arg0 interface{}, args ...interface{}) { 428 | const ( 429 | lvl = TRACE 430 | ) 431 | switch first := arg0.(type) { 432 | case string: 433 | // Use the string as a format string 434 | log.intLogf(lvl, first, args...) 435 | case func() string: 436 | // Log the closure (no other arguments used) 437 | log.intLogc(lvl, first) 438 | default: 439 | // Build a format string so that it will be similar to Sprint 440 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 441 | } 442 | } 443 | 444 | // Info logs a message at the info log level. 445 | // See Debug for an explanation of the arguments. 446 | func (log Logger) Info(arg0 interface{}, args ...interface{}) { 447 | const ( 448 | lvl = INFO 449 | ) 450 | switch first := arg0.(type) { 451 | case string: 452 | // Use the string as a format string 453 | log.intLogf(lvl, first, args...) 454 | case func() string: 455 | // Log the closure (no other arguments used) 456 | log.intLogc(lvl, first) 457 | default: 458 | // Build a format string so that it will be similar to Sprint 459 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 460 | } 461 | } 462 | 463 | // Warn logs a message at the warning log level and returns the formatted error. 464 | // At the warning level and higher, there is no performance benefit if the 465 | // message is not actually logged, because all formats are processed and all 466 | // closures are executed to format the error message. 467 | // See Debug for further explanation of the arguments. 468 | func (log Logger) Warn(arg0 interface{}, args ...interface{}) error { 469 | const ( 470 | lvl = WARNING 471 | ) 472 | var msg string 473 | switch first := arg0.(type) { 474 | case string: 475 | // Use the string as a format string 476 | msg = fmt.Sprintf(first, args...) 477 | case func() string: 478 | // Log the closure (no other arguments used) 479 | msg = first() 480 | default: 481 | // Build a format string so that it will be similar to Sprint 482 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 483 | } 484 | log.intLogf(lvl, msg) 485 | return errors.New(msg) 486 | } 487 | 488 | // Error logs a message at the error log level and returns the formatted error, 489 | // See Warn for an explanation of the performance and Debug for an explanation 490 | // of the parameters. 491 | func (log Logger) Error(arg0 interface{}, args ...interface{}) error { 492 | const ( 493 | lvl = ERROR 494 | ) 495 | var msg string 496 | switch first := arg0.(type) { 497 | case string: 498 | // Use the string as a format string 499 | msg = fmt.Sprintf(first, args...) 500 | case func() string: 501 | // Log the closure (no other arguments used) 502 | msg = first() 503 | default: 504 | // Build a format string so that it will be similar to Sprint 505 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 506 | } 507 | log.intLogf(lvl, msg) 508 | return errors.New(msg) 509 | } 510 | 511 | // Critical logs a message at the critical log level and returns the formatted error, 512 | // See Warn for an explanation of the performance and Debug for an explanation 513 | // of the parameters. 514 | func (log Logger) Critical(arg0 interface{}, args ...interface{}) error { 515 | const ( 516 | lvl = CRITICAL 517 | ) 518 | var msg string 519 | switch first := arg0.(type) { 520 | case string: 521 | // Use the string as a format string 522 | msg = fmt.Sprintf(first, args...) 523 | case func() string: 524 | // Log the closure (no other arguments used) 525 | msg = first() 526 | default: 527 | // Build a format string so that it will be similar to Sprint 528 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 529 | } 530 | log.intLogf(lvl, msg) 531 | return errors.New(msg) 532 | } 533 | -------------------------------------------------------------------------------- /log4go_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "crypto/md5" 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "runtime" 13 | "testing" 14 | "time" 15 | ) 16 | 17 | const testLogFile = "_logtest.log" 18 | 19 | var now time.Time = time.Unix(0, 1234567890123456789).In(time.UTC) 20 | 21 | func newLogRecord(lvl Level, src string, msg string) *LogRecord { 22 | return &LogRecord{ 23 | Level: lvl, 24 | Source: src, 25 | Created: now, 26 | Message: msg, 27 | } 28 | } 29 | 30 | func TestELog(t *testing.T) { 31 | fmt.Printf("Testing %s\n", L4G_VERSION) 32 | lr := newLogRecord(CRITICAL, "source", "message") 33 | if lr.Level != CRITICAL { 34 | t.Errorf("Incorrect level: %d should be %d", lr.Level, CRITICAL) 35 | } 36 | if lr.Source != "source" { 37 | t.Errorf("Incorrect source: %s should be %s", lr.Source, "source") 38 | } 39 | if lr.Message != "message" { 40 | t.Errorf("Incorrect message: %s should be %s", lr.Source, "message") 41 | } 42 | } 43 | 44 | var formatTests = []struct { 45 | Test string 46 | Record *LogRecord 47 | Formats map[string]string 48 | }{ 49 | { 50 | Test: "Standard formats", 51 | Record: &LogRecord{ 52 | Level: ERROR, 53 | Source: "source", 54 | Message: "message", 55 | Created: now, 56 | }, 57 | Formats: map[string]string{ 58 | // TODO(kevlar): How can I do this so it'll work outside of PST? 59 | FORMAT_DEFAULT: "[2009/02/13 23:31:30 UTC] [EROR] (source) message\n", 60 | FORMAT_SHORT: "[23:31 13/02/09] [EROR] message\n", 61 | FORMAT_ABBREV: "[EROR] message\n", 62 | }, 63 | }, 64 | } 65 | 66 | func TestFormatLogRecord(t *testing.T) { 67 | for _, test := range formatTests { 68 | name := test.Test 69 | for fmt, want := range test.Formats { 70 | if got := FormatLogRecord(fmt, test.Record); got != want { 71 | t.Errorf("%s - %s:", name, fmt) 72 | t.Errorf(" got %q", got) 73 | t.Errorf(" want %q", want) 74 | } 75 | } 76 | } 77 | } 78 | 79 | var logRecordWriteTests = []struct { 80 | Test string 81 | Record *LogRecord 82 | Console string 83 | }{ 84 | { 85 | Test: "Normal message", 86 | Record: &LogRecord{ 87 | Level: CRITICAL, 88 | Source: "source", 89 | Message: "message", 90 | Created: now, 91 | }, 92 | Console: "[23:31:30 UTC 2009/02/13] [CRIT] [source] message", 93 | }, 94 | } 95 | 96 | func TestConsoleLogWriter(t *testing.T) { 97 | console := new(ConsoleLogWriter) 98 | 99 | console.color = false 100 | console.format = "[%T %z %D] [%L] [%S] %M" 101 | 102 | r, w := io.Pipe() 103 | console.out = w 104 | 105 | defer console.Close() 106 | 107 | buf := make([]byte, 1024) 108 | 109 | for _, test := range logRecordWriteTests { 110 | name := test.Test 111 | 112 | // Pipe write and read must be in diff routines otherwise cause dead lock 113 | go console.LogWrite(test.Record) 114 | 115 | n, _ := r.Read(buf) 116 | if got, want := string(buf[:n]), test.Console; got != (want+"\n") { 117 | t.Errorf("%s: got %q", name, got) 118 | t.Errorf("%s: want %q", name, want) 119 | } 120 | } 121 | } 122 | 123 | func TestFileLogWriter(t *testing.T) { 124 | defer func(buflen int) { 125 | DefaultBufferLength = buflen 126 | }(DefaultBufferLength) 127 | DefaultBufferLength = 0 128 | 129 | w := NewFileLogWriter(testLogFile, false) 130 | if w == nil { 131 | t.Fatalf("Invalid return: w should not be nil") 132 | } 133 | defer os.Remove(testLogFile) 134 | 135 | w.LogWrite(newLogRecord(CRITICAL, "source", "message")) 136 | w.Close() 137 | runtime.Gosched() 138 | 139 | if contents, err := ioutil.ReadFile(testLogFile); err != nil { 140 | t.Errorf("read(%q): %s", testLogFile, err) 141 | } else if len(contents) != 50 { 142 | t.Errorf("malformed filelog: %q (%d bytes)", string(contents), len(contents)) 143 | } 144 | } 145 | 146 | func TestXMLLogWriter(t *testing.T) { 147 | defer func(buflen int) { 148 | DefaultBufferLength = buflen 149 | }(DefaultBufferLength) 150 | DefaultBufferLength = 0 151 | 152 | w := NewXMLLogWriter(testLogFile, false) 153 | if w == nil { 154 | t.Fatalf("Invalid return: w should not be nil") 155 | } 156 | defer os.Remove(testLogFile) 157 | 158 | w.LogWrite(newLogRecord(CRITICAL, "source", "message")) 159 | w.Close() 160 | runtime.Gosched() 161 | 162 | if contents, err := ioutil.ReadFile(testLogFile); err != nil { 163 | t.Errorf("read(%q): %s", testLogFile, err) 164 | } else if len(contents) != 177 { 165 | t.Errorf("malformed xmllog: %q (%d bytes)", string(contents), len(contents)) 166 | } 167 | } 168 | 169 | func TestLogger(t *testing.T) { 170 | sl := NewDefaultLogger(WARNING) 171 | if sl == nil { 172 | t.Fatalf("NewDefaultLogger should never return nil") 173 | } 174 | if lw, exist := sl["stdout"]; lw == nil || exist != true { 175 | t.Fatalf("NewDefaultLogger produced invalid logger (DNE or nil)") 176 | } 177 | if sl["stdout"].Level != WARNING { 178 | t.Fatalf("NewDefaultLogger produced invalid logger (incorrect level)") 179 | } 180 | if len(sl) != 1 { 181 | t.Fatalf("NewDefaultLogger produced invalid logger (incorrect map count)") 182 | } 183 | 184 | //func (l *Logger) AddFilter(name string, level int, writer LogWriter) {} 185 | l := make(Logger) 186 | l.AddFilter("stdout", DEBUG, NewConsoleLogWriter()) 187 | if lw, exist := l["stdout"]; lw == nil || exist != true { 188 | t.Fatalf("AddFilter produced invalid logger (DNE or nil)") 189 | } 190 | if l["stdout"].Level != DEBUG { 191 | t.Fatalf("AddFilter produced invalid logger (incorrect level)") 192 | } 193 | if len(l) != 1 { 194 | t.Fatalf("AddFilter produced invalid logger (incorrect map count)") 195 | } 196 | 197 | //func (l *Logger) Warn(format string, args ...interface{}) error {} 198 | if err := l.Warn("%s %d %#v", "Warning:", 1, []int{}); err.Error() != "Warning: 1 []int{}" { 199 | t.Errorf("Warn returned invalid error: %s", err) 200 | } 201 | 202 | //func (l *Logger) Error(format string, args ...interface{}) error {} 203 | if err := l.Error("%s %d %#v", "Error:", 10, []string{}); err.Error() != "Error: 10 []string{}" { 204 | t.Errorf("Error returned invalid error: %s", err) 205 | } 206 | 207 | //func (l *Logger) Critical(format string, args ...interface{}) error {} 208 | if err := l.Critical("%s %d %#v", "Critical:", 100, []int64{}); err.Error() != "Critical: 100 []int64{}" { 209 | t.Errorf("Critical returned invalid error: %s", err) 210 | } 211 | // Already tested or basically untestable 212 | //func (l *Logger) Log(level int, source, message string) {} 213 | //func (l *Logger) Logf(level int, format string, args ...interface{}) {} 214 | //func (l *Logger) intLogf(level int, format string, args ...interface{}) string {} 215 | //func (l *Logger) Finest(format string, args ...interface{}) {} 216 | //func (l *Logger) Fine(format string, args ...interface{}) {} 217 | //func (l *Logger) Debug(format string, args ...interface{}) {} 218 | //func (l *Logger) Trace(format string, args ...interface{}) {} 219 | //func (l *Logger) Info(format string, args ...interface{}) {} 220 | } 221 | 222 | func TestLogOutput(t *testing.T) { 223 | const ( 224 | expected = "fdf3e51e444da56b4cb400f30bc47424" 225 | ) 226 | 227 | // Unbuffered output 228 | defer func(buflen int) { 229 | DefaultBufferLength = buflen 230 | }(DefaultBufferLength) 231 | DefaultBufferLength = 0 232 | 233 | l := make(Logger) 234 | 235 | // Delete and open the output log without a timestamp (for a constant md5sum) 236 | l.AddFilter("file", FINEST, NewFileLogWriter(testLogFile, false).SetFormat("[%L] %M")) 237 | defer os.Remove(testLogFile) 238 | 239 | // Send some log messages 240 | l.Log(CRITICAL, "testsrc1", fmt.Sprintf("This message is level %d", int(CRITICAL))) 241 | l.Logf(ERROR, "This message is level %v", ERROR) 242 | l.Logf(WARNING, "This message is level %s", WARNING) 243 | l.Logc(INFO, func() string { return "This message is level INFO" }) 244 | l.Trace("This message is level %d", int(TRACE)) 245 | l.Debug("This message is level %s", DEBUG) 246 | l.Fine(func() string { return fmt.Sprintf("This message is level %v", FINE) }) 247 | l.Finest("This message is level %v", FINEST) 248 | l.Finest(FINEST, "is also this message's level") 249 | 250 | l.Close() 251 | 252 | contents, err := ioutil.ReadFile(testLogFile) 253 | if err != nil { 254 | t.Fatalf("Could not read output log: %s", err) 255 | } 256 | 257 | sum := md5.New() 258 | sum.Write(contents) 259 | if sumstr := hex.EncodeToString(sum.Sum(nil)); sumstr != expected { 260 | t.Errorf("--- Log Contents:\n%s---", string(contents)) 261 | t.Fatalf("Checksum does not match: %s (expecting %s)", sumstr, expected) 262 | } 263 | } 264 | 265 | func TestCountMallocs(t *testing.T) { 266 | const N = 1 267 | var m runtime.MemStats 268 | getMallocs := func() uint64 { 269 | runtime.ReadMemStats(&m) 270 | return m.Mallocs 271 | } 272 | 273 | // Console logger 274 | sl := NewDefaultLogger(INFO) 275 | mallocs := 0 - getMallocs() 276 | for i := 0; i < N; i++ { 277 | sl.Log(WARNING, "here", "This is a WARNING message") 278 | } 279 | mallocs += getMallocs() 280 | fmt.Printf("mallocs per sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) 281 | 282 | // Console logger formatted 283 | mallocs = 0 - getMallocs() 284 | for i := 0; i < N; i++ { 285 | sl.Logf(WARNING, "%s is a log message with level %d", "This", WARNING) 286 | } 287 | mallocs += getMallocs() 288 | fmt.Printf("mallocs per sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) 289 | 290 | // Console logger (not logged) 291 | sl = NewDefaultLogger(INFO) 292 | mallocs = 0 - getMallocs() 293 | for i := 0; i < N; i++ { 294 | sl.Log(DEBUG, "here", "This is a DEBUG log message") 295 | } 296 | mallocs += getMallocs() 297 | fmt.Printf("mallocs per unlogged sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) 298 | 299 | // Console logger formatted (not logged) 300 | mallocs = 0 - getMallocs() 301 | for i := 0; i < N; i++ { 302 | sl.Logf(DEBUG, "%s is a log message with level %d", "This", DEBUG) 303 | } 304 | mallocs += getMallocs() 305 | fmt.Printf("mallocs per unlogged sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) 306 | } 307 | 308 | func TestXMLConfig(t *testing.T) { 309 | const ( 310 | configfile = "_example.xml" 311 | ) 312 | 313 | fd, err := os.Create(configfile) 314 | if err != nil { 315 | t.Fatalf("Could not open %s for writing: %s", configfile, err) 316 | } 317 | 318 | fmt.Fprintln(fd, "") 319 | fmt.Fprintln(fd, " ") 320 | fmt.Fprintln(fd, " stdout") 321 | fmt.Fprintln(fd, " console") 322 | fmt.Fprintln(fd, " ") 323 | fmt.Fprintln(fd, " DEBUG") 324 | fmt.Fprintln(fd, " true") 325 | fmt.Fprintln(fd, " [%D %T] [%L] (%S) %M") 326 | fmt.Fprintln(fd, " ") 327 | fmt.Fprintln(fd, " ") 328 | fmt.Fprintln(fd, " file") 329 | fmt.Fprintln(fd, " file") 330 | fmt.Fprintln(fd, " FINEST") 331 | fmt.Fprintln(fd, " test.log") 332 | fmt.Fprintln(fd, " ") 343 | fmt.Fprintln(fd, " [%D %T] [%L] (%S) %M") 344 | fmt.Fprintln(fd, " false ") 345 | fmt.Fprintln(fd, " 0M ") 346 | fmt.Fprintln(fd, " 0K ") 347 | fmt.Fprintln(fd, " true ") 348 | fmt.Fprintln(fd, " ") 349 | fmt.Fprintln(fd, " ") 350 | fmt.Fprintln(fd, " xmllog") 351 | fmt.Fprintln(fd, " xml") 352 | fmt.Fprintln(fd, " TRACE") 353 | fmt.Fprintln(fd, " trace.xml") 354 | fmt.Fprintln(fd, " true ") 355 | fmt.Fprintln(fd, " 100M ") 356 | fmt.Fprintln(fd, " 6K ") 357 | fmt.Fprintln(fd, " false ") 358 | fmt.Fprintln(fd, " ") 359 | fmt.Fprintln(fd, " ") 360 | fmt.Fprintln(fd, " donotopen") 361 | fmt.Fprintln(fd, " socket") 362 | fmt.Fprintln(fd, " FINEST") 363 | fmt.Fprintln(fd, " 192.168.1.255:12124 ") 364 | fmt.Fprintln(fd, " udp ") 365 | fmt.Fprintln(fd, " ") 366 | fmt.Fprintln(fd, "") 367 | fd.Close() 368 | 369 | log := make(Logger) 370 | log.LoadConfig(configfile) 371 | defer os.Remove("trace.xml") 372 | defer os.Remove("test.log") 373 | defer log.Close() 374 | 375 | // Make sure we got all loggers 376 | if len(log) != 3 { 377 | t.Fatalf("XMLConfig: Expected 3 filters, found %d", len(log)) 378 | } 379 | 380 | // Make sure they're the right keys 381 | if _, ok := log["stdout"]; !ok { 382 | t.Errorf("XMLConfig: Expected stdout logger") 383 | } 384 | if _, ok := log["file"]; !ok { 385 | t.Fatalf("XMLConfig: Expected file logger") 386 | } 387 | if _, ok := log["xmllog"]; !ok { 388 | t.Fatalf("XMLConfig: Expected xmllog logger") 389 | } 390 | 391 | // Make sure they're the right type 392 | if _, ok := log["stdout"].LogWriter.(*ConsoleLogWriter); !ok { 393 | t.Fatalf("XMLConfig: Expected stdout to be ConsoleLogWriter, found %T", log["stdout"].LogWriter) 394 | } 395 | if _, ok := log["file"].LogWriter.(*FileLogWriter); !ok { 396 | t.Fatalf("XMLConfig: Expected file to be *FileLogWriter, found %T", log["file"].LogWriter) 397 | } 398 | if _, ok := log["xmllog"].LogWriter.(*FileLogWriter); !ok { 399 | t.Fatalf("XMLConfig: Expected xmllog to be *FileLogWriter, found %T", log["xmllog"].LogWriter) 400 | } 401 | 402 | // Make sure levels are set 403 | if lvl := log["stdout"].Level; lvl != DEBUG { 404 | t.Errorf("XMLConfig: Expected stdout to be set to level %d, found %d", DEBUG, lvl) 405 | } 406 | if lvl := log["file"].Level; lvl != FINEST { 407 | t.Errorf("XMLConfig: Expected file to be set to level %d, found %d", FINEST, lvl) 408 | } 409 | if lvl := log["xmllog"].Level; lvl != TRACE { 410 | t.Errorf("XMLConfig: Expected xmllog to be set to level %d, found %d", TRACE, lvl) 411 | } 412 | 413 | // Make sure the w is open and points to the right file 414 | if fname := log["file"].LogWriter.(*FileLogWriter).file.Name(); fname != "test.log" { 415 | t.Errorf("XMLConfig: Expected file to have opened %s, found %s", "test.log", fname) 416 | } 417 | 418 | // Make sure the XLW is open and points to the right file 419 | if fname := log["xmllog"].LogWriter.(*FileLogWriter).file.Name(); fname != "trace.xml" { 420 | t.Errorf("XMLConfig: Expected xmllog to have opened %s, found %s", "trace.xml", fname) 421 | } 422 | 423 | // Move XML log file 424 | os.Rename(configfile, "examples/"+configfile) // Keep this so that an example with the documentation is available 425 | } 426 | 427 | func BenchmarkFormatLogRecord(b *testing.B) { 428 | const updateEvery = 1 429 | rec := &LogRecord{ 430 | Level: CRITICAL, 431 | Created: now, 432 | Source: "source", 433 | Message: "message", 434 | } 435 | for i := 0; i < b.N; i++ { 436 | rec.Created = rec.Created.Add(1 * time.Second / updateEvery) 437 | if i%2 == 0 { 438 | FormatLogRecord(FORMAT_DEFAULT, rec) 439 | } else { 440 | FormatLogRecord(FORMAT_SHORT, rec) 441 | } 442 | } 443 | } 444 | 445 | func BenchmarkConsoleLog(b *testing.B) { 446 | /* This doesn't seem to work on OS X 447 | sink, err := os.Open(os.DevNull) 448 | if err != nil { 449 | panic(err) 450 | } 451 | if err := syscall.Dup2(int(sink.Fd()), syscall.Stdout); err != nil { 452 | panic(err) 453 | } 454 | */ 455 | 456 | stdout = ioutil.Discard 457 | sl := NewDefaultLogger(INFO) 458 | for i := 0; i < b.N; i++ { 459 | sl.Log(WARNING, "here", "This is a log message") 460 | } 461 | } 462 | 463 | func BenchmarkConsoleNotLogged(b *testing.B) { 464 | sl := NewDefaultLogger(INFO) 465 | for i := 0; i < b.N; i++ { 466 | sl.Log(DEBUG, "here", "This is a log message") 467 | } 468 | } 469 | 470 | func BenchmarkConsoleUtilLog(b *testing.B) { 471 | sl := NewDefaultLogger(INFO) 472 | for i := 0; i < b.N; i++ { 473 | sl.Info("%s is a log message", "This") 474 | } 475 | } 476 | 477 | func BenchmarkConsoleUtilNotLog(b *testing.B) { 478 | sl := NewDefaultLogger(INFO) 479 | for i := 0; i < b.N; i++ { 480 | sl.Debug("%s is a log message", "This") 481 | } 482 | } 483 | 484 | func BenchmarkFileLog(b *testing.B) { 485 | sl := make(Logger) 486 | b.StopTimer() 487 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 488 | b.StartTimer() 489 | for i := 0; i < b.N; i++ { 490 | sl.Log(WARNING, "here", "This is a log message") 491 | } 492 | b.StopTimer() 493 | os.Remove("benchlog.log") 494 | } 495 | 496 | func BenchmarkFileNotLogged(b *testing.B) { 497 | sl := make(Logger) 498 | b.StopTimer() 499 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 500 | b.StartTimer() 501 | for i := 0; i < b.N; i++ { 502 | sl.Log(DEBUG, "here", "This is a log message") 503 | } 504 | b.StopTimer() 505 | os.Remove("benchlog.log") 506 | } 507 | 508 | func BenchmarkFileUtilLog(b *testing.B) { 509 | sl := make(Logger) 510 | b.StopTimer() 511 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 512 | b.StartTimer() 513 | for i := 0; i < b.N; i++ { 514 | sl.Info("%s is a log message", "This") 515 | } 516 | b.StopTimer() 517 | os.Remove("benchlog.log") 518 | } 519 | 520 | func BenchmarkFileUtilNotLog(b *testing.B) { 521 | sl := make(Logger) 522 | b.StopTimer() 523 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 524 | b.StartTimer() 525 | for i := 0; i < b.N; i++ { 526 | sl.Debug("%s is a log message", "This") 527 | } 528 | b.StopTimer() 529 | os.Remove("benchlog.log") 530 | } 531 | 532 | // Benchmark results (windows amd64 10g) 533 | // BenchmarkFormatLogRecord-4 300000 4433 ns/op 534 | // BenchmarkConsoleLog-4 1000000 1746 ns/op 535 | // BenchmarkConsoleNotLogged-4 20000000 97.3 ns/op 536 | // BenchmarkConsoleUtilLog-4 300000 3783 ns/op 537 | // BenchmarkConsoleUtilNotLog-4 20000000 102 ns/op 538 | // BenchmarkFileLog-4 200000 9910 ns/op 539 | // BenchmarkFileNotLogged-4 20000000 94.9 ns/op 540 | // BenchmarkFileUtilLog-4 200000 9610 ns/op 541 | // BenchmarkFileUtilNotLog-4 20000000 103 ns/op 542 | 543 | --------------------------------------------------------------------------------