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 <keyangxie@126.com>
This commit is contained in:
xiekeyang 2015-04-17 20:19:20 +08:00 committed by xiekeyang
parent 5f553b3cfc
commit 9d7c6923c1
3 changed files with 125 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import (
"os" "os"
"time" "time"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution" "github.com/docker/distribution"
"github.com/docker/distribution/configuration" "github.com/docker/distribution/configuration"
ctxu "github.com/docker/distribution/context" ctxu "github.com/docker/distribution/context"
@ -101,6 +102,7 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
app.configureEvents(&configuration) app.configureEvents(&configuration)
app.configureRedis(&configuration) app.configureRedis(&configuration)
app.configureLogHook(&configuration)
// configure storage caches // configure storage caches
if cc, ok := configuration.Storage["cache"]; ok { 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) { func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() // ensure that request body is always closed. defer r.Body.Close() // ensure that request body is always closed.

53
docs/handlers/hooks.go Normal file
View file

@ -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
}

45
docs/handlers/mail.go Normal file
View file

@ -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
}