f9297379 by astaxie

finish logs module

1 parent 49bbca0c
1 package logs
2
3 import (
4 "encoding/json"
5 "io"
6 "log"
7 "net"
8 )
9
10 type ConnWriter struct {
11 lg *log.Logger
12 innerWriter io.WriteCloser
13 reconnectOnMsg bool
14 reconnect bool
15 net string
16 addr string
17 level int
18 }
19
20 func NewConn() LoggerInterface {
21 conn := new(ConnWriter)
22 conn.level = LevelTrace
23 return conn
24 }
25
26 func (c *ConnWriter) Init(jsonconfig string) error {
27 var m map[string]interface{}
28 err := json.Unmarshal([]byte(jsonconfig), &m)
29 if err != nil {
30 return err
31 }
32 if rom, ok := m["reconnectOnMsg"]; ok {
33 c.reconnectOnMsg = rom.(bool)
34 }
35 if rc, ok := m["reconnect"]; ok {
36 c.reconnect = rc.(bool)
37 }
38 if nt, ok := m["net"]; ok {
39 c.net = nt.(string)
40 }
41 if addr, ok := m["addr"]; ok {
42 c.addr = addr.(string)
43 }
44 if lv, ok := m["level"]; ok {
45 c.level = int(lv.(float64))
46 }
47 return nil
48 }
49
50 func (c *ConnWriter) WriteMsg(msg string, level int) error {
51 if level < c.level {
52 return nil
53 }
54 if c.neddedConnectOnMsg() {
55 err := c.connect()
56 if err != nil {
57 return err
58 }
59 }
60
61 if c.reconnectOnMsg {
62 defer c.innerWriter.Close()
63 }
64 c.lg.Println(msg)
65 return nil
66 }
67
68 func (c *ConnWriter) Destroy() {
69 if c.innerWriter == nil {
70 return
71 }
72 c.innerWriter.Close()
73 }
74
75 func (c *ConnWriter) connect() error {
76 if c.innerWriter != nil {
77 c.innerWriter.Close()
78 c.innerWriter = nil
79 }
80
81 conn, err := net.Dial(c.net, c.addr)
82 if err != nil {
83 return err
84 }
85
86 tcpConn, ok := conn.(*net.TCPConn)
87 if ok {
88 tcpConn.SetKeepAlive(true)
89 }
90
91 c.innerWriter = conn
92 c.lg = log.New(conn, "", log.Ldate|log.Ltime)
93 return nil
94 }
95
96 func (c *ConnWriter) neddedConnectOnMsg() bool {
97 if c.reconnect {
98 c.reconnect = false
99 return true
100 }
101
102 if c.innerWriter == nil {
103 return true
104 }
105
106 return c.reconnectOnMsg
107 }
108
109 func init() {
110 Register("conn", NewConn)
111 }
1 package logs
2
3 import (
4 "testing"
5 )
6
7 func TestConn(t *testing.T) {
8 log := NewLogger(1000)
9 log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
10 log.Info("info")
11 }
1 package logs
2
3 import (
4 "encoding/json"
5 "log"
6 "os"
7 )
8
9 type ConsoleWriter struct {
10 lg *log.Logger
11 level int
12 }
13
14 func NewConsole() LoggerInterface {
15 cw := new(ConsoleWriter)
16 cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime)
17 cw.level = LevelTrace
18 return cw
19 }
20
21 func (c *ConsoleWriter) Init(jsonconfig string) error {
22 var m map[string]interface{}
23 err := json.Unmarshal([]byte(jsonconfig), &m)
24 if err != nil {
25 return err
26 }
27 if lv, ok := m["level"]; ok {
28 c.level = int(lv.(float64))
29 }
30 return nil
31 }
32
33 func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
34 if level < c.level {
35 return nil
36 }
37 c.lg.Println(msg)
38 return nil
39 }
40
41 func (c *ConsoleWriter) Destroy() {
42
43 }
44
45 func init() {
46 Register("console", NewConsole)
47 }
1 package logs
2
3 import (
4 "testing"
5 )
6
7 func TestConsole(t *testing.T) {
8 log := NewLogger(10000)
9 log.Trace("trace")
10 log.Info("info")
11 log.Warn("warning")
12 log.Debug("debug")
13 log.Critical("critical")
14 log2 := NewLogger(100)
15 log2.SetLogger("console", `{"level":1}`)
16 log.Trace("trace")
17 log.Info("info")
18 log.Warn("warning")
19 log.Debug("debug")
20 log.Critical("critical")
21 }
1 package logs
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io/ioutil"
8 "log"
9 "os"
10 "path"
11 "path/filepath"
12 "strings"
13 "sync"
14 "time"
15 )
16
17 type FileLogWriter struct {
18 *log.Logger
19 mw *MuxWriter
20 // The opened file
21 filename string
22
23 maxlines int
24 maxlines_curlines int
25
26 // Rotate at size
27 maxsize int
28 maxsize_cursize int
29
30 // Rotate daily
31 daily bool
32 maxdays int64
33 daily_opendate int
34
35 rotate bool
36
37 startLock sync.Mutex // Only one log can write to the file
38
39 level int
40 }
41
42 type MuxWriter struct {
43 sync.Mutex
44 fd *os.File
45 }
46
47 func (l *MuxWriter) Write(b []byte) (int, error) {
48 l.Lock()
49 defer l.Unlock()
50 return l.fd.Write(b)
51 }
52
53 func (l *MuxWriter) SetFd(fd *os.File) {
54 if l.fd != nil {
55 l.fd.Close()
56 }
57 l.fd = fd
58 }
59
60 func NewFileWriter() LoggerInterface {
61 w := &FileLogWriter{
62 filename: "",
63 maxlines: 1000000,
64 maxsize: 1 << 28, //256 MB
65 daily: true,
66 maxdays: 7,
67 rotate: true,
68 level: LevelTrace,
69 }
70 // use MuxWriter instead direct use os.File for lock write when rotate
71 w.mw = new(MuxWriter)
72 // set MuxWriter as Logger's io.Writer
73 w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
74 return w
75 }
76
77 // jsonconfig like this
78 //{
79 // "filename":"logs/beego.log",
80 // "maxlines":10000,
81 // "maxsize":1<<30,
82 // "daily":true,
83 // "maxdays":15,
84 // "rotate":true
85 //}
86 func (w *FileLogWriter) Init(jsonconfig string) error {
87 var m map[string]interface{}
88 err := json.Unmarshal([]byte(jsonconfig), &m)
89 if err != nil {
90 return err
91 }
92 if fn, ok := m["filename"]; !ok {
93 return errors.New("jsonconfig must have filename")
94 } else {
95 w.filename = fn.(string)
96 }
97 if ml, ok := m["maxlines"]; ok {
98 w.maxlines = int(ml.(float64))
99 }
100 if ms, ok := m["maxsize"]; ok {
101 w.maxsize = int(ms.(float64))
102 }
103 if dl, ok := m["daily"]; ok {
104 w.daily = dl.(bool)
105 }
106 if md, ok := m["maxdays"]; ok {
107 w.maxdays = int64(md.(float64))
108 }
109 if rt, ok := m["rotate"]; ok {
110 w.rotate = rt.(bool)
111 }
112 if lv, ok := m["level"]; ok {
113 w.level = int(lv.(float64))
114 }
115 err = w.StartLogger()
116 return err
117 }
118
119 func (w *FileLogWriter) StartLogger() error {
120 fd, err := w.createLogFile()
121 if err != nil {
122 return err
123 }
124 w.mw.SetFd(fd)
125 err = w.initFd()
126 if err != nil {
127 return err
128 }
129 return nil
130 }
131
132 func (w *FileLogWriter) docheck(size int) {
133 w.startLock.Lock()
134 defer w.startLock.Unlock()
135 if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
136 (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
137 (w.daily && time.Now().Day() != w.daily_opendate) {
138 if err := w.DoRotate(); err != nil {
139 fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
140 return
141 }
142 }
143 w.maxlines_curlines++
144 w.maxsize_cursize += size
145 }
146
147 func (w *FileLogWriter) WriteMsg(msg string, level int) error {
148 if level < w.level {
149 return nil
150 }
151 n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
152 w.docheck(n)
153 w.Logger.Println(msg)
154 return nil
155 }
156
157 func (w *FileLogWriter) createLogFile() (*os.File, error) {
158 // Open the log file
159 fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
160 return fd, err
161 }
162
163 func (w *FileLogWriter) initFd() error {
164 fd := w.mw.fd
165 finfo, err := fd.Stat()
166 if err != nil {
167 return fmt.Errorf("get stat err: %s\n", err)
168 }
169 w.maxsize_cursize = int(finfo.Size())
170 w.daily_opendate = time.Now().Day()
171 if finfo.Size() > 0 {
172 content, err := ioutil.ReadFile(w.filename)
173 if err != nil {
174 return err
175 }
176 w.maxlines_curlines = len(strings.Split(string(content), "\n"))
177 } else {
178 w.maxlines_curlines = 0
179 }
180 return nil
181 }
182
183 func (w *FileLogWriter) DoRotate() error {
184 _, err := os.Lstat(w.filename)
185 if err == nil { // file exists
186 // Find the next available number
187 num := 1
188 fname := ""
189 for ; err == nil && num <= 999; num++ {
190 fname = w.filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
191 _, err = os.Lstat(fname)
192 }
193 // return error if the last file checked still existed
194 if err == nil {
195 return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
196 }
197
198 // block Logger's io.Writer
199 w.mw.Lock()
200 defer w.mw.Unlock()
201
202 fd := w.mw.fd
203 fd.Close()
204
205 // close fd before rename
206 // Rename the file to its newfound home
207 err = os.Rename(w.filename, fname)
208 if err != nil {
209 return fmt.Errorf("Rotate: %s\n", err)
210 }
211
212 // re-start logger
213 err = w.StartLogger()
214 if err != nil {
215 return fmt.Errorf("Rotate StartLogger: %s\n", err)
216 }
217
218 go w.deleteOldLog()
219 }
220
221 return nil
222 }
223
224 func (w *FileLogWriter) deleteOldLog() {
225 dir := path.Dir(w.filename)
226 filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
227 if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.maxdays) {
228 if strings.HasPrefix(filepath.Base(path), filepath.Base(w.filename)) {
229 os.Remove(path)
230 }
231 }
232 return nil
233 })
234 }
235
236 func (w *FileLogWriter) Destroy() {
237 w.mw.fd.Close()
238 }
239
240 func init() {
241 Register("file", NewFileWriter)
242 }
1 package logs
2
3 import (
4 "bufio"
5 "fmt"
6 "os"
7 "testing"
8 "time"
9 )
10
11 func TestFile(t *testing.T) {
12 log := NewLogger(10000)
13 log.SetLogger("file", `{"filename":"test.log"}`)
14 log.Trace("test")
15 log.Info("info")
16 log.Debug("debug")
17 log.Warn("warning")
18 log.Error("error")
19 log.Critical("critical")
20 time.Sleep(time.Second * 4)
21 f, err := os.Open("test.log")
22 if err != nil {
23 t.Fatal(err)
24 }
25 b := bufio.NewReader(f)
26 linenum := 0
27 for {
28 line, _, err := b.ReadLine()
29 if err != nil {
30 break
31 }
32 if len(line) > 0 {
33 linenum++
34 }
35 }
36 if linenum != 6 {
37 t.Fatal(linenum, "not line 6")
38 }
39 os.Remove("test.log")
40 }
41
42 func TestFile2(t *testing.T) {
43 log := NewLogger(10000)
44 log.SetLogger("file", `{"filename":"test2.log","level":2}`)
45 log.Trace("test")
46 log.Info("info")
47 log.Debug("debug")
48 log.Warn("warning")
49 log.Error("error")
50 log.Critical("critical")
51 time.Sleep(time.Second * 4)
52 f, err := os.Open("test2.log")
53 if err != nil {
54 t.Fatal(err)
55 }
56 b := bufio.NewReader(f)
57 linenum := 0
58 for {
59 line, _, err := b.ReadLine()
60 if err != nil {
61 break
62 }
63 if len(line) > 0 {
64 linenum++
65 }
66 }
67 if linenum != 4 {
68 t.Fatal(linenum, "not line 4")
69 }
70 os.Remove("test2.log")
71 }
72
73 func TestFileRotate(t *testing.T) {
74 log := NewLogger(10000)
75 log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
76 log.Trace("test")
77 log.Info("info")
78 log.Debug("debug")
79 log.Warn("warning")
80 log.Error("error")
81 log.Critical("critical")
82 time.Sleep(time.Second * 4)
83 rotatename := "test3.log" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)
84 b, err := exists(rotatename)
85 if !b || err != nil {
86 t.Fatal("rotate not gen")
87 }
88 os.Remove(rotatename)
89 os.Remove("test3.log")
90 }
91
92 func exists(path string) (bool, error) {
93 _, err := os.Stat(path)
94 if err == nil {
95 return true, nil
96 }
97 if os.IsNotExist(err) {
98 return false, nil
99 }
100 return false, err
101 }
1 package logs
2
3 import (
4 "fmt"
5 "sync"
6 )
7
8 const (
9 LevelTrace = iota
10 LevelDebug
11 LevelInfo
12 LevelWarn
13 LevelError
14 LevelCritical
15 )
16
17 type loggerType func() LoggerInterface
18
19 type LoggerInterface interface {
20 Init(config string) error
21 WriteMsg(msg string, level int) error
22 Destroy()
23 }
24
25 var adapters = make(map[string]loggerType)
26
27 // Register makes a log provide available by the provided name.
28 // If Register is called twice with the same name or if driver is nil,
29 // it panics.
30 func Register(name string, log loggerType) {
31 if log == nil {
32 panic("logs: Register provide is nil")
33 }
34 if _, dup := adapters[name]; dup {
35 panic("logs: Register called twice for provider " + name)
36 }
37 adapters[name] = log
38 }
39
40 type BeeLogger struct {
41 lock sync.Mutex
42 level int
43 msg chan *logMsg
44 outputs map[string]LoggerInterface
45 }
46
47 type logMsg struct {
48 level int
49 msg string
50 }
51
52 // config need to be correct JSON as string: {"interval":360}
53 func NewLogger(channellen int64) *BeeLogger {
54 bl := new(BeeLogger)
55 bl.msg = make(chan *logMsg, channellen)
56 bl.outputs = make(map[string]LoggerInterface)
57 bl.SetLogger("console", "") // default output to console
58 go bl.StartLogger()
59 return bl
60 }
61
62 func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
63 bl.lock.Lock()
64 defer bl.lock.Unlock()
65 if log, ok := adapters[adaptername]; ok {
66 lg := log()
67 lg.Init(config)
68 bl.outputs[adaptername] = lg
69 return nil
70 } else {
71 return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
72 }
73 }
74
75 func (bl *BeeLogger) DelLogger(adaptername string) error {
76 bl.lock.Lock()
77 defer bl.lock.Unlock()
78 if lg, ok := bl.outputs[adaptername]; ok {
79 lg.Destroy()
80 delete(bl.outputs, adaptername)
81 return nil
82 } else {
83 return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
84 }
85 }
86
87 func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
88 if bl.level > loglevel {
89 return nil
90 }
91 lm := new(logMsg)
92 lm.level = loglevel
93 lm.msg = msg
94 bl.msg <- lm
95 return nil
96 }
97
98 func (bl *BeeLogger) SetLevel(l int) {
99 bl.level = l
100 }
101
102 func (bl *BeeLogger) StartLogger() {
103 for {
104 select {
105 case bm := <-bl.msg:
106 for _, l := range bl.outputs {
107 l.WriteMsg(bm.msg, bm.level)
108 }
109 }
110 }
111 }
112
113 func (bl *BeeLogger) Trace(format string, v ...interface{}) {
114 msg := fmt.Sprintf("[T] "+format, v...)
115 bl.writerMsg(LevelTrace, msg)
116 }
117
118 func (bl *BeeLogger) Debug(format string, v ...interface{}) {
119 msg := fmt.Sprintf("[D] "+format, v...)
120 bl.writerMsg(LevelDebug, msg)
121 }
122
123 func (bl *BeeLogger) Info(format string, v ...interface{}) {
124 msg := fmt.Sprintf("[I] "+format, v...)
125 bl.writerMsg(LevelInfo, msg)
126 }
127
128 func (bl *BeeLogger) Warn(format string, v ...interface{}) {
129 msg := fmt.Sprintf("[W] "+format, v...)
130 bl.writerMsg(LevelWarn, msg)
131 }
132
133 func (bl *BeeLogger) Error(format string, v ...interface{}) {
134 msg := fmt.Sprintf("[E] "+format, v...)
135 bl.writerMsg(LevelError, msg)
136 }
137
138 func (bl *BeeLogger) Critical(format string, v ...interface{}) {
139 msg := fmt.Sprintf("[C] "+format, v...)
140 bl.writerMsg(LevelCritical, msg)
141 }
142
143 func (bl *BeeLogger) Close() {
144 for _, l := range bl.outputs {
145 l.Destroy()
146 }
147 }
1 package logs
2
3 import (
4 "encoding/json"
5 "errors"
6 "net/smtp"
7 "strings"
8 )
9
10 const (
11 subjectPhrase = "Diagnostic message from server"
12 )
13
14 // smtpWriter is used to send emails via given SMTP-server.
15 type SmtpWriter struct {
16 username string
17 password string
18 host string
19 subject string
20 recipientAddresses []string
21 level int
22 }
23
24 func NewSmtpWriter() LoggerInterface {
25 return &SmtpWriter{level: LevelTrace}
26 }
27
28 func (s *SmtpWriter) Init(jsonconfig string) error {
29 var m map[string]interface{}
30 err := json.Unmarshal([]byte(jsonconfig), &m)
31 if err != nil {
32 return err
33 }
34 if username, ok := m["username"]; !ok {
35 return errors.New("smtp config must have auth username")
36 } else if password, ok := m["password"]; !ok {
37 return errors.New("smtp config must have auth password")
38 } else if hostname, ok := m["host"]; !ok {
39 return errors.New("smtp config must have host like 'mail.example.com:25'")
40 } else if sendTos, ok := m["sendTos"]; !ok {
41 return errors.New("smtp config must have sendTos")
42 } else {
43 s.username = username.(string)
44 s.password = password.(string)
45 s.host = hostname.(string)
46 for _, v := range sendTos.([]interface{}) {
47 s.recipientAddresses = append(s.recipientAddresses, v.(string))
48 }
49 }
50
51 if subject, ok := m["subject"]; ok {
52 s.subject = subject.(string)
53 } else {
54 s.subject = subjectPhrase
55 }
56 if lv, ok := m["level"]; ok {
57 s.level = int(lv.(float64))
58 }
59 return nil
60 }
61
62 func (s *SmtpWriter) WriteMsg(msg string, level int) error {
63 if level < s.level {
64 return nil
65 }
66
67 hp := strings.Split(s.host, ":")
68
69 // Set up authentication information.
70 auth := smtp.PlainAuth(
71 "",
72 s.username,
73 s.password,
74 hp[0],
75 )
76 // Connect to the server, authenticate, set the sender and recipient,
77 // and send the email all in one step.
78 content_type := "Content-Type: text/plain" + "; charset=UTF-8"
79 mailmsg := []byte("To: " + strings.Join(s.recipientAddresses, ";") + "\r\nFrom: " + s.username + "<" + s.username +
80 ">\r\nSubject: " + s.subject + "\r\n" + content_type + "\r\n\r\n" + msg)
81 err := smtp.SendMail(
82 s.host,
83 auth,
84 s.username,
85 s.recipientAddresses,
86 mailmsg,
87 )
88 return err
89 }
90
91 func (s *SmtpWriter) Destroy() {
92 return
93 }
94
95 func init() {
96 Register("smtp", NewSmtpWriter)
97 }
1 package logs
2
3 import (
4 "testing"
5 )
6
7 func TestSmtp(t *testing.T) {
8 log := NewLogger(10000)
9 log.SetLogger("smtp", `{"username":"xxxxxx@gmail.com","password":"xxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
10 log.Critical("sendmail critical")
11 }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!