├── .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 |
--------------------------------------------------------------------------------