├── README.md ├── conn.go ├── conn_test.go ├── console.go ├── console_test.go ├── file.go ├── file_test.go ├── log.go ├── smtp.go └── smtp_test.go /README.md: -------------------------------------------------------------------------------- 1 | ## beelogs 2 | beelogs is the upgrade version from beego logs! beelogs add some methods, such as Rest(), GoOn(), StealOne()... 3 | 4 | 5 | ## How to install? 6 | 7 | go get github.com/henrylee2cn/beelogs 8 | 9 | 10 | ## What adapters are supported? 11 | 12 | As of now this logs support console, file,smtp and conn. 13 | 14 | 15 | ## How to use it? 16 | 17 | First you must import it 18 | 19 | import ( 20 | "github.com/henrylee2cn/beelogs" 21 | ) 22 | 23 | Then init a Log (example with console adapter) 24 | 25 | log := NewLogger(10000,false) 26 | log.SetLogger("console", map[]interface{"level":2,"writer":os.Stdout}) 27 | 28 | > the first params stand for how many channel 29 | 30 | Use it like this: 31 | 32 | log.Debug("debug") 33 | log.Informational("info") 34 | log.Notice("notice") 35 | log.Warning("warning") 36 | log.Error("error") 37 | log.Critical("critical") 38 | log.Alert("alert") 39 | log.Emergency("emergency") 40 | 41 | 42 | ## File adapter 43 | 44 | Configure file adapter like this: 45 | 46 | log := NewLogger(10000) 47 | log.SetLogger("file", map[string]interface{}{"filename":"test.log"}) 48 | 49 | 50 | ## Conn adapter 51 | 52 | Configure like this: 53 | 54 | log := NewLogger(1000) 55 | log.SetLogger("conn", map[string]interface{}{"net":"tcp","addr":":7020"}) 56 | log.Info("info") 57 | 58 | 59 | ## Smtp adapter 60 | 61 | Configure like this: 62 | 63 | log := NewLogger(10000) 64 | log.SetLogger("smtp", map[string]interface{}{ 65 | "username": "beegotest@gmail.com", 66 | "password": "xxxxxxxx", 67 | "host": "smtp.gmail.com:587", 68 | "sendTos": []string{ 69 | "xiemengjun@gmail.com", 70 | }, 71 | }) 72 | log.Critical("sendmail critical") 73 | time.Sleep(time.Second * 30) 74 | 75 | 76 | 77 | ## StealOne 78 | 79 | Configure like this: 80 | 81 | log := NewLogger(10000) 82 | log.SetStealLevel(LevelNotice) 83 | log.Critical("sendmail critical") 84 | for level, msg, normal := StealOne(); normal; level, msg, normal = StealOne() { 85 | fmt.Println(level, msg) 86 | } 87 | time.Sleep(time.Second * 30) 88 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "encoding/json" 19 | "io" 20 | "log" 21 | "net" 22 | ) 23 | 24 | // ConnWriter implements LoggerInterface. 25 | // it writes messages in keep-live tcp connection. 26 | type ConnWriter struct { 27 | lg *log.Logger 28 | innerWriter io.WriteCloser 29 | ReconnectOnMsg bool `json:"reconnectOnMsg"` 30 | Reconnect bool `json:"reconnect"` 31 | Net string `json:"net"` 32 | Addr string `json:"addr"` 33 | Level int `json:"level"` 34 | } 35 | 36 | // create new ConnWrite returning as LoggerInterface. 37 | func NewConn() LoggerInterface { 38 | conn := new(ConnWriter) 39 | conn.Level = LevelDebug 40 | return conn 41 | } 42 | 43 | // init connection writer with json config. 44 | // json config only need key "level". 45 | func (c *ConnWriter) Init(config map[string]interface{}) error { 46 | conf, err := json.Marshal(config) 47 | if err != nil { 48 | return err 49 | } 50 | return json.Unmarshal(conf, c) 51 | } 52 | 53 | // write message in connection. 54 | // if connection is down, try to re-connect. 55 | func (c *ConnWriter) WriteMsg(msg string, level int) error { 56 | if level > c.Level { 57 | return nil 58 | } 59 | if c.neddedConnectOnMsg() { 60 | err := c.connect() 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | if c.ReconnectOnMsg { 67 | defer c.innerWriter.Close() 68 | } 69 | c.lg.Println(msg) 70 | return nil 71 | } 72 | 73 | // implementing method. empty. 74 | func (c *ConnWriter) Flush() { 75 | 76 | } 77 | 78 | // destroy connection writer and close tcp listener. 79 | func (c *ConnWriter) Destroy() { 80 | if c.innerWriter != nil { 81 | c.innerWriter.Close() 82 | } 83 | } 84 | 85 | func (c *ConnWriter) connect() error { 86 | if c.innerWriter != nil { 87 | c.innerWriter.Close() 88 | c.innerWriter = nil 89 | } 90 | 91 | conn, err := net.Dial(c.Net, c.Addr) 92 | if err != nil { 93 | return err 94 | } 95 | 96 | if tcpConn, ok := conn.(*net.TCPConn); ok { 97 | tcpConn.SetKeepAlive(true) 98 | } 99 | 100 | c.innerWriter = conn 101 | c.lg = log.New(conn, "", log.Ldate|log.Ltime) 102 | return nil 103 | } 104 | 105 | func (c *ConnWriter) neddedConnectOnMsg() bool { 106 | if c.Reconnect { 107 | c.Reconnect = false 108 | return true 109 | } 110 | 111 | if c.innerWriter == nil { 112 | return true 113 | } 114 | 115 | return c.ReconnectOnMsg 116 | } 117 | 118 | func init() { 119 | Register("conn", NewConn) 120 | } 121 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestConn(t *testing.T) { 22 | log := NewLogger(1000) 23 | log.SetLogger("conn", map[string]interface{}{"net": "tcp", "addr": ":7020"}) 24 | log.Informational("informational") 25 | } 26 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "errors" 19 | "io" 20 | "log" 21 | "os" 22 | "runtime" 23 | ) 24 | 25 | type Brush func(string) string 26 | 27 | func NewBrush(color string) Brush { 28 | pre := "\033[" 29 | reset := "\033[0m" 30 | return func(text string) string { 31 | return pre + color + "m" + text + reset 32 | } 33 | } 34 | 35 | var colors = []Brush{ 36 | NewBrush("1;37"), // Emergency white 37 | NewBrush("1;36"), // Alert cyan 38 | NewBrush("1;35"), // Critical magenta 39 | NewBrush("1;31"), // Error red 40 | NewBrush("1;33"), // Warning yellow 41 | NewBrush("1;32"), // Notice green 42 | NewBrush("1;34"), // Informational blue 43 | NewBrush("1;34"), // Debug blue 44 | } 45 | 46 | // ConsoleWriter implements LoggerInterface and writes messages to terminal. 47 | type ConsoleWriter struct { 48 | lg *log.Logger 49 | Level int `json:"level"` 50 | } 51 | 52 | // create ConsoleWriter returning as LoggerInterface. 53 | func NewConsole() LoggerInterface { 54 | cw := &ConsoleWriter{ 55 | Level: LevelDebug, 56 | lg: log.New(os.Stdout, "", log.LstdFlags), 57 | } 58 | return cw 59 | } 60 | 61 | // init console logger. 62 | // config like map[string]interface{}{"level":LevelTrace,"writer":os.Stdout}. 63 | func (c *ConsoleWriter) Init(config map[string]interface{}) error { 64 | if config == nil { 65 | return nil 66 | } 67 | if l, ok := config["level"]; ok { 68 | if l2, ok2 := l.(int); ok2 { 69 | c.Level = l2 70 | } else { 71 | return errors.New("consloe config-level's type is incorrect!") 72 | } 73 | } 74 | if w, ok := config["writer"]; ok { 75 | if w2, ok2 := w.(io.Writer); ok2 { 76 | c.lg = log.New(w2, "", log.LstdFlags) 77 | } 78 | } 79 | return nil 80 | } 81 | 82 | // write message in console. 83 | func (c *ConsoleWriter) WriteMsg(msg string, level int) error { 84 | if level > c.Level { 85 | return nil 86 | } 87 | if goos := runtime.GOOS; goos == "windows" { 88 | c.lg.Println(msg) 89 | return nil 90 | } 91 | c.lg.Println(colors[level](msg)) 92 | 93 | return nil 94 | } 95 | 96 | // implementing method. empty. 97 | func (c *ConsoleWriter) Destroy() { 98 | 99 | } 100 | 101 | // implementing method. empty. 102 | func (c *ConsoleWriter) Flush() { 103 | 104 | } 105 | 106 | func init() { 107 | Register("console", NewConsole) 108 | } 109 | -------------------------------------------------------------------------------- /console_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | // Try each log level in decreasing order of priority. 22 | func testConsoleCalls(bl *BeeLogger) { 23 | bl.Emergency("emergency") 24 | bl.Alert("alert") 25 | bl.Critical("critical") 26 | bl.Error("error") 27 | bl.Warning("warning") 28 | bl.Notice("notice") 29 | bl.Informational("informational") 30 | bl.Debug("debug") 31 | } 32 | 33 | // Test console logging by visually comparing the lines being output with and 34 | // without a log level specification. 35 | func TestConsole(t *testing.T) { 36 | log1 := NewLogger(10000) 37 | log1.EnableFuncCallDepth(true) 38 | log1.SetLogger("console", nil) 39 | testConsoleCalls(log1) 40 | 41 | log2 := NewLogger(100) 42 | log2.SetLogger("console", map[string]interface{}{"level": 3}) 43 | testConsoleCalls(log2) 44 | } 45 | 46 | func BenchmarkConsole(b *testing.B) { 47 | log := NewLogger(10000) 48 | log.EnableFuncCallDepth(true) 49 | log.SetLogger("console", nil) 50 | for i := 0; i < b.N; i++ { 51 | log.Debug("debug") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "log" 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | "sync" 28 | "time" 29 | ) 30 | 31 | // FileLogWriter implements LoggerInterface. 32 | // It writes messages by lines limit, file size limit, or time frequency. 33 | type FileLogWriter struct { 34 | *log.Logger 35 | mw *MuxWriter 36 | // The opened file 37 | Filename string `json:"filename"` 38 | 39 | Maxlines int `json:"maxlines"` 40 | maxlines_curlines int 41 | 42 | // Rotate at size 43 | Maxsize int `json:"maxsize"` 44 | maxsize_cursize int 45 | 46 | // Rotate daily 47 | Daily bool `json:"daily"` 48 | Maxdays int64 `json:"maxdays"` 49 | daily_opendate int 50 | 51 | Rotate bool `json:"rotate"` 52 | 53 | startLock sync.Mutex // Only one log can write to the file 54 | 55 | Level int `json:"level"` 56 | } 57 | 58 | // an *os.File writer with locker. 59 | type MuxWriter struct { 60 | sync.Mutex 61 | fd *os.File 62 | } 63 | 64 | // write to os.File. 65 | func (l *MuxWriter) Write(b []byte) (int, error) { 66 | l.Lock() 67 | defer l.Unlock() 68 | return l.fd.Write(b) 69 | } 70 | 71 | // set os.File in writer. 72 | func (l *MuxWriter) SetFd(fd *os.File) { 73 | if l.fd != nil { 74 | l.fd.Close() 75 | } 76 | l.fd = fd 77 | } 78 | 79 | // create a FileLogWriter returning as LoggerInterface. 80 | func NewFileWriter() LoggerInterface { 81 | w := &FileLogWriter{ 82 | Filename: "", 83 | Maxlines: 1000000, 84 | Maxsize: 1 << 28, //256 MB 85 | Daily: true, 86 | Maxdays: 7, 87 | Rotate: true, 88 | Level: LevelDebug, 89 | } 90 | // use MuxWriter instead direct use os.File for lock write when rotate 91 | w.mw = new(MuxWriter) 92 | // set MuxWriter as Logger's io.Writer 93 | w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime) 94 | return w 95 | } 96 | 97 | // Init file logger with json config. 98 | // config like: 99 | // { 100 | // "filename":"logs/beego.log", 101 | // "maxlines":10000, 102 | // "maxsize":1<<30, 103 | // "daily":true, 104 | // "maxdays":15, 105 | // "rotate":true 106 | // } 107 | func (w *FileLogWriter) Init(config map[string]interface{}) error { 108 | if config == nil { 109 | return errors.New("config can not be empty") 110 | } 111 | if filename, ok := config["filename"]; !ok || len(filename.(string)) == 0 { 112 | return errors.New("config must have filename") 113 | } 114 | conf, err := json.Marshal(config) 115 | if err != nil { 116 | return err 117 | } 118 | err = json.Unmarshal(conf, w) 119 | if err != nil { 120 | return err 121 | } 122 | return w.startLogger() 123 | } 124 | 125 | // start file logger. create log file and set to locker-inside file writer. 126 | func (w *FileLogWriter) startLogger() error { 127 | fd, err := w.createLogFile() 128 | if err != nil { 129 | return err 130 | } 131 | w.mw.SetFd(fd) 132 | return w.initFd() 133 | } 134 | 135 | func (w *FileLogWriter) docheck(size int) { 136 | w.startLock.Lock() 137 | defer w.startLock.Unlock() 138 | if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || 139 | (w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) || 140 | (w.Daily && time.Now().Day() != w.daily_opendate)) { 141 | if err := w.DoRotate(); err != nil { 142 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) 143 | return 144 | } 145 | } 146 | w.maxlines_curlines++ 147 | w.maxsize_cursize += size 148 | } 149 | 150 | // write logger message into file. 151 | func (w *FileLogWriter) WriteMsg(msg string, level int) error { 152 | if level > w.Level { 153 | return nil 154 | } 155 | n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] " 156 | w.docheck(n) 157 | w.Logger.Println(msg) 158 | return nil 159 | } 160 | 161 | func (w *FileLogWriter) createLogFile() (*os.File, error) { 162 | // Open the log file 163 | fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) 164 | return fd, err 165 | } 166 | 167 | func (w *FileLogWriter) initFd() error { 168 | fd := w.mw.fd 169 | finfo, err := fd.Stat() 170 | if err != nil { 171 | return fmt.Errorf("get stat err: %s\n", err) 172 | } 173 | w.maxsize_cursize = int(finfo.Size()) 174 | w.daily_opendate = time.Now().Day() 175 | w.maxlines_curlines = 0 176 | if finfo.Size() > 0 { 177 | count, err := w.lines() 178 | if err != nil { 179 | return err 180 | } 181 | w.maxlines_curlines = count 182 | } 183 | return nil 184 | } 185 | 186 | func (w *FileLogWriter) lines() (int, error) { 187 | fd, err := os.Open(w.Filename) 188 | if err != nil { 189 | return 0, err 190 | } 191 | defer fd.Close() 192 | 193 | buf := make([]byte, 32768) // 32k 194 | count := 0 195 | lineSep := []byte{'\n'} 196 | 197 | for { 198 | c, err := fd.Read(buf) 199 | if err != nil && err != io.EOF { 200 | return count, err 201 | } 202 | 203 | count += bytes.Count(buf[:c], lineSep) 204 | 205 | if err == io.EOF { 206 | break 207 | } 208 | } 209 | 210 | return count, nil 211 | } 212 | 213 | // DoRotate means it need to write file in new file. 214 | // new file name like xx.log.2013-01-01.2 215 | func (w *FileLogWriter) DoRotate() error { 216 | _, err := os.Lstat(w.Filename) 217 | if err == nil { // file exists 218 | // Find the next available number 219 | num := 1 220 | fname := "" 221 | for ; err == nil && num <= 999; num++ { 222 | fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) 223 | _, err = os.Lstat(fname) 224 | } 225 | // return error if the last file checked still existed 226 | if err == nil { 227 | return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename) 228 | } 229 | 230 | // block Logger's io.Writer 231 | w.mw.Lock() 232 | defer w.mw.Unlock() 233 | 234 | fd := w.mw.fd 235 | fd.Close() 236 | 237 | // close fd before rename 238 | // Rename the file to its newfound home 239 | err = os.Rename(w.Filename, fname) 240 | if err != nil { 241 | return fmt.Errorf("Rotate: %s\n", err) 242 | } 243 | 244 | // re-start logger 245 | err = w.startLogger() 246 | if err != nil { 247 | return fmt.Errorf("Rotate StartLogger: %s\n", err) 248 | } 249 | 250 | go w.deleteOldLog() 251 | } 252 | 253 | return nil 254 | } 255 | 256 | func (w *FileLogWriter) deleteOldLog() { 257 | dir := filepath.Dir(w.Filename) 258 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { 259 | defer func() { 260 | if r := recover(); r != nil { 261 | returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) 262 | fmt.Println(returnErr) 263 | } 264 | }() 265 | 266 | if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { 267 | if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { 268 | os.Remove(path) 269 | } 270 | } 271 | return 272 | }) 273 | } 274 | 275 | // destroy file logger, close file writer. 276 | func (w *FileLogWriter) Destroy() { 277 | w.mw.fd.Close() 278 | } 279 | 280 | // flush file logger. 281 | // there are no buffering messages in file logger in memory. 282 | // flush file means sync file from disk. 283 | func (w *FileLogWriter) Flush() { 284 | w.mw.fd.Sync() 285 | } 286 | 287 | func init() { 288 | Register("file", NewFileWriter) 289 | } 290 | -------------------------------------------------------------------------------- /file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "bufio" 19 | "fmt" 20 | "os" 21 | "strconv" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | func TestFile(t *testing.T) { 27 | log := NewLogger(10000) 28 | log.SetLogger("file", map[string]interface{}{"filename": "test.log"}) 29 | log.Debug("debug") 30 | log.Informational("info") 31 | log.Notice("notice") 32 | log.Warning("warning") 33 | log.Error("error") 34 | log.Alert("alert") 35 | log.Critical("critical") 36 | log.Emergency("emergency") 37 | time.Sleep(time.Second * 4) 38 | f, err := os.Open("test.log") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | b := bufio.NewReader(f) 43 | linenum := 0 44 | for { 45 | line, _, err := b.ReadLine() 46 | if err != nil { 47 | break 48 | } 49 | if len(line) > 0 { 50 | linenum++ 51 | } 52 | } 53 | var expected = LevelDebug + 1 54 | if linenum != expected { 55 | t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines") 56 | } 57 | os.Remove("test.log") 58 | } 59 | 60 | func TestFile2(t *testing.T) { 61 | log := NewLogger(10000) 62 | log.SetLogger("file", map[string]interface{}{"filename": "test2.log", "level": LevelError}) 63 | log.Debug("debug") 64 | log.Info("info") 65 | log.Notice("notice") 66 | log.Warning("warning") 67 | log.Error("error") 68 | log.Alert("alert") 69 | log.Critical("critical") 70 | log.Emergency("emergency") 71 | time.Sleep(time.Second * 4) 72 | f, err := os.Open("test2.log") 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | b := bufio.NewReader(f) 77 | linenum := 0 78 | for { 79 | line, _, err := b.ReadLine() 80 | if err != nil { 81 | break 82 | } 83 | if len(line) > 0 { 84 | linenum++ 85 | } 86 | } 87 | var expected = LevelError + 1 88 | if linenum != expected { 89 | t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines") 90 | } 91 | os.Remove("test2.log") 92 | } 93 | 94 | func TestFileRotate(t *testing.T) { 95 | log := NewLogger(10000) 96 | log.SetLogger("file", map[string]interface{}{"filename": "test3.log", "maxlines": 4}) 97 | log.Debug("debug") 98 | log.Info("info") 99 | log.Notice("notice") 100 | log.Warning("warning") 101 | log.Error("error") 102 | log.Alert("alert") 103 | log.Critical("critical") 104 | log.Emergency("emergency") 105 | time.Sleep(time.Second * 4) 106 | rotatename := "test3.log" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) 107 | b, err := exists(rotatename) 108 | if !b || err != nil { 109 | t.Fatal("rotate not generated") 110 | } 111 | os.Remove(rotatename) 112 | os.Remove("test3.log") 113 | } 114 | 115 | func exists(path string) (bool, error) { 116 | _, err := os.Stat(path) 117 | if err == nil { 118 | return true, nil 119 | } 120 | if os.IsNotExist(err) { 121 | return false, nil 122 | } 123 | return false, err 124 | } 125 | 126 | func BenchmarkFile(b *testing.B) { 127 | log := NewLogger(100000) 128 | log.SetLogger("file", map[string]interface{}{"filename": "test4.log"}) 129 | for i := 0; i < b.N; i++ { 130 | log.Debug("debug") 131 | } 132 | os.Remove("test4.log") 133 | } 134 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Usage: 16 | // 17 | // import "github.com/astaxie/beego/logs" 18 | // 19 | // log := NewLogger(10000) 20 | // log.SetLogger("console", "") 21 | // 22 | // > the first params stand for how many channel 23 | // 24 | // Use it like this: 25 | // 26 | // log.Debug("debug") 27 | // log.Informational("info") 28 | // log.Notice("notice") 29 | // log.Warning("warning") 30 | // log.Error("error") 31 | // log.Critical("critical") 32 | // log.Alert("alert") 33 | // log.Emergency("emergency") 34 | // 35 | // more docs http://beego.me/docs/module/logs.md 36 | 37 | // Modified By henrylee2cn 38 | 39 | package beelogs 40 | 41 | import ( 42 | "errors" 43 | "fmt" 44 | "path" 45 | "runtime" 46 | "sync" 47 | ) 48 | 49 | // RFC5424 log message levels. 50 | const ( 51 | LevelNothing = iota - 1 52 | LevelEmergency 53 | LevelAlert 54 | LevelCritical 55 | LevelError 56 | LevelWarning 57 | LevelNotice 58 | LevelInformational 59 | LevelDebug 60 | ) 61 | 62 | type loggerType func() LoggerInterface 63 | 64 | // LoggerInterface defines the behavior of a log provider. 65 | type LoggerInterface interface { 66 | Init(config map[string]interface{}) error 67 | WriteMsg(msg string, level int) error 68 | Destroy() 69 | Flush() 70 | } 71 | 72 | var adapters = make(map[string]loggerType) 73 | 74 | // Register makes a log provide available by the provided name. 75 | // If Register is called twice with the same name or if driver is nil, 76 | // it panics. 77 | func Register(name string, log loggerType) { 78 | if log == nil { 79 | panic("logs: Register provide is nil") 80 | } 81 | // if _, dup := adapters[name]; dup { 82 | // panic("logs: Register called twice for provider " + name) 83 | // } 84 | adapters[name] = log 85 | } 86 | 87 | // BeeLogger's status 88 | const ( 89 | NULL = iota - 1 90 | WORK 91 | REST 92 | CLOSE 93 | ) 94 | 95 | // BeeLogger is default logger in beego application. 96 | // it can contain several providers and log message into all providers. 97 | type BeeLogger struct { 98 | lock sync.RWMutex 99 | level int 100 | enableFuncCallDepth bool 101 | loggerFuncCallDepth int 102 | asynchronous bool 103 | msg chan *logMsg 104 | steal []*logMsg 105 | stealLevel int 106 | stealLock sync.Mutex 107 | outputs map[string]LoggerInterface 108 | status int 109 | } 110 | 111 | type logMsg struct { 112 | level int 113 | msg string 114 | } 115 | 116 | // NewLogger returns a new BeeLogger. 117 | // channellen means the number of messages in chan. 118 | // if the buffering chan is full, logger adapters write to file or other way. 119 | func NewLogger(channellen int64, stealLevel ...int) *BeeLogger { 120 | bl := new(BeeLogger) 121 | bl.level = LevelDebug 122 | bl.loggerFuncCallDepth = 2 123 | bl.msg = make(chan *logMsg, channellen) 124 | bl.outputs = make(map[string]LoggerInterface) 125 | bl.status = WORK 126 | bl.steal = []*logMsg{} 127 | if len(stealLevel) > 0 { 128 | bl.stealLevel = stealLevel[0] 129 | } else { 130 | bl.stealLevel = LevelNothing 131 | } 132 | return bl 133 | } 134 | 135 | func (bl *BeeLogger) Async(enable bool) *BeeLogger { 136 | bl.asynchronous = enable 137 | if enable { 138 | go bl.startLogger() 139 | } 140 | return bl 141 | } 142 | 143 | // SetLogger provides a given logger adapter into BeeLogger with config string. 144 | func (bl *BeeLogger) SetLogger(adaptername string, config map[string]interface{}) error { 145 | bl.lock.Lock() 146 | defer bl.lock.Unlock() 147 | if log, ok := adapters[adaptername]; ok { 148 | lg := log() 149 | err := lg.Init(config) 150 | bl.outputs[adaptername] = lg 151 | if err != nil { 152 | fmt.Println("logs.BeeLogger.SetLogger: " + err.Error()) 153 | return err 154 | } 155 | } else { 156 | return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) 157 | } 158 | return nil 159 | } 160 | 161 | // remove a logger adapter in BeeLogger. 162 | func (bl *BeeLogger) DelLogger(adaptername string) error { 163 | bl.lock.Lock() 164 | defer bl.lock.Unlock() 165 | if lg, ok := bl.outputs[adaptername]; ok { 166 | lg.Destroy() 167 | delete(bl.outputs, adaptername) 168 | return nil 169 | } else { 170 | return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) 171 | } 172 | } 173 | 174 | func (bl *BeeLogger) writerMsg(loglevel int, msg string) error { 175 | if i, s := bl.Status(); i != WORK { 176 | return errors.New("The current status is " + s) 177 | } 178 | 179 | lm := new(logMsg) 180 | lm.level = loglevel 181 | if bl.enableFuncCallDepth { 182 | _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) 183 | if !ok { 184 | file = "???" 185 | line = 0 186 | } 187 | _, filename := path.Split(file) 188 | lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg) 189 | } else { 190 | lm.msg = msg 191 | } 192 | 193 | if bl.stealLevel >= lm.level { 194 | bl.stealOne(lm) 195 | } 196 | 197 | if bl.asynchronous { 198 | bl.msg <- lm 199 | } else { 200 | bl.lock.RLock() 201 | defer bl.lock.RUnlock() 202 | for name, l := range bl.outputs { 203 | err := l.WriteMsg(lm.msg, lm.level) 204 | if err != nil { 205 | fmt.Println("unable to WriteMsg to adapter:", name, err) 206 | return err 207 | } 208 | } 209 | } 210 | return nil 211 | } 212 | 213 | // Set log message level. 214 | // 215 | // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), 216 | // log providers will not even be sent the message. 217 | func (bl *BeeLogger) SetLevel(l int) { 218 | bl.level = l 219 | } 220 | 221 | func (bl *BeeLogger) SetStealLevel(l int) { 222 | bl.stealLevel = l 223 | } 224 | 225 | // set log funcCallDepth 226 | func (bl *BeeLogger) SetLogFuncCallDepth(d int) { 227 | bl.loggerFuncCallDepth = d 228 | } 229 | 230 | // get log funcCallDepth for wrapper 231 | func (bl *BeeLogger) GetLogFuncCallDepth() int { 232 | return bl.loggerFuncCallDepth 233 | } 234 | 235 | // enable log funcCallDepth 236 | func (bl *BeeLogger) EnableFuncCallDepth(b bool) { 237 | bl.enableFuncCallDepth = b 238 | } 239 | 240 | // start logger chan reading. 241 | // when chan is not empty, write logs. 242 | func (bl *BeeLogger) startLogger() { 243 | for bl.asynchronous || len(bl.msg) > 0 { 244 | select { 245 | case bm := <-bl.msg: 246 | bl.lock.RLock() 247 | for _, l := range bl.outputs { 248 | err := l.WriteMsg(bm.msg, bm.level) 249 | if err != nil { 250 | fmt.Println("ERROR, unable to WriteMsg:", err) 251 | } 252 | } 253 | bl.lock.RUnlock() 254 | } 255 | } 256 | } 257 | 258 | // Log EMERGENCY level message. 259 | func (bl *BeeLogger) Emergency(format string, v ...interface{}) { 260 | if LevelEmergency > bl.level { 261 | return 262 | } 263 | msg := fmt.Sprintf("[M] "+format, v...) 264 | bl.writerMsg(LevelEmergency, msg) 265 | } 266 | 267 | // Log ALERT level message. 268 | func (bl *BeeLogger) Alert(format string, v ...interface{}) { 269 | if LevelAlert > bl.level { 270 | return 271 | } 272 | msg := fmt.Sprintf("[A] "+format, v...) 273 | bl.writerMsg(LevelAlert, msg) 274 | } 275 | 276 | // Log CRITICAL level message. 277 | func (bl *BeeLogger) Critical(format string, v ...interface{}) { 278 | if LevelCritical > bl.level { 279 | return 280 | } 281 | msg := fmt.Sprintf("[C] "+format, v...) 282 | bl.writerMsg(LevelCritical, msg) 283 | } 284 | 285 | // Log ERROR level message. 286 | func (bl *BeeLogger) Error(format string, v ...interface{}) { 287 | if LevelError > bl.level { 288 | return 289 | } 290 | msg := fmt.Sprintf("[E] "+format, v...) 291 | bl.writerMsg(LevelError, msg) 292 | } 293 | 294 | // Log WARNING level message. 295 | func (bl *BeeLogger) Warning(format string, v ...interface{}) { 296 | if LevelWarning > bl.level { 297 | return 298 | } 299 | msg := fmt.Sprintf("[W] "+format, v...) 300 | bl.writerMsg(LevelWarning, msg) 301 | } 302 | 303 | // Log NOTICE level message. 304 | func (bl *BeeLogger) Notice(format string, v ...interface{}) { 305 | if LevelNotice > bl.level { 306 | return 307 | } 308 | msg := fmt.Sprintf("[N] "+format, v...) 309 | bl.writerMsg(LevelNotice, msg) 310 | } 311 | 312 | // Log INFORMATIONAL level message. 313 | func (bl *BeeLogger) Informational(format string, v ...interface{}) { 314 | if LevelInformational > bl.level { 315 | return 316 | } 317 | msg := fmt.Sprintf("[I] "+format, v...) 318 | bl.writerMsg(LevelInformational, msg) 319 | } 320 | 321 | // Log DEBUG level message. 322 | func (bl *BeeLogger) Debug(format string, v ...interface{}) { 323 | if LevelDebug > bl.level { 324 | return 325 | } 326 | msg := fmt.Sprintf("[D] "+format, v...) 327 | bl.writerMsg(LevelDebug, msg) 328 | } 329 | 330 | // flush all chan data. 331 | func (bl *BeeLogger) Flush() { 332 | for _, l := range bl.outputs { 333 | l.Flush() 334 | } 335 | } 336 | 337 | // close logger, flush all chan data and destroy all adapters in BeeLogger. 338 | func (bl *BeeLogger) Close() { 339 | bl.SetStatus(CLOSE) 340 | bl.lock.RLock() 341 | defer bl.lock.RUnlock() 342 | for { 343 | if len(bl.msg) > 0 { 344 | bm := <-bl.msg 345 | for _, l := range bl.outputs { 346 | err := l.WriteMsg(bm.msg, bm.level) 347 | if err != nil { 348 | fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err) 349 | } 350 | } 351 | continue 352 | } 353 | break 354 | } 355 | for _, l := range bl.outputs { 356 | l.Flush() 357 | l.Destroy() 358 | } 359 | } 360 | 361 | func (bl *BeeLogger) Rest() { 362 | if i, _ := bl.Status(); i != WORK { 363 | return 364 | } 365 | bl.SetStatus(REST) 366 | } 367 | 368 | func (bl *BeeLogger) GoOn() { 369 | if i, _ := bl.Status(); i != REST { 370 | return 371 | } 372 | bl.SetStatus(WORK) 373 | } 374 | 375 | // get a log message 376 | func (bl *BeeLogger) StealOne() (level int, msg string, normal bool) { 377 | bl.stealLock.Lock() 378 | defer bl.stealLock.Unlock() 379 | if len(bl.steal) == 0 { 380 | if bl.status == CLOSE { 381 | return 0, "", false 382 | } else { 383 | return 0, "", true 384 | } 385 | } 386 | lm := bl.steal[0] 387 | bl.steal = bl.steal[1:] 388 | return lm.level, lm.msg, true 389 | } 390 | 391 | func (bl *BeeLogger) stealOne(lm *logMsg) { 392 | bl.stealLock.Lock() 393 | defer bl.stealLock.Unlock() 394 | bl.steal = append(bl.steal, lm) 395 | } 396 | 397 | func (bl *BeeLogger) Status() (int, string) { 398 | bl.lock.RLock() 399 | defer bl.lock.RUnlock() 400 | 401 | switch bl.status { 402 | case WORK: 403 | return WORK, "WORK" 404 | case REST: 405 | return REST, "REST" 406 | case CLOSE: 407 | return CLOSE, "CLOSE" 408 | } 409 | return NULL, "NULL" 410 | } 411 | 412 | func (bl *BeeLogger) SetStatus(status int) { 413 | bl.lock.Lock() 414 | defer bl.lock.Unlock() 415 | bl.status = status 416 | } 417 | -------------------------------------------------------------------------------- /smtp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "crypto/tls" 19 | "encoding/json" 20 | "fmt" 21 | "net" 22 | "net/smtp" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | const ( 28 | // no usage 29 | // subjectPhrase = "Diagnostic message from server" 30 | ) 31 | 32 | // smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server. 33 | type SmtpWriter struct { 34 | Username string `json:"Username"` 35 | Password string `json:"password"` 36 | Host string `json:"Host"` 37 | Subject string `json:"subject"` 38 | FromAddress string `json:"fromAddress"` 39 | RecipientAddresses []string `json:"sendTos"` 40 | Level int `json:"level"` 41 | } 42 | 43 | // create smtp writer. 44 | func NewSmtpWriter() LoggerInterface { 45 | return &SmtpWriter{Level: LevelDebug} 46 | } 47 | 48 | // init smtp writer with json config. 49 | // config like: 50 | // { 51 | // "Username":"example@gmail.com", 52 | // "password:"password", 53 | // "host":"smtp.gmail.com:465", 54 | // "subject":"email title", 55 | // "fromAddress":"from@example.com", 56 | // "sendTos":["email1","email2"], 57 | // "level":LevelError 58 | // } 59 | func (s *SmtpWriter) Init(config map[string]interface{}) error { 60 | conf, err := json.Marshal(config) 61 | if err != nil { 62 | return err 63 | } 64 | return json.Unmarshal(conf, s) 65 | } 66 | 67 | func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth { 68 | if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 { 69 | return nil 70 | } 71 | return smtp.PlainAuth( 72 | "", 73 | s.Username, 74 | s.Password, 75 | host, 76 | ) 77 | } 78 | 79 | func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { 80 | client, err := smtp.Dial(hostAddressWithPort) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | host, _, _ := net.SplitHostPort(hostAddressWithPort) 86 | tlsConn := &tls.Config{ 87 | InsecureSkipVerify: true, 88 | ServerName: host, 89 | } 90 | if err = client.StartTLS(tlsConn); err != nil { 91 | return err 92 | } 93 | 94 | if auth != nil { 95 | if err = client.Auth(auth); err != nil { 96 | return err 97 | } 98 | } 99 | 100 | if err = client.Mail(fromAddress); err != nil { 101 | return err 102 | } 103 | 104 | for _, rec := range recipients { 105 | if err = client.Rcpt(rec); err != nil { 106 | return err 107 | } 108 | } 109 | 110 | w, err := client.Data() 111 | if err != nil { 112 | return err 113 | } 114 | _, err = w.Write([]byte(msgContent)) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | err = w.Close() 120 | if err != nil { 121 | return err 122 | } 123 | 124 | err = client.Quit() 125 | if err != nil { 126 | return err 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // write message in smtp writer. 133 | // it will send an email with subject and only this message. 134 | func (s *SmtpWriter) WriteMsg(msg string, level int) error { 135 | if level > s.Level { 136 | return nil 137 | } 138 | 139 | hp := strings.Split(s.Host, ":") 140 | 141 | // Set up authentication information. 142 | auth := s.GetSmtpAuth(hp[0]) 143 | 144 | // Connect to the server, authenticate, set the sender and recipient, 145 | // and send the email all in one step. 146 | content_type := "Content-Type: text/plain" + "; charset=UTF-8" 147 | mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + 148 | ">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg) 149 | 150 | return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) 151 | } 152 | 153 | // implementing method. empty. 154 | func (s *SmtpWriter) Flush() { 155 | return 156 | } 157 | 158 | // implementing method. empty. 159 | func (s *SmtpWriter) Destroy() { 160 | return 161 | } 162 | 163 | func init() { 164 | Register("smtp", NewSmtpWriter) 165 | } 166 | -------------------------------------------------------------------------------- /smtp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package beelogs 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestSmtp(t *testing.T) { 23 | log := NewLogger(10000) 24 | log.SetLogger("smtp", map[string]interface{}{ 25 | "username": "beegotest@gmail.com", 26 | "password": "xxxxxxxx", 27 | "host": "smtp.gmail.com:587", 28 | "sendTos": []string{ 29 | "xiemengjun@gmail.com", 30 | }, 31 | }) 32 | log.Critical("sendmail critical") 33 | time.Sleep(time.Second * 30) 34 | } 35 | --------------------------------------------------------------------------------