From 9d7c6923c19c2afd3e1553a81dbfbc4d99803765 Mon Sep 17 00:00:00 2001 From: xiekeyang Date: Fri, 17 Apr 2015 20:19:20 +0800 Subject: [PATCH] Feature: Web Panic Reporting via hooks This PR is for issue of "email after registry webapp panic" #41, improving my previous design (closed). It use self setting up hooks, to catch panic in web application. And, send email in hooks handle directly, to no use new http server and handler. Signed-off-by: xiekeyang --- docs/handlers/app.go | 27 +++++++++++++++++++++ docs/handlers/hooks.go | 53 ++++++++++++++++++++++++++++++++++++++++++ docs/handlers/mail.go | 45 +++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 docs/handlers/hooks.go create mode 100644 docs/handlers/mail.go diff --git a/docs/handlers/app.go b/docs/handlers/app.go index 1d58e945..77e3a956 100644 --- a/docs/handlers/app.go +++ b/docs/handlers/app.go @@ -9,6 +9,7 @@ import ( "os" "time" + log "github.com/Sirupsen/logrus" "github.com/docker/distribution" "github.com/docker/distribution/configuration" ctxu "github.com/docker/distribution/context" @@ -101,6 +102,7 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App app.configureEvents(&configuration) app.configureRedis(&configuration) + app.configureLogHook(&configuration) // configure storage caches if cc, ok := configuration.Storage["cache"]; ok { @@ -291,6 +293,31 @@ func (app *App) configureRedis(configuration *configuration.Configuration) { })) } +// configureLogHook prepares logging hook parameters. +func (app *App) configureLogHook(configuration *configuration.Configuration) { + logger := ctxu.GetLogger(app).(*log.Entry).Logger + for _, configHook := range configuration.Log.Hooks { + if !configHook.Disabled { + switch configHook.Type { + case "mail": + hook := &logHook{} + hook.LevelsParam = configHook.Levels + hook.Mail = &mailer{ + Addr: configHook.MailOptions.SMTP.Addr, + Username: configHook.MailOptions.SMTP.Username, + Password: configHook.MailOptions.SMTP.Password, + Insecure: configHook.MailOptions.SMTP.Insecure, + From: configHook.MailOptions.From, + To: configHook.MailOptions.To, + } + logger.Hooks.Add(hook) + default: + } + } + } + app.Context = ctxu.WithLogger(app.Context, logger) +} + func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // ensure that request body is always closed. diff --git a/docs/handlers/hooks.go b/docs/handlers/hooks.go new file mode 100644 index 00000000..7bbab4f8 --- /dev/null +++ b/docs/handlers/hooks.go @@ -0,0 +1,53 @@ +package handlers + +import ( + "bytes" + "errors" + "fmt" + "strings" + "text/template" + + "github.com/Sirupsen/logrus" +) + +// logHook is for hooking Panic in web application +type logHook struct { + LevelsParam []string + Mail *mailer +} + +// Fire forwards an error to LogHook +func (hook *logHook) Fire(entry *logrus.Entry) error { + addr := strings.Split(hook.Mail.Addr, ":") + if len(addr) != 2 { + return errors.New("Invalid Mail Address") + } + host := addr[0] + subject := fmt.Sprintf("[%s] %s: %s", entry.Level, host, entry.Message) + + html := ` + {{.Message}} + + {{range $key, $value := .Data}} + {{$key}}: {{$value}} + {{end}} + ` + b := bytes.NewBuffer(make([]byte, 0)) + t := template.Must(template.New("mail body").Parse(html)) + if err := t.Execute(b, entry); err != nil { + return err + } + body := fmt.Sprintf("%s", b) + + return hook.Mail.sendMail(subject, body) +} + +// Levels contains hook levels to be catched +func (hook *logHook) Levels() []logrus.Level { + levels := []logrus.Level{} + for _, v := range hook.LevelsParam { + lv, _ := logrus.ParseLevel(v) + levels = append(levels, lv) + } + return levels +} diff --git a/docs/handlers/mail.go b/docs/handlers/mail.go new file mode 100644 index 00000000..39244909 --- /dev/null +++ b/docs/handlers/mail.go @@ -0,0 +1,45 @@ +package handlers + +import ( + "errors" + "net/smtp" + "strings" +) + +// mailer provides fields of email configuration for sending. +type mailer struct { + Addr, Username, Password, From string + Insecure bool + To []string +} + +// sendMail allows users to send email, only if mail parameters is configured correctly. +func (mail *mailer) sendMail(subject, message string) error { + addr := strings.Split(mail.Addr, ":") + if len(addr) != 2 { + return errors.New("Invalid Mail Address") + } + host := addr[0] + msg := []byte("To:" + strings.Join(mail.To, ";") + + "\r\nFrom: " + mail.From + + "\r\nSubject: " + subject + + "\r\nContent-Type: text/plain\r\n\r\n" + + message) + auth := smtp.PlainAuth( + "", + mail.Username, + mail.Password, + host, + ) + err := smtp.SendMail( + mail.Addr, + auth, + mail.From, + mail.To, + []byte(msg), + ) + if err != nil { + return err + } + return nil +}