├── .gitignore ├── LICENSE ├── README ├── config.go ├── examples ├── ConsoleLogWriter_Manual.go ├── FileLogWriter_Manual.go ├── SimpleNetLogServer.go ├── SocketLogWriter_Manual.go ├── XMLConfigurationExample.go └── example.xml ├── filelog.go ├── log4go.go ├── log4go_test.go ├── pattlog.go ├── socklog.go ├── termlog.go └── wrapper.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw[op] 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | # This is an unmaintained fork, left only so it doesn't break imports. 2 | 3 | Please see http://log4go.googlecode.com/ 4 | 5 | Installation: 6 | - Run `goinstall log4go.googlecode.com/hg` 7 | 8 | Usage: 9 | - Add the following import: 10 | import l4g "log4go.googlecode.com/hg" 11 | 12 | Acknowledgements: 13 | - pomack 14 | For providing awesome patches to bring log4go up to the latest Go spec 15 | -------------------------------------------------------------------------------- /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 | ) 13 | 14 | type xmlProperty struct { 15 | Name string `xml:"name,attr"` 16 | Value string `xml:",chardata"` 17 | } 18 | 19 | type xmlFilter struct { 20 | Enabled string `xml:"enabled,attr"` 21 | Tag string `xml:"tag"` 22 | Level string `xml:"level"` 23 | Type string `xml:"type"` 24 | Property []xmlProperty `xml:"property"` 25 | } 26 | 27 | type xmlLoggerConfig struct { 28 | Filter []xmlFilter `xml:"filter"` 29 | } 30 | 31 | // Load XML configuration; see examples/example.xml for documentation 32 | func (log Logger) LoadConfiguration(filename string) { 33 | log.Close() 34 | 35 | // Open the configuration file 36 | fd, err := os.Open(filename) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err) 39 | os.Exit(1) 40 | } 41 | 42 | contents, err := ioutil.ReadAll(fd) 43 | if err != nil { 44 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err) 45 | os.Exit(1) 46 | } 47 | 48 | xc := new(xmlLoggerConfig) 49 | if err := xml.Unmarshal(contents, xc); err != nil { 50 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err) 51 | os.Exit(1) 52 | } 53 | 54 | for _, xmlfilt := range xc.Filter { 55 | var filt LogWriter 56 | var lvl Level 57 | bad, good, enabled := false, true, false 58 | 59 | // Check required children 60 | if len(xmlfilt.Enabled) == 0 { 61 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename) 62 | bad = true 63 | } else { 64 | enabled = xmlfilt.Enabled != "false" 65 | } 66 | if len(xmlfilt.Tag) == 0 { 67 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename) 68 | bad = true 69 | } 70 | if len(xmlfilt.Type) == 0 { 71 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename) 72 | bad = true 73 | } 74 | if len(xmlfilt.Level) == 0 { 75 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename) 76 | bad = true 77 | } 78 | 79 | switch xmlfilt.Level { 80 | case "FINEST": 81 | lvl = FINEST 82 | case "FINE": 83 | lvl = FINE 84 | case "DEBUG": 85 | lvl = DEBUG 86 | case "TRACE": 87 | lvl = TRACE 88 | case "INFO": 89 | lvl = INFO 90 | case "WARNING": 91 | lvl = WARNING 92 | case "ERROR": 93 | lvl = ERROR 94 | case "CRITICAL": 95 | lvl = CRITICAL 96 | default: 97 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level) 98 | bad = true 99 | } 100 | 101 | // Just so all of the required attributes are errored at the same time if missing 102 | if bad { 103 | os.Exit(1) 104 | } 105 | 106 | switch xmlfilt.Type { 107 | case "console": 108 | filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled) 109 | case "file": 110 | filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled) 111 | case "xml": 112 | filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled) 113 | case "socket": 114 | filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled) 115 | default: 116 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type) 117 | os.Exit(1) 118 | } 119 | 120 | // Just so all of the required params are errored at the same time if wrong 121 | if !good { 122 | os.Exit(1) 123 | } 124 | 125 | // If we're disabled (syntax and correctness checks only), don't add to logger 126 | if !enabled { 127 | continue 128 | } 129 | 130 | log[xmlfilt.Tag] = &Filter{lvl, filt} 131 | } 132 | } 133 | 134 | func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) { 135 | 136 | format := "[%D %T] [%L] (%S) %M" 137 | 138 | // Parse properties 139 | for _, prop := range props { 140 | switch prop.Name { 141 | case "format": 142 | format = strings.Trim(prop.Value, " \r\n") 143 | default: 144 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename) 145 | } 146 | } 147 | 148 | // If it's disabled, we're just checking syntax 149 | if !enabled { 150 | return nil, true 151 | } 152 | 153 | clw := NewConsoleLogWriter() 154 | clw.SetFormat(format) 155 | 156 | return clw, true 157 | } 158 | 159 | // Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024) 160 | func strToNumSuffix(str string, mult int) int { 161 | num := 1 162 | if len(str) > 1 { 163 | switch str[len(str)-1] { 164 | case 'G', 'g': 165 | num *= mult 166 | fallthrough 167 | case 'M', 'm': 168 | num *= mult 169 | fallthrough 170 | case 'K', 'k': 171 | num *= mult 172 | str = str[0 : len(str)-1] 173 | } 174 | } 175 | parsed, _ := strconv.Atoi(str) 176 | return parsed * num 177 | } 178 | func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { 179 | file := "" 180 | format := "[%D %T] [%L] (%S) %M" 181 | maxlines := 0 182 | maxsize := 0 183 | daily := false 184 | rotate := false 185 | 186 | // Parse properties 187 | for _, prop := range props { 188 | switch prop.Name { 189 | case "filename": 190 | file = strings.Trim(prop.Value, " \r\n") 191 | case "format": 192 | format = strings.Trim(prop.Value, " \r\n") 193 | case "maxlines": 194 | maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) 195 | case "maxsize": 196 | maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) 197 | case "daily": 198 | daily = strings.Trim(prop.Value, " \r\n") != "false" 199 | case "rotate": 200 | rotate = strings.Trim(prop.Value, " \r\n") != "false" 201 | default: 202 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) 203 | } 204 | } 205 | 206 | // Check properties 207 | if len(file) == 0 { 208 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename) 209 | return nil, false 210 | } 211 | 212 | // If it's disabled, we're just checking syntax 213 | if !enabled { 214 | return nil, true 215 | } 216 | 217 | flw := NewFileLogWriter(file, rotate) 218 | flw.SetFormat(format) 219 | flw.SetRotateLines(maxlines) 220 | flw.SetRotateSize(maxsize) 221 | flw.SetRotateDaily(daily) 222 | return flw, true 223 | } 224 | 225 | func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) { 226 | file := "" 227 | maxrecords := 0 228 | maxsize := 0 229 | daily := false 230 | rotate := false 231 | 232 | // Parse properties 233 | for _, prop := range props { 234 | switch prop.Name { 235 | case "filename": 236 | file = strings.Trim(prop.Value, " \r\n") 237 | case "maxrecords": 238 | maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000) 239 | case "maxsize": 240 | maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024) 241 | case "daily": 242 | daily = strings.Trim(prop.Value, " \r\n") != "false" 243 | case "rotate": 244 | rotate = strings.Trim(prop.Value, " \r\n") != "false" 245 | default: 246 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename) 247 | } 248 | } 249 | 250 | // Check properties 251 | if len(file) == 0 { 252 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename) 253 | return nil, false 254 | } 255 | 256 | // If it's disabled, we're just checking syntax 257 | if !enabled { 258 | return nil, true 259 | } 260 | 261 | xlw := NewXMLLogWriter(file, rotate) 262 | xlw.SetRotateLines(maxrecords) 263 | xlw.SetRotateSize(maxsize) 264 | xlw.SetRotateDaily(daily) 265 | return xlw, true 266 | } 267 | 268 | func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) { 269 | endpoint := "" 270 | protocol := "udp" 271 | 272 | // Parse properties 273 | for _, prop := range props { 274 | switch prop.Name { 275 | case "endpoint": 276 | endpoint = strings.Trim(prop.Value, " \r\n") 277 | case "protocol": 278 | protocol = strings.Trim(prop.Value, " \r\n") 279 | default: 280 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename) 281 | } 282 | } 283 | 284 | // Check properties 285 | if len(endpoint) == 0 { 286 | fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename) 287 | return nil, false 288 | } 289 | 290 | // If it's disabled, we're just checking syntax 291 | if !enabled { 292 | return nil, true 293 | } 294 | 295 | return NewSocketLogWriter(protocol, endpoint), true 296 | } 297 | -------------------------------------------------------------------------------- /examples/ConsoleLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import l4g "code.google.com/p/log4go" 8 | 9 | func main() { 10 | log := l4g.NewLogger() 11 | defer log.Close() 12 | log.AddFilter("stdout", l4g.DEBUG, l4g.NewConsoleLogWriter()) 13 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 14 | } 15 | -------------------------------------------------------------------------------- /examples/FileLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | ) 10 | 11 | import l4g "code.google.com/p/log4go" 12 | 13 | const ( 14 | filename = "flw.log" 15 | ) 16 | 17 | func main() { 18 | // Get a new logger instance 19 | log := l4g.NewLogger() 20 | 21 | // Create a default logger that is logging messages of FINE or higher 22 | log.AddFilter("file", l4g.FINE, l4g.NewFileLogWriter(filename, false)) 23 | log.Close() 24 | 25 | /* Can also specify manually via the following: (these are the defaults) */ 26 | flw := l4g.NewFileLogWriter(filename, false) 27 | flw.SetFormat("[%D %T] [%L] (%S) %M") 28 | flw.SetRotate(false) 29 | flw.SetRotateSize(0) 30 | flw.SetRotateLines(0) 31 | flw.SetRotateDaily(false) 32 | log.AddFilter("file", l4g.FINE, flw) 33 | 34 | // Log some experimental messages 35 | log.Finest("Everything is created now (notice that I will not be printing to the file)") 36 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 37 | log.Critical("Time to close out!") 38 | 39 | // Close the log 40 | log.Close() 41 | 42 | // Print what was logged to the file (yes, I know I'm skipping error checking) 43 | fd, _ := os.Open(filename) 44 | in := bufio.NewReader(fd) 45 | fmt.Print("Messages logged to file were: (line numbers not included)\n") 46 | for lineno := 1; ; lineno++ { 47 | line, err := in.ReadString('\n') 48 | if err == io.EOF { 49 | break 50 | } 51 | fmt.Printf("%3d:\t%s", lineno, line) 52 | } 53 | fd.Close() 54 | 55 | // Remove the file so it's not lying around 56 | os.Remove(filename) 57 | } 58 | -------------------------------------------------------------------------------- /examples/SimpleNetLogServer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | ) 9 | 10 | var ( 11 | port = flag.String("p", "12124", "Port number to listen on") 12 | ) 13 | 14 | func e(err error) { 15 | if err != nil { 16 | fmt.Printf("Erroring out: %s\n", err) 17 | os.Exit(1) 18 | } 19 | } 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | // Bind to the port 25 | bind, err := net.ResolveUDPAddr("0.0.0.0:" + *port) 26 | e(err) 27 | 28 | // Create listener 29 | listener, err := net.ListenUDP("udp", bind) 30 | e(err) 31 | 32 | fmt.Printf("Listening to port %s...\n", *port) 33 | for { 34 | // read into a new buffer 35 | buffer := make([]byte, 1024) 36 | _, _, err := listener.ReadFrom(buffer) 37 | e(err) 38 | 39 | // log to standard output 40 | fmt.Println(string(buffer)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/SocketLogWriter_Manual.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | import l4g "code.google.com/p/log4go" 8 | 9 | func main() { 10 | log := l4g.NewLogger() 11 | log.AddFilter("network", l4g.FINEST, l4g.NewSocketLogWriter("udp", "192.168.1.255:12124")) 12 | 13 | // Run `nc -u -l -p 12124` or similar before you run this to see the following message 14 | log.Info("The time is now: %s", time.Now().Format("15:04:05 MST 2006/01/02")) 15 | 16 | // This makes sure the output stream buffer is written 17 | log.Close() 18 | } 19 | -------------------------------------------------------------------------------- /examples/XMLConfigurationExample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import l4g "code.google.com/p/log4go" 4 | 5 | func main() { 6 | // Load the configuration (isn't this easy?) 7 | l4g.LoadConfiguration("example.xml") 8 | 9 | // And now we're ready! 10 | l4g.Finest("This will only go to those of you really cool UDP kids! If you change enabled=true.") 11 | l4g.Debug("Oh no! %d + %d = %d!", 2, 2, 2+2) 12 | l4g.Info("About that time, eh chaps?") 13 | } 14 | -------------------------------------------------------------------------------- /examples/example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | file 10 | file 11 | FINEST 12 | test.log 13 | 24 | [%D %T] [%L] (%S) %M 25 | false 26 | 0M 27 | 0K 28 | true 29 | 30 | 31 | xmllog 32 | xml 33 | TRACE 34 | trace.xml 35 | true 36 | 100M 37 | 6K 38 | false 39 | 40 | 41 | donotopen 42 | socket 43 | FINEST 44 | 192.168.1.255:12124 45 | udp 46 | 47 | 48 | -------------------------------------------------------------------------------- /filelog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // This log writer sends output to a file 12 | type FileLogWriter struct { 13 | rec chan *LogRecord 14 | rot chan bool 15 | 16 | // The opened file 17 | filename string 18 | file *os.File 19 | 20 | // The logging format 21 | format string 22 | 23 | // File header/trailer 24 | header, trailer string 25 | 26 | // Rotate at linecount 27 | maxlines int 28 | maxlines_curlines int 29 | 30 | // Rotate at size 31 | maxsize int 32 | maxsize_cursize int 33 | 34 | // Rotate daily 35 | daily bool 36 | daily_opendate int 37 | 38 | // Keep old logfiles (.001, .002, etc) 39 | rotate bool 40 | maxbackup int 41 | } 42 | 43 | // This is the FileLogWriter's output method 44 | func (w *FileLogWriter) LogWrite(rec *LogRecord) { 45 | w.rec <- rec 46 | } 47 | 48 | func (w *FileLogWriter) Close() { 49 | close(w.rec) 50 | w.file.Sync() 51 | } 52 | 53 | // NewFileLogWriter creates a new LogWriter which writes to the given file and 54 | // has rotation enabled if rotate is true. 55 | // 56 | // If rotate is true, any time a new log file is opened, the old one is renamed 57 | // with a .### extension to preserve it. The various Set* methods can be used 58 | // to configure log rotation based on lines, size, and daily. 59 | // 60 | // The standard log-line format is: 61 | // [%D %T] [%L] (%S) %M 62 | func NewFileLogWriter(fname string, rotate bool) *FileLogWriter { 63 | w := &FileLogWriter{ 64 | rec: make(chan *LogRecord, LogBufferLength), 65 | rot: make(chan bool), 66 | filename: fname, 67 | format: "[%D %T] [%L] (%S) %M", 68 | rotate: rotate, 69 | maxbackup: 999, 70 | } 71 | 72 | // open the file for the first time 73 | if err := w.intRotate(); err != nil { 74 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 75 | return nil 76 | } 77 | 78 | go func() { 79 | defer func() { 80 | if w.file != nil { 81 | fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) 82 | w.file.Close() 83 | } 84 | }() 85 | 86 | for { 87 | select { 88 | case <-w.rot: 89 | if err := w.intRotate(); err != nil { 90 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 91 | return 92 | } 93 | case rec, ok := <-w.rec: 94 | if !ok { 95 | return 96 | } 97 | now := time.Now() 98 | if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) || 99 | (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) || 100 | (w.daily && now.Day() != w.daily_opendate) { 101 | if err := w.intRotate(); err != nil { 102 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 103 | return 104 | } 105 | } 106 | 107 | // Perform the write 108 | n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec)) 109 | if err != nil { 110 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) 111 | return 112 | } 113 | 114 | // Update the counts 115 | w.maxlines_curlines++ 116 | w.maxsize_cursize += n 117 | } 118 | } 119 | }() 120 | 121 | return w 122 | } 123 | 124 | // Request that the logs rotate 125 | func (w *FileLogWriter) Rotate() { 126 | w.rot <- true 127 | } 128 | 129 | // If this is called in a threaded context, it MUST be synchronized 130 | func (w *FileLogWriter) intRotate() error { 131 | // Close any log file that may be open 132 | if w.file != nil { 133 | fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()})) 134 | w.file.Close() 135 | } 136 | 137 | // If we are keeping log files, move it to the next available number 138 | if w.rotate { 139 | _, err := os.Lstat(w.filename) 140 | if err == nil { // file exists 141 | // Find the next available number 142 | num := 1 143 | fname := "" 144 | if w.daily && time.Now().Day() != w.daily_opendate { 145 | yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") 146 | 147 | for ; err == nil && num <= w.maxbackup; num++ { 148 | fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num) 149 | _, err = os.Lstat(fname) 150 | } 151 | // return error if the last file checked still existed 152 | if err == nil { 153 | return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename) 154 | } 155 | } else { 156 | num = w.maxbackup - 1 157 | for ; num >= 1; num-- { 158 | fname = w.filename + fmt.Sprintf(".%d", num) 159 | nfname := w.filename + fmt.Sprintf(".%d", num+1) 160 | _, err = os.Lstat(fname) 161 | if err == nil { 162 | os.Rename(fname, nfname) 163 | } 164 | } 165 | } 166 | 167 | w.file.Close() 168 | // Rename the file to its newfound home 169 | err = os.Rename(w.filename, fname) 170 | if err != nil { 171 | return fmt.Errorf("Rotate: %s\n", err) 172 | } 173 | } 174 | } 175 | 176 | // Open the log file 177 | fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) 178 | if err != nil { 179 | return err 180 | } 181 | w.file = fd 182 | 183 | now := time.Now() 184 | fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now})) 185 | 186 | // Set the daily open date to the current date 187 | w.daily_opendate = now.Day() 188 | 189 | // initialize rotation values 190 | w.maxlines_curlines = 0 191 | w.maxsize_cursize = 0 192 | 193 | return nil 194 | } 195 | 196 | // Set the logging format (chainable). Must be called before the first log 197 | // message is written. 198 | func (w *FileLogWriter) SetFormat(format string) *FileLogWriter { 199 | w.format = format 200 | return w 201 | } 202 | 203 | // Set the logfile header and footer (chainable). Must be called before the first log 204 | // message is written. These are formatted similar to the FormatLogRecord (e.g. 205 | // you can use %D and %T in your header/footer for date and time). 206 | func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter { 207 | w.header, w.trailer = head, foot 208 | if w.maxlines_curlines == 0 { 209 | fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()})) 210 | } 211 | return w 212 | } 213 | 214 | // Set rotate at linecount (chainable). Must be called before the first log 215 | // message is written. 216 | func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter { 217 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines) 218 | w.maxlines = maxlines 219 | return w 220 | } 221 | 222 | // Set rotate at size (chainable). Must be called before the first log message 223 | // is written. 224 | func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter { 225 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize) 226 | w.maxsize = maxsize 227 | return w 228 | } 229 | 230 | // Set rotate daily (chainable). Must be called before the first log message is 231 | // written. 232 | func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter { 233 | //fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily) 234 | w.daily = daily 235 | return w 236 | } 237 | 238 | // Set max backup files. Must be called before the first log message 239 | // is written. 240 | func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter { 241 | w.maxbackup = maxbackup 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 | // NewXMLLogWriter is a utility method for creating a FileLogWriter set up to 256 | // output XML record log messages instead of line-based ones. 257 | func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter { 258 | return NewFileLogWriter(fname, rotate).SetFormat( 259 | ` 260 | %D %T 261 | %S 262 | %M 263 | `).SetHeadFoot("", "") 264 | } 265 | -------------------------------------------------------------------------------- /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 | "runtime" 53 | "strings" 54 | "time" 55 | ) 56 | 57 | // Version information 58 | const ( 59 | L4G_VERSION = "log4go-v3.0.1" 60 | L4G_MAJOR = 3 61 | L4G_MINOR = 0 62 | L4G_BUILD = 1 63 | ) 64 | 65 | /****** Constants ******/ 66 | 67 | // These are the integer logging levels used by the logger 68 | type Level int 69 | 70 | const ( 71 | FINEST Level = iota 72 | FINE 73 | DEBUG 74 | TRACE 75 | INFO 76 | WARNING 77 | ERROR 78 | CRITICAL 79 | ) 80 | 81 | // Logging level strings 82 | var ( 83 | levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"} 84 | ) 85 | 86 | func (l Level) String() string { 87 | if l < 0 || int(l) > len(levelStrings) { 88 | return "UNKNOWN" 89 | } 90 | return levelStrings[int(l)] 91 | } 92 | 93 | /****** Variables ******/ 94 | var ( 95 | // LogBufferLength specifies how many log messages a particular log4go 96 | // logger can buffer at a time before writing them. 97 | LogBufferLength = 32 98 | ) 99 | 100 | /****** LogRecord ******/ 101 | 102 | // A LogRecord contains all of the pertinent information for each message 103 | type LogRecord struct { 104 | Level Level // The log level 105 | Created time.Time // The time at which the log message was created (nanoseconds) 106 | Source string // The message source 107 | Message string // The log message 108 | } 109 | 110 | /****** LogWriter ******/ 111 | 112 | // This is an interface for anything that should be able to write logs 113 | type LogWriter interface { 114 | // This will be called to log a LogRecord message. 115 | LogWrite(rec *LogRecord) 116 | 117 | // This should clean up anything lingering about the LogWriter, as it is called before 118 | // the LogWriter is removed. LogWrite should not be called after Close. 119 | Close() 120 | } 121 | 122 | /****** Logger ******/ 123 | 124 | // A Filter represents the log level below which no log records are written to 125 | // the associated LogWriter. 126 | type Filter struct { 127 | Level Level 128 | LogWriter 129 | } 130 | 131 | // A Logger represents a collection of Filters through which log messages are 132 | // written. 133 | type Logger map[string]*Filter 134 | 135 | // Create a new logger. 136 | // 137 | // DEPRECATED: Use make(Logger) instead. 138 | func NewLogger() Logger { 139 | os.Stderr.WriteString("warning: use of deprecated NewLogger\n") 140 | return make(Logger) 141 | } 142 | 143 | // Create a new logger with a "stdout" filter configured to send log messages at 144 | // or above lvl to standard output. 145 | // 146 | // DEPRECATED: use NewDefaultLogger instead. 147 | func NewConsoleLogger(lvl Level) Logger { 148 | os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n") 149 | return Logger{ 150 | "stdout": &Filter{lvl, NewConsoleLogWriter()}, 151 | } 152 | } 153 | 154 | // Create a new logger with a "stdout" filter configured to send log messages at 155 | // or above lvl to standard output. 156 | func NewDefaultLogger(lvl Level) Logger { 157 | return Logger{ 158 | "stdout": &Filter{lvl, NewConsoleLogWriter()}, 159 | } 160 | } 161 | 162 | // Closes all log writers in preparation for exiting the program or a 163 | // reconfiguration of logging. Calling this is not really imperative, unless 164 | // you want to guarantee that all log messages are written. Close removes 165 | // all filters (and thus all LogWriters) from the logger. 166 | func (log Logger) Close() { 167 | // Close all open loggers 168 | for name, filt := range log { 169 | filt.Close() 170 | delete(log, name) 171 | } 172 | } 173 | 174 | // Add a new LogWriter to the Logger which will only log messages at lvl or 175 | // higher. This function should not be called from multiple goroutines. 176 | // Returns the logger for chaining. 177 | func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger { 178 | log[name] = &Filter{lvl, writer} 179 | return log 180 | } 181 | 182 | /******* Logging *******/ 183 | // Send a formatted log message internally 184 | func (log Logger) intLogf(lvl Level, format string, args ...interface{}) { 185 | skip := true 186 | 187 | // Determine if any logging will be done 188 | for _, filt := range log { 189 | if lvl >= filt.Level { 190 | skip = false 191 | break 192 | } 193 | } 194 | if skip { 195 | return 196 | } 197 | 198 | // Determine caller func 199 | pc, _, lineno, ok := runtime.Caller(2) 200 | src := "" 201 | if ok { 202 | src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) 203 | } 204 | 205 | msg := format 206 | if len(args) > 0 { 207 | msg = fmt.Sprintf(format, args...) 208 | } 209 | 210 | // Make the log record 211 | rec := &LogRecord{ 212 | Level: lvl, 213 | Created: time.Now(), 214 | Source: src, 215 | Message: msg, 216 | } 217 | 218 | // Dispatch the logs 219 | for _, filt := range log { 220 | if lvl < filt.Level { 221 | continue 222 | } 223 | filt.LogWrite(rec) 224 | } 225 | } 226 | 227 | // Send a closure log message internally 228 | func (log Logger) intLogc(lvl Level, closure func() string) { 229 | skip := true 230 | 231 | // Determine if any logging will be done 232 | for _, filt := range log { 233 | if lvl >= filt.Level { 234 | skip = false 235 | break 236 | } 237 | } 238 | if skip { 239 | return 240 | } 241 | 242 | // Determine caller func 243 | pc, _, lineno, ok := runtime.Caller(2) 244 | src := "" 245 | if ok { 246 | src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno) 247 | } 248 | 249 | // Make the log record 250 | rec := &LogRecord{ 251 | Level: lvl, 252 | Created: time.Now(), 253 | Source: src, 254 | Message: closure(), 255 | } 256 | 257 | // Dispatch the logs 258 | for _, filt := range log { 259 | if lvl < filt.Level { 260 | continue 261 | } 262 | filt.LogWrite(rec) 263 | } 264 | } 265 | 266 | // Send a log message with manual level, source, and message. 267 | func (log Logger) Log(lvl Level, source, message string) { 268 | skip := true 269 | 270 | // Determine if any logging will be done 271 | for _, filt := range log { 272 | if lvl >= filt.Level { 273 | skip = false 274 | break 275 | } 276 | } 277 | if skip { 278 | return 279 | } 280 | 281 | // Make the log record 282 | rec := &LogRecord{ 283 | Level: lvl, 284 | Created: time.Now(), 285 | Source: source, 286 | Message: message, 287 | } 288 | 289 | // Dispatch the logs 290 | for _, filt := range log { 291 | if lvl < filt.Level { 292 | continue 293 | } 294 | filt.LogWrite(rec) 295 | } 296 | } 297 | 298 | // Logf logs a formatted log message at the given log level, using the caller as 299 | // its source. 300 | func (log Logger) Logf(lvl Level, format string, args ...interface{}) { 301 | log.intLogf(lvl, format, args...) 302 | } 303 | 304 | // Logc logs a string returned by the closure at the given log level, using the caller as 305 | // its source. If no log message would be written, the closure is never called. 306 | func (log Logger) Logc(lvl Level, closure func() string) { 307 | log.intLogc(lvl, closure) 308 | } 309 | 310 | // Finest logs a message at the finest log level. 311 | // See Debug for an explanation of the arguments. 312 | func (log Logger) Finest(arg0 interface{}, args ...interface{}) { 313 | const ( 314 | lvl = FINEST 315 | ) 316 | switch first := arg0.(type) { 317 | case string: 318 | // Use the string as a format string 319 | log.intLogf(lvl, first, args...) 320 | case func() string: 321 | // Log the closure (no other arguments used) 322 | log.intLogc(lvl, first) 323 | default: 324 | // Build a format string so that it will be similar to Sprint 325 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 326 | } 327 | } 328 | 329 | // Fine logs a message at the fine log level. 330 | // See Debug for an explanation of the arguments. 331 | func (log Logger) Fine(arg0 interface{}, args ...interface{}) { 332 | const ( 333 | lvl = FINE 334 | ) 335 | switch first := arg0.(type) { 336 | case string: 337 | // Use the string as a format string 338 | log.intLogf(lvl, first, args...) 339 | case func() string: 340 | // Log the closure (no other arguments used) 341 | log.intLogc(lvl, first) 342 | default: 343 | // Build a format string so that it will be similar to Sprint 344 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 345 | } 346 | } 347 | 348 | // Debug is a utility method for debug log messages. 349 | // The behavior of Debug depends on the first argument: 350 | // - arg0 is a string 351 | // When given a string as the first argument, this behaves like Logf but with 352 | // the DEBUG log level: the first argument is interpreted as a format for the 353 | // latter arguments. 354 | // - arg0 is a func()string 355 | // When given a closure of type func()string, this logs the string returned by 356 | // the closure iff it will be logged. The closure runs at most one time. 357 | // - arg0 is interface{} 358 | // When given anything else, the log message will be each of the arguments 359 | // formatted with %v and separated by spaces (ala Sprint). 360 | func (log Logger) Debug(arg0 interface{}, args ...interface{}) { 361 | const ( 362 | lvl = DEBUG 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 | // Trace logs a message at the trace log level. 378 | // See Debug for an explanation of the arguments. 379 | func (log Logger) Trace(arg0 interface{}, args ...interface{}) { 380 | const ( 381 | lvl = TRACE 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 | // Info logs a message at the info log level. 397 | // See Debug for an explanation of the arguments. 398 | func (log Logger) Info(arg0 interface{}, args ...interface{}) { 399 | const ( 400 | lvl = INFO 401 | ) 402 | switch first := arg0.(type) { 403 | case string: 404 | // Use the string as a format string 405 | log.intLogf(lvl, first, args...) 406 | case func() string: 407 | // Log the closure (no other arguments used) 408 | log.intLogc(lvl, first) 409 | default: 410 | // Build a format string so that it will be similar to Sprint 411 | log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 412 | } 413 | } 414 | 415 | // Warn logs a message at the warning log level and returns the formatted error. 416 | // At the warning level and higher, there is no performance benefit if the 417 | // message is not actually logged, because all formats are processed and all 418 | // closures are executed to format the error message. 419 | // See Debug for further explanation of the arguments. 420 | func (log Logger) Warn(arg0 interface{}, args ...interface{}) error { 421 | const ( 422 | lvl = WARNING 423 | ) 424 | var msg string 425 | switch first := arg0.(type) { 426 | case string: 427 | // Use the string as a format string 428 | msg = fmt.Sprintf(first, args...) 429 | case func() string: 430 | // Log the closure (no other arguments used) 431 | msg = first() 432 | default: 433 | // Build a format string so that it will be similar to Sprint 434 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 435 | } 436 | log.intLogf(lvl, msg) 437 | return errors.New(msg) 438 | } 439 | 440 | // Error logs a message at the error log level and returns the formatted error, 441 | // See Warn for an explanation of the performance and Debug for an explanation 442 | // of the parameters. 443 | func (log Logger) Error(arg0 interface{}, args ...interface{}) error { 444 | const ( 445 | lvl = ERROR 446 | ) 447 | var msg string 448 | switch first := arg0.(type) { 449 | case string: 450 | // Use the string as a format string 451 | msg = fmt.Sprintf(first, args...) 452 | case func() string: 453 | // Log the closure (no other arguments used) 454 | msg = first() 455 | default: 456 | // Build a format string so that it will be similar to Sprint 457 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 458 | } 459 | log.intLogf(lvl, msg) 460 | return errors.New(msg) 461 | } 462 | 463 | // Critical logs a message at the critical log level and returns the formatted error, 464 | // See Warn for an explanation of the performance and Debug for an explanation 465 | // of the parameters. 466 | func (log Logger) Critical(arg0 interface{}, args ...interface{}) error { 467 | const ( 468 | lvl = CRITICAL 469 | ) 470 | var msg string 471 | switch first := arg0.(type) { 472 | case string: 473 | // Use the string as a format string 474 | msg = fmt.Sprintf(first, args...) 475 | case func() string: 476 | // Log the closure (no other arguments used) 477 | msg = first() 478 | default: 479 | // Build a format string so that it will be similar to Sprint 480 | msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 481 | } 482 | log.intLogf(lvl, msg) 483 | return errors.New(msg) 484 | } 485 | -------------------------------------------------------------------------------- /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] message\n", 93 | }, 94 | } 95 | 96 | func TestConsoleLogWriter(t *testing.T) { 97 | console := make(ConsoleLogWriter) 98 | 99 | r, w := io.Pipe() 100 | go console.run(w) 101 | defer console.Close() 102 | 103 | buf := make([]byte, 1024) 104 | 105 | for _, test := range logRecordWriteTests { 106 | name := test.Test 107 | 108 | console.LogWrite(test.Record) 109 | n, _ := r.Read(buf) 110 | 111 | if got, want := string(buf[:n]), test.Console; got != want { 112 | t.Errorf("%s: got %q", name, got) 113 | t.Errorf("%s: want %q", name, want) 114 | } 115 | } 116 | } 117 | 118 | func TestFileLogWriter(t *testing.T) { 119 | defer func(buflen int) { 120 | LogBufferLength = buflen 121 | }(LogBufferLength) 122 | LogBufferLength = 0 123 | 124 | w := NewFileLogWriter(testLogFile, false) 125 | if w == nil { 126 | t.Fatalf("Invalid return: w should not be nil") 127 | } 128 | defer os.Remove(testLogFile) 129 | 130 | w.LogWrite(newLogRecord(CRITICAL, "source", "message")) 131 | w.Close() 132 | runtime.Gosched() 133 | 134 | if contents, err := ioutil.ReadFile(testLogFile); err != nil { 135 | t.Errorf("read(%q): %s", testLogFile, err) 136 | } else if len(contents) != 50 { 137 | t.Errorf("malformed filelog: %q (%d bytes)", string(contents), len(contents)) 138 | } 139 | } 140 | 141 | func TestXMLLogWriter(t *testing.T) { 142 | defer func(buflen int) { 143 | LogBufferLength = buflen 144 | }(LogBufferLength) 145 | LogBufferLength = 0 146 | 147 | w := NewXMLLogWriter(testLogFile, false) 148 | if w == nil { 149 | t.Fatalf("Invalid return: w should not be nil") 150 | } 151 | defer os.Remove(testLogFile) 152 | 153 | w.LogWrite(newLogRecord(CRITICAL, "source", "message")) 154 | w.Close() 155 | runtime.Gosched() 156 | 157 | if contents, err := ioutil.ReadFile(testLogFile); err != nil { 158 | t.Errorf("read(%q): %s", testLogFile, err) 159 | } else if len(contents) != 185 { 160 | t.Errorf("malformed xmllog: %q (%d bytes)", string(contents), len(contents)) 161 | } 162 | } 163 | 164 | func TestLogger(t *testing.T) { 165 | sl := NewDefaultLogger(WARNING) 166 | if sl == nil { 167 | t.Fatalf("NewDefaultLogger should never return nil") 168 | } 169 | if lw, exist := sl["stdout"]; lw == nil || exist != true { 170 | t.Fatalf("NewDefaultLogger produced invalid logger (DNE or nil)") 171 | } 172 | if sl["stdout"].Level != WARNING { 173 | t.Fatalf("NewDefaultLogger produced invalid logger (incorrect level)") 174 | } 175 | if len(sl) != 1 { 176 | t.Fatalf("NewDefaultLogger produced invalid logger (incorrect map count)") 177 | } 178 | 179 | //func (l *Logger) AddFilter(name string, level int, writer LogWriter) {} 180 | l := make(Logger) 181 | l.AddFilter("stdout", DEBUG, NewConsoleLogWriter()) 182 | if lw, exist := l["stdout"]; lw == nil || exist != true { 183 | t.Fatalf("AddFilter produced invalid logger (DNE or nil)") 184 | } 185 | if l["stdout"].Level != DEBUG { 186 | t.Fatalf("AddFilter produced invalid logger (incorrect level)") 187 | } 188 | if len(l) != 1 { 189 | t.Fatalf("AddFilter produced invalid logger (incorrect map count)") 190 | } 191 | 192 | //func (l *Logger) Warn(format string, args ...interface{}) error {} 193 | if err := l.Warn("%s %d %#v", "Warning:", 1, []int{}); err.Error() != "Warning: 1 []int{}" { 194 | t.Errorf("Warn returned invalid error: %s", err) 195 | } 196 | 197 | //func (l *Logger) Error(format string, args ...interface{}) error {} 198 | if err := l.Error("%s %d %#v", "Error:", 10, []string{}); err.Error() != "Error: 10 []string{}" { 199 | t.Errorf("Error returned invalid error: %s", err) 200 | } 201 | 202 | //func (l *Logger) Critical(format string, args ...interface{}) error {} 203 | if err := l.Critical("%s %d %#v", "Critical:", 100, []int64{}); err.Error() != "Critical: 100 []int64{}" { 204 | t.Errorf("Critical returned invalid error: %s", err) 205 | } 206 | 207 | // Already tested or basically untestable 208 | //func (l *Logger) Log(level int, source, message string) {} 209 | //func (l *Logger) Logf(level int, format string, args ...interface{}) {} 210 | //func (l *Logger) intLogf(level int, format string, args ...interface{}) string {} 211 | //func (l *Logger) Finest(format string, args ...interface{}) {} 212 | //func (l *Logger) Fine(format string, args ...interface{}) {} 213 | //func (l *Logger) Debug(format string, args ...interface{}) {} 214 | //func (l *Logger) Trace(format string, args ...interface{}) {} 215 | //func (l *Logger) Info(format string, args ...interface{}) {} 216 | } 217 | 218 | func TestLogOutput(t *testing.T) { 219 | const ( 220 | expected = "fdf3e51e444da56b4cb400f30bc47424" 221 | ) 222 | 223 | // Unbuffered output 224 | defer func(buflen int) { 225 | LogBufferLength = buflen 226 | }(LogBufferLength) 227 | LogBufferLength = 0 228 | 229 | l := make(Logger) 230 | 231 | // Delete and open the output log without a timestamp (for a constant md5sum) 232 | l.AddFilter("file", FINEST, NewFileLogWriter(testLogFile, false).SetFormat("[%L] %M")) 233 | defer os.Remove(testLogFile) 234 | 235 | // Send some log messages 236 | l.Log(CRITICAL, "testsrc1", fmt.Sprintf("This message is level %d", int(CRITICAL))) 237 | l.Logf(ERROR, "This message is level %v", ERROR) 238 | l.Logf(WARNING, "This message is level %s", WARNING) 239 | l.Logc(INFO, func() string { return "This message is level INFO" }) 240 | l.Trace("This message is level %d", int(TRACE)) 241 | l.Debug("This message is level %s", DEBUG) 242 | l.Fine(func() string { return fmt.Sprintf("This message is level %v", FINE) }) 243 | l.Finest("This message is level %v", FINEST) 244 | l.Finest(FINEST, "is also this message's level") 245 | 246 | l.Close() 247 | 248 | contents, err := ioutil.ReadFile(testLogFile) 249 | if err != nil { 250 | t.Fatalf("Could not read output log: %s", err) 251 | } 252 | 253 | sum := md5.New() 254 | sum.Write(contents) 255 | if sumstr := hex.EncodeToString(sum.Sum(nil)); sumstr != expected { 256 | t.Errorf("--- Log Contents:\n%s---", string(contents)) 257 | t.Fatalf("Checksum does not match: %s (expecting %s)", sumstr, expected) 258 | } 259 | } 260 | 261 | func TestCountMallocs(t *testing.T) { 262 | const N = 1 263 | var m runtime.MemStats 264 | getMallocs := func() uint64 { 265 | runtime.ReadMemStats(&m) 266 | return m.Mallocs 267 | } 268 | 269 | // Console logger 270 | sl := NewDefaultLogger(INFO) 271 | mallocs := 0 - getMallocs() 272 | for i := 0; i < N; i++ { 273 | sl.Log(WARNING, "here", "This is a WARNING message") 274 | } 275 | mallocs += getMallocs() 276 | fmt.Printf("mallocs per sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) 277 | 278 | // Console logger formatted 279 | mallocs = 0 - getMallocs() 280 | for i := 0; i < N; i++ { 281 | sl.Logf(WARNING, "%s is a log message with level %d", "This", WARNING) 282 | } 283 | mallocs += getMallocs() 284 | fmt.Printf("mallocs per sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) 285 | 286 | // Console logger (not logged) 287 | sl = NewDefaultLogger(INFO) 288 | mallocs = 0 - getMallocs() 289 | for i := 0; i < N; i++ { 290 | sl.Log(DEBUG, "here", "This is a DEBUG log message") 291 | } 292 | mallocs += getMallocs() 293 | fmt.Printf("mallocs per unlogged sl.Log((WARNING, \"here\", \"This is a log message\"): %d\n", mallocs/N) 294 | 295 | // Console logger formatted (not logged) 296 | mallocs = 0 - getMallocs() 297 | for i := 0; i < N; i++ { 298 | sl.Logf(DEBUG, "%s is a log message with level %d", "This", DEBUG) 299 | } 300 | mallocs += getMallocs() 301 | fmt.Printf("mallocs per unlogged sl.Logf(WARNING, \"%%s is a log message with level %%d\", \"This\", WARNING): %d\n", mallocs/N) 302 | } 303 | 304 | func TestXMLConfig(t *testing.T) { 305 | const ( 306 | configfile = "example.xml" 307 | ) 308 | 309 | fd, err := os.Create(configfile) 310 | if err != nil { 311 | t.Fatalf("Could not open %s for writing: %s", configfile, err) 312 | } 313 | 314 | fmt.Fprintln(fd, "") 315 | fmt.Fprintln(fd, " ") 316 | fmt.Fprintln(fd, " stdout") 317 | fmt.Fprintln(fd, " console") 318 | fmt.Fprintln(fd, " ") 319 | fmt.Fprintln(fd, " DEBUG") 320 | fmt.Fprintln(fd, " ") 321 | fmt.Fprintln(fd, " ") 322 | fmt.Fprintln(fd, " file") 323 | fmt.Fprintln(fd, " file") 324 | fmt.Fprintln(fd, " FINEST") 325 | fmt.Fprintln(fd, " test.log") 326 | fmt.Fprintln(fd, " ") 337 | fmt.Fprintln(fd, " [%D %T] [%L] (%S) %M") 338 | fmt.Fprintln(fd, " false ") 339 | fmt.Fprintln(fd, " 0M ") 340 | fmt.Fprintln(fd, " 0K ") 341 | fmt.Fprintln(fd, " true ") 342 | fmt.Fprintln(fd, " ") 343 | fmt.Fprintln(fd, " ") 344 | fmt.Fprintln(fd, " xmllog") 345 | fmt.Fprintln(fd, " xml") 346 | fmt.Fprintln(fd, " TRACE") 347 | fmt.Fprintln(fd, " trace.xml") 348 | fmt.Fprintln(fd, " true ") 349 | fmt.Fprintln(fd, " 100M ") 350 | fmt.Fprintln(fd, " 6K ") 351 | fmt.Fprintln(fd, " false ") 352 | fmt.Fprintln(fd, " ") 353 | fmt.Fprintln(fd, " ") 354 | fmt.Fprintln(fd, " donotopen") 355 | fmt.Fprintln(fd, " socket") 356 | fmt.Fprintln(fd, " FINEST") 357 | fmt.Fprintln(fd, " 192.168.1.255:12124 ") 358 | fmt.Fprintln(fd, " udp ") 359 | fmt.Fprintln(fd, " ") 360 | fmt.Fprintln(fd, "") 361 | fd.Close() 362 | 363 | log := make(Logger) 364 | log.LoadConfiguration(configfile) 365 | defer os.Remove("trace.xml") 366 | defer os.Remove("test.log") 367 | defer log.Close() 368 | 369 | // Make sure we got all loggers 370 | if len(log) != 3 { 371 | t.Fatalf("XMLConfig: Expected 3 filters, found %d", len(log)) 372 | } 373 | 374 | // Make sure they're the right keys 375 | if _, ok := log["stdout"]; !ok { 376 | t.Errorf("XMLConfig: Expected stdout logger") 377 | } 378 | if _, ok := log["file"]; !ok { 379 | t.Fatalf("XMLConfig: Expected file logger") 380 | } 381 | if _, ok := log["xmllog"]; !ok { 382 | t.Fatalf("XMLConfig: Expected xmllog logger") 383 | } 384 | 385 | // Make sure they're the right type 386 | if _, ok := log["stdout"].LogWriter.(ConsoleLogWriter); !ok { 387 | t.Fatalf("XMLConfig: Expected stdout to be ConsoleLogWriter, found %T", log["stdout"].LogWriter) 388 | } 389 | if _, ok := log["file"].LogWriter.(*FileLogWriter); !ok { 390 | t.Fatalf("XMLConfig: Expected file to be *FileLogWriter, found %T", log["file"].LogWriter) 391 | } 392 | if _, ok := log["xmllog"].LogWriter.(*FileLogWriter); !ok { 393 | t.Fatalf("XMLConfig: Expected xmllog to be *FileLogWriter, found %T", log["xmllog"].LogWriter) 394 | } 395 | 396 | // Make sure levels are set 397 | if lvl := log["stdout"].Level; lvl != DEBUG { 398 | t.Errorf("XMLConfig: Expected stdout to be set to level %d, found %d", DEBUG, lvl) 399 | } 400 | if lvl := log["file"].Level; lvl != FINEST { 401 | t.Errorf("XMLConfig: Expected file to be set to level %d, found %d", FINEST, lvl) 402 | } 403 | if lvl := log["xmllog"].Level; lvl != TRACE { 404 | t.Errorf("XMLConfig: Expected xmllog to be set to level %d, found %d", TRACE, lvl) 405 | } 406 | 407 | // Make sure the w is open and points to the right file 408 | if fname := log["file"].LogWriter.(*FileLogWriter).file.Name(); fname != "test.log" { 409 | t.Errorf("XMLConfig: Expected file to have opened %s, found %s", "test.log", fname) 410 | } 411 | 412 | // Make sure the XLW is open and points to the right file 413 | if fname := log["xmllog"].LogWriter.(*FileLogWriter).file.Name(); fname != "trace.xml" { 414 | t.Errorf("XMLConfig: Expected xmllog to have opened %s, found %s", "trace.xml", fname) 415 | } 416 | 417 | // Move XML log file 418 | os.Rename(configfile, "examples/"+configfile) // Keep this so that an example with the documentation is available 419 | } 420 | 421 | func BenchmarkFormatLogRecord(b *testing.B) { 422 | const updateEvery = 1 423 | rec := &LogRecord{ 424 | Level: CRITICAL, 425 | Created: now, 426 | Source: "source", 427 | Message: "message", 428 | } 429 | for i := 0; i < b.N; i++ { 430 | rec.Created = rec.Created.Add(1 * time.Second / updateEvery) 431 | if i%2 == 0 { 432 | FormatLogRecord(FORMAT_DEFAULT, rec) 433 | } else { 434 | FormatLogRecord(FORMAT_SHORT, rec) 435 | } 436 | } 437 | } 438 | 439 | func BenchmarkConsoleLog(b *testing.B) { 440 | /* This doesn't seem to work on OS X 441 | sink, err := os.Open(os.DevNull) 442 | if err != nil { 443 | panic(err) 444 | } 445 | if err := syscall.Dup2(int(sink.Fd()), syscall.Stdout); err != nil { 446 | panic(err) 447 | } 448 | */ 449 | 450 | stdout = ioutil.Discard 451 | sl := NewDefaultLogger(INFO) 452 | for i := 0; i < b.N; i++ { 453 | sl.Log(WARNING, "here", "This is a log message") 454 | } 455 | } 456 | 457 | func BenchmarkConsoleNotLogged(b *testing.B) { 458 | sl := NewDefaultLogger(INFO) 459 | for i := 0; i < b.N; i++ { 460 | sl.Log(DEBUG, "here", "This is a log message") 461 | } 462 | } 463 | 464 | func BenchmarkConsoleUtilLog(b *testing.B) { 465 | sl := NewDefaultLogger(INFO) 466 | for i := 0; i < b.N; i++ { 467 | sl.Info("%s is a log message", "This") 468 | } 469 | } 470 | 471 | func BenchmarkConsoleUtilNotLog(b *testing.B) { 472 | sl := NewDefaultLogger(INFO) 473 | for i := 0; i < b.N; i++ { 474 | sl.Debug("%s is a log message", "This") 475 | } 476 | } 477 | 478 | func BenchmarkFileLog(b *testing.B) { 479 | sl := make(Logger) 480 | b.StopTimer() 481 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 482 | b.StartTimer() 483 | for i := 0; i < b.N; i++ { 484 | sl.Log(WARNING, "here", "This is a log message") 485 | } 486 | b.StopTimer() 487 | os.Remove("benchlog.log") 488 | } 489 | 490 | func BenchmarkFileNotLogged(b *testing.B) { 491 | sl := make(Logger) 492 | b.StopTimer() 493 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 494 | b.StartTimer() 495 | for i := 0; i < b.N; i++ { 496 | sl.Log(DEBUG, "here", "This is a log message") 497 | } 498 | b.StopTimer() 499 | os.Remove("benchlog.log") 500 | } 501 | 502 | func BenchmarkFileUtilLog(b *testing.B) { 503 | sl := make(Logger) 504 | b.StopTimer() 505 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 506 | b.StartTimer() 507 | for i := 0; i < b.N; i++ { 508 | sl.Info("%s is a log message", "This") 509 | } 510 | b.StopTimer() 511 | os.Remove("benchlog.log") 512 | } 513 | 514 | func BenchmarkFileUtilNotLog(b *testing.B) { 515 | sl := make(Logger) 516 | b.StopTimer() 517 | sl.AddFilter("file", INFO, NewFileLogWriter("benchlog.log", false)) 518 | b.StartTimer() 519 | for i := 0; i < b.N; i++ { 520 | sl.Debug("%s is a log message", "This") 521 | } 522 | b.StopTimer() 523 | os.Remove("benchlog.log") 524 | } 525 | 526 | // Benchmark results (darwin amd64 6g) 527 | //elog.BenchmarkConsoleLog 100000 22819 ns/op 528 | //elog.BenchmarkConsoleNotLogged 2000000 879 ns/op 529 | //elog.BenchmarkConsoleUtilLog 50000 34380 ns/op 530 | //elog.BenchmarkConsoleUtilNotLog 1000000 1339 ns/op 531 | //elog.BenchmarkFileLog 100000 26497 ns/op 532 | //elog.BenchmarkFileNotLogged 2000000 821 ns/op 533 | //elog.BenchmarkFileUtilLog 50000 33945 ns/op 534 | //elog.BenchmarkFileUtilNotLog 1000000 1258 ns/op 535 | -------------------------------------------------------------------------------- /pattlog.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2010, Kyle Lemons . All rights reserved. 2 | 3 | package log4go 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | const ( 14 | FORMAT_DEFAULT = "[%D %T] [%L] (%S) %M" 15 | FORMAT_SHORT = "[%t %d] [%L] %M" 16 | FORMAT_ABBREV = "[%L] %M" 17 | ) 18 | 19 | type formatCacheType struct { 20 | LastUpdateSeconds int64 21 | shortTime, shortDate string 22 | longTime, longDate string 23 | } 24 | 25 | var formatCache = &formatCacheType{} 26 | var muFormatCache = sync.Mutex{} 27 | 28 | func setFormatCache(f *formatCacheType) { 29 | muFormatCache.Lock() 30 | defer muFormatCache.Unlock() 31 | formatCache = f 32 | } 33 | func getFormatCache() *formatCacheType { 34 | muFormatCache.Lock() 35 | defer muFormatCache.Unlock() 36 | return formatCache 37 | } 38 | // Known format codes: 39 | // %T - Time (15:04:05 MST) 40 | // %t - Time (15:04) 41 | // %D - Date (2006/01/02) 42 | // %d - Date (01/02/06) 43 | // %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT) 44 | // %S - Source 45 | // %M - Message 46 | // Ignores unknown formats 47 | // Recommended: "[%D %T] [%L] (%S) %M" 48 | func FormatLogRecord(format string, rec *LogRecord) string { 49 | if rec == nil { 50 | return "" 51 | } 52 | if len(format) == 0 { 53 | return "" 54 | } 55 | 56 | out := bytes.NewBuffer(make([]byte, 0, 64)) 57 | secs := rec.Created.UnixNano() / 1e9 58 | 59 | cache := getFormatCache() 60 | if cache.LastUpdateSeconds != secs { 61 | month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year() 62 | hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second() 63 | zone, _ := rec.Created.Zone() 64 | updated := &formatCacheType{ 65 | LastUpdateSeconds: secs, 66 | shortTime: fmt.Sprintf("%02d:%02d", hour, minute), 67 | shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100), 68 | longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone), 69 | longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day), 70 | } 71 | cache = updated 72 | setFormatCache(updated) 73 | } 74 | 75 | // Split the string into pieces by % signs 76 | pieces := bytes.Split([]byte(format), []byte{'%'}) 77 | 78 | // Iterate over the pieces, replacing known formats 79 | for i, piece := range pieces { 80 | if i > 0 && len(piece) > 0 { 81 | switch piece[0] { 82 | case 'T': 83 | out.WriteString(cache.longTime) 84 | case 't': 85 | out.WriteString(cache.shortTime) 86 | case 'D': 87 | out.WriteString(cache.longDate) 88 | case 'd': 89 | out.WriteString(cache.shortDate) 90 | case 'L': 91 | out.WriteString(levelStrings[rec.Level]) 92 | case 'S': 93 | out.WriteString(rec.Source) 94 | case 's': 95 | slice := strings.Split(rec.Source, "/") 96 | out.WriteString(slice[len(slice)-1]) 97 | case 'M': 98 | out.WriteString(rec.Message) 99 | } 100 | if len(piece) > 1 { 101 | out.Write(piece[1:]) 102 | } 103 | } else if len(piece) > 0 { 104 | out.Write(piece) 105 | } 106 | } 107 | out.WriteByte('\n') 108 | 109 | return out.String() 110 | } 111 | 112 | // This is the standard writer that prints to standard output. 113 | type FormatLogWriter chan *LogRecord 114 | 115 | // This creates a new FormatLogWriter 116 | func NewFormatLogWriter(out io.Writer, format string) FormatLogWriter { 117 | records := make(FormatLogWriter, LogBufferLength) 118 | go records.run(out, format) 119 | return records 120 | } 121 | 122 | func (w FormatLogWriter) run(out io.Writer, format string) { 123 | for rec := range w { 124 | fmt.Fprint(out, FormatLogRecord(format, rec)) 125 | } 126 | } 127 | 128 | // This is the FormatLogWriter's output method. This will block if the output 129 | // buffer is full. 130 | func (w FormatLogWriter) LogWrite(rec *LogRecord) { 131 | w <- rec 132 | } 133 | 134 | // Close stops the logger from sending messages to standard output. Attempts to 135 | // send log messages to this logger after a Close have undefined behavior. 136 | func (w FormatLogWriter) Close() { 137 | close(w) 138 | } 139 | -------------------------------------------------------------------------------- /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 chan *LogRecord 14 | 15 | // This is the SocketLogWriter's output method 16 | func (w SocketLogWriter) LogWrite(rec *LogRecord) { 17 | w <- rec 18 | } 19 | 20 | func (w SocketLogWriter) Close() { 21 | close(w) 22 | } 23 | 24 | func NewSocketLogWriter(proto, hostport string) SocketLogWriter { 25 | sock, err := net.Dial(proto, hostport) 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err) 28 | return nil 29 | } 30 | 31 | w := SocketLogWriter(make(chan *LogRecord, LogBufferLength)) 32 | 33 | go func() { 34 | defer func() { 35 | if sock != nil && proto == "tcp" { 36 | sock.Close() 37 | } 38 | }() 39 | 40 | for rec := range w { 41 | // Marshall into JSON 42 | js, err := json.Marshal(rec) 43 | if err != nil { 44 | fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) 45 | return 46 | } 47 | 48 | _, err = sock.Write(js) 49 | if err != nil { 50 | fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err) 51 | return 52 | } 53 | } 54 | }() 55 | 56 | return w 57 | } 58 | -------------------------------------------------------------------------------- /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 | "time" 10 | ) 11 | 12 | var stdout io.Writer = os.Stdout 13 | 14 | // This is the standard writer that prints to standard output. 15 | type ConsoleLogWriter struct { 16 | format string 17 | w chan *LogRecord 18 | } 19 | 20 | // This creates a new ConsoleLogWriter 21 | func NewConsoleLogWriter() *ConsoleLogWriter { 22 | consoleWriter := &ConsoleLogWriter{ 23 | format: "[%T %D] [%L] (%S) %M", 24 | w: make(chan *LogRecord, LogBufferLength), 25 | } 26 | go consoleWriter.run(stdout) 27 | return consoleWriter 28 | } 29 | func (c *ConsoleLogWriter) SetFormat(format string) { 30 | c.format = format 31 | } 32 | func (c *ConsoleLogWriter) run(out io.Writer) { 33 | for rec := range c.w { 34 | fmt.Fprint(out, FormatLogRecord(c.format, rec)) 35 | } 36 | } 37 | 38 | // This is the ConsoleLogWriter's output method. This will block if the output 39 | // buffer is full. 40 | func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) { 41 | c.w <- rec 42 | } 43 | 44 | // Close stops the logger from sending messages to standard output. Attempts to 45 | // send log messages to this logger after a Close have undefined behavior. 46 | func (c *ConsoleLogWriter) Close() { 47 | close(c.w) 48 | time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete 49 | } 50 | -------------------------------------------------------------------------------- /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 | ) 11 | 12 | var ( 13 | Global Logger 14 | ) 15 | 16 | func init() { 17 | Global = NewDefaultLogger(DEBUG) 18 | } 19 | 20 | // Wrapper for (*Logger).LoadConfiguration 21 | func LoadConfiguration(filename string) { 22 | Global.LoadConfiguration(filename) 23 | } 24 | 25 | // Wrapper for (*Logger).AddFilter 26 | func AddFilter(name string, lvl Level, writer LogWriter) { 27 | Global.AddFilter(name, lvl, writer) 28 | } 29 | 30 | // Wrapper for (*Logger).Close (closes and removes all logwriters) 31 | func Close() { 32 | Global.Close() 33 | } 34 | 35 | func Crash(args ...interface{}) { 36 | if len(args) > 0 { 37 | Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...) 38 | } 39 | panic(args) 40 | } 41 | 42 | // Logs the given message and crashes the program 43 | func Crashf(format string, args ...interface{}) { 44 | Global.intLogf(CRITICAL, format, args...) 45 | Global.Close() // so that hopefully the messages get logged 46 | panic(fmt.Sprintf(format, args...)) 47 | } 48 | 49 | // Compatibility with `log` 50 | func Exit(args ...interface{}) { 51 | if len(args) > 0 { 52 | Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) 53 | } 54 | Global.Close() // so that hopefully the messages get logged 55 | os.Exit(0) 56 | } 57 | 58 | // Compatibility with `log` 59 | func Exitf(format string, args ...interface{}) { 60 | Global.intLogf(ERROR, format, args...) 61 | Global.Close() // so that hopefully the messages get logged 62 | os.Exit(0) 63 | } 64 | 65 | // Compatibility with `log` 66 | func Stderr(args ...interface{}) { 67 | if len(args) > 0 { 68 | Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...) 69 | } 70 | } 71 | 72 | // Compatibility with `log` 73 | func Stderrf(format string, args ...interface{}) { 74 | Global.intLogf(ERROR, format, args...) 75 | } 76 | 77 | // Compatibility with `log` 78 | func Stdout(args ...interface{}) { 79 | if len(args) > 0 { 80 | Global.intLogf(INFO, strings.Repeat(" %v", len(args))[1:], args...) 81 | } 82 | } 83 | 84 | // Compatibility with `log` 85 | func Stdoutf(format string, args ...interface{}) { 86 | Global.intLogf(INFO, format, args...) 87 | } 88 | 89 | // Send a log message manually 90 | // Wrapper for (*Logger).Log 91 | func Log(lvl Level, source, message string) { 92 | Global.Log(lvl, source, message) 93 | } 94 | 95 | // Send a formatted log message easily 96 | // Wrapper for (*Logger).Logf 97 | func Logf(lvl Level, format string, args ...interface{}) { 98 | Global.intLogf(lvl, format, args...) 99 | } 100 | 101 | // Send a closure log message 102 | // Wrapper for (*Logger).Logc 103 | func Logc(lvl Level, closure func() string) { 104 | Global.intLogc(lvl, closure) 105 | } 106 | 107 | // Utility for finest log messages (see Debug() for parameter explanation) 108 | // Wrapper for (*Logger).Finest 109 | func Finest(arg0 interface{}, args ...interface{}) { 110 | const ( 111 | lvl = FINEST 112 | ) 113 | switch first := arg0.(type) { 114 | case string: 115 | // Use the string as a format string 116 | Global.intLogf(lvl, first, args...) 117 | case func() string: 118 | // Log the closure (no other arguments used) 119 | Global.intLogc(lvl, first) 120 | default: 121 | // Build a format string so that it will be similar to Sprint 122 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 123 | } 124 | } 125 | 126 | // Utility for fine log messages (see Debug() for parameter explanation) 127 | // Wrapper for (*Logger).Fine 128 | func Fine(arg0 interface{}, args ...interface{}) { 129 | const ( 130 | lvl = FINE 131 | ) 132 | switch first := arg0.(type) { 133 | case string: 134 | // Use the string as a format string 135 | Global.intLogf(lvl, first, args...) 136 | case func() string: 137 | // Log the closure (no other arguments used) 138 | Global.intLogc(lvl, first) 139 | default: 140 | // Build a format string so that it will be similar to Sprint 141 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 142 | } 143 | } 144 | 145 | // Utility for debug log messages 146 | // 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) 147 | // 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. 148 | // When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint). 149 | // Wrapper for (*Logger).Debug 150 | func Debug(arg0 interface{}, args ...interface{}) { 151 | const ( 152 | lvl = DEBUG 153 | ) 154 | switch first := arg0.(type) { 155 | case string: 156 | // Use the string as a format string 157 | Global.intLogf(lvl, first, args...) 158 | case func() string: 159 | // Log the closure (no other arguments used) 160 | Global.intLogc(lvl, first) 161 | default: 162 | // Build a format string so that it will be similar to Sprint 163 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 164 | } 165 | } 166 | 167 | // Utility for trace log messages (see Debug() for parameter explanation) 168 | // Wrapper for (*Logger).Trace 169 | func Trace(arg0 interface{}, args ...interface{}) { 170 | const ( 171 | lvl = TRACE 172 | ) 173 | switch first := arg0.(type) { 174 | case string: 175 | // Use the string as a format string 176 | Global.intLogf(lvl, first, args...) 177 | case func() string: 178 | // Log the closure (no other arguments used) 179 | Global.intLogc(lvl, first) 180 | default: 181 | // Build a format string so that it will be similar to Sprint 182 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 183 | } 184 | } 185 | 186 | // Utility for info log messages (see Debug() for parameter explanation) 187 | // Wrapper for (*Logger).Info 188 | func Info(arg0 interface{}, args ...interface{}) { 189 | const ( 190 | lvl = INFO 191 | ) 192 | switch first := arg0.(type) { 193 | case string: 194 | // Use the string as a format string 195 | Global.intLogf(lvl, first, args...) 196 | case func() string: 197 | // Log the closure (no other arguments used) 198 | Global.intLogc(lvl, first) 199 | default: 200 | // Build a format string so that it will be similar to Sprint 201 | Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...) 202 | } 203 | } 204 | 205 | // Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 206 | // These functions will execute a closure exactly once, to build the error message for the return 207 | // Wrapper for (*Logger).Warn 208 | func Warn(arg0 interface{}, args ...interface{}) error { 209 | const ( 210 | lvl = WARNING 211 | ) 212 | switch first := arg0.(type) { 213 | case string: 214 | // Use the string as a format string 215 | Global.intLogf(lvl, first, args...) 216 | return errors.New(fmt.Sprintf(first, args...)) 217 | case func() string: 218 | // Log the closure (no other arguments used) 219 | str := first() 220 | Global.intLogf(lvl, "%s", str) 221 | return errors.New(str) 222 | default: 223 | // Build a format string so that it will be similar to Sprint 224 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 225 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 226 | } 227 | return nil 228 | } 229 | 230 | // Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 231 | // These functions will execute a closure exactly once, to build the error message for the return 232 | // Wrapper for (*Logger).Error 233 | func Error(arg0 interface{}, args ...interface{}) error { 234 | const ( 235 | lvl = ERROR 236 | ) 237 | switch first := arg0.(type) { 238 | case string: 239 | // Use the string as a format string 240 | Global.intLogf(lvl, first, args...) 241 | return errors.New(fmt.Sprintf(first, args...)) 242 | case func() string: 243 | // Log the closure (no other arguments used) 244 | str := first() 245 | Global.intLogf(lvl, "%s", str) 246 | return errors.New(str) 247 | default: 248 | // Build a format string so that it will be similar to Sprint 249 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 250 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 251 | } 252 | return nil 253 | } 254 | 255 | // Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation) 256 | // These functions will execute a closure exactly once, to build the error message for the return 257 | // Wrapper for (*Logger).Critical 258 | func Critical(arg0 interface{}, args ...interface{}) error { 259 | const ( 260 | lvl = CRITICAL 261 | ) 262 | switch first := arg0.(type) { 263 | case string: 264 | // Use the string as a format string 265 | Global.intLogf(lvl, first, args...) 266 | return errors.New(fmt.Sprintf(first, args...)) 267 | case func() string: 268 | // Log the closure (no other arguments used) 269 | str := first() 270 | Global.intLogf(lvl, "%s", str) 271 | return errors.New(str) 272 | default: 273 | // Build a format string so that it will be similar to Sprint 274 | Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...) 275 | return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...)) 276 | } 277 | return nil 278 | } 279 | --------------------------------------------------------------------------------