Merge pull request #394 from xiekeyang/feature-panic-hook
Feature: Add Hook for Web Application Panicmaster
						commit
						0d40913b9a
					
				|  | @ -4,6 +4,20 @@ log: | |||
|   fields: | ||||
|     service: registry | ||||
|     environment: development | ||||
|   hooks: | ||||
|     - type: mail | ||||
|       disabled: true | ||||
|       levels: | ||||
|         - panic | ||||
|       options: | ||||
|         smtp: | ||||
|           addr: mail.example.com:25 | ||||
|           username: mailuser | ||||
|           password: password | ||||
|           insecure: true | ||||
|         from: sender@example.com | ||||
|         to: | ||||
|           - errors@example.com | ||||
| storage: | ||||
|     cache: | ||||
|         blobdescriptor: redis | ||||
|  | @ -41,5 +55,5 @@ notifications: | |||
|           timeout: 1s | ||||
|           threshold: 10 | ||||
|           backoff: 1s | ||||
|           disabled: true | ||||
|     | ||||
|           disabled: true  | ||||
| 
 | ||||
|  |  | |||
|  | @ -29,6 +29,10 @@ type Configuration struct { | |||
| 		// Fields allows users to specify static string fields to include in
 | ||||
| 		// the logger context.
 | ||||
| 		Fields map[string]interface{} `yaml:"fields,omitempty"` | ||||
| 
 | ||||
| 		// Hooks allows users to configurate the log hooks, to enabling the
 | ||||
| 		// sequent handling behavior, when defined levels of log message emit.
 | ||||
| 		Hooks []LogHook `yaml:"hooks,omitempty"` | ||||
| 	} | ||||
| 
 | ||||
| 	// Loglevel is the level at which registry operations are logged. This is
 | ||||
|  | @ -126,6 +130,47 @@ type Configuration struct { | |||
| 	} `yaml:"redis,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // LogHook is composed of hook Level and Type.
 | ||||
| // After hooks configuration, it can execute the next handling automatically,
 | ||||
| // when defined levels of log message emitted.
 | ||||
| // Example: hook can sending an email notification when error log happens in app.
 | ||||
| type LogHook struct { | ||||
| 	// Disable lets user select to enable hook or not.
 | ||||
| 	Disabled bool `yaml:"disabled,omitempty"` | ||||
| 
 | ||||
| 	// Type allows user to select which type of hook handler they want.
 | ||||
| 	Type string `yaml:"type,omitempty"` | ||||
| 
 | ||||
| 	// Levels set which levels of log message will let hook executed.
 | ||||
| 	Levels []string `yaml:"levels,omitempty"` | ||||
| 
 | ||||
| 	// MailOptions allows user to configurate email parameters.
 | ||||
| 	MailOptions MailOptions `yaml:"options,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // MailOptions provides the configuration sections to user, for specific handler.
 | ||||
| type MailOptions struct { | ||||
| 	SMTP struct { | ||||
| 		// Addr defines smtp host address
 | ||||
| 		Addr string `yaml:"addr,omitempty"` | ||||
| 
 | ||||
| 		// Username defines user name to smtp host
 | ||||
| 		Username string `yaml:"username,omitempty"` | ||||
| 
 | ||||
| 		// Password defines password of login user
 | ||||
| 		Password string `yaml:"password,omitempty"` | ||||
| 
 | ||||
| 		// Insecure defines if smtp login skips the secure cerification.
 | ||||
| 		Insecure bool `yaml:"insecure,omitempty"` | ||||
| 	} `yaml:"smtp,omitempty"` | ||||
| 
 | ||||
| 	// From defines mail sending address
 | ||||
| 	From string `yaml:"from,omitempty"` | ||||
| 
 | ||||
| 	// To defines mail receiving address
 | ||||
| 	To []string `yaml:"to,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // v0_1Configuration is a Version 0.1 Configuration struct
 | ||||
| // This is currently aliased to Configuration, as it is the current version
 | ||||
| type v0_1Configuration Configuration | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ var configStruct = Configuration{ | |||
| 		Level     Loglevel               `yaml:"level"` | ||||
| 		Formatter string                 `yaml:"formatter,omitempty"` | ||||
| 		Fields    map[string]interface{} `yaml:"fields,omitempty"` | ||||
| 		Hooks     []LogHook              `yaml:"hooks,omitempty"` | ||||
| 	}{ | ||||
| 		Fields: map[string]interface{}{"environment": "test"}, | ||||
| 	}, | ||||
|  |  | |||
|  | @ -25,6 +25,20 @@ log: | |||
| 	fields: | ||||
| 		service: registry | ||||
| 		environment: staging | ||||
| 	hooks: | ||||
| 		- type: mail | ||||
| 		  disabled: true | ||||
| 		  levels: | ||||
| 			- panic | ||||
| 		  options: | ||||
| 			smtp: | ||||
| 				addr: mail.example.com:25 | ||||
| 				username: mailuser | ||||
| 				password: password | ||||
| 				insecure: true | ||||
| 			from: sender@example.com | ||||
| 			to:  | ||||
| 				- errors@example.com | ||||
| loglevel: debug # deprecated: use "log" | ||||
| storage: | ||||
| 	filesystem: | ||||
|  | @ -233,6 +247,28 @@ log: | |||
|     </td> | ||||
| </table> | ||||
| 
 | ||||
| ## hooks | ||||
| 
 | ||||
| 
 | ||||
| ```yaml  | ||||
| hooks: | ||||
|   - type: mail | ||||
|     levels: | ||||
|       - panic | ||||
|     options: | ||||
|       smtp: | ||||
|         addr: smtp.sendhost.com:25 | ||||
|         username: sendername | ||||
|         password: password | ||||
|         insecure: true | ||||
|       from: name@sendhost.com | ||||
|       to: | ||||
|         - name@receivehost.com | ||||
| ``` | ||||
| 
 | ||||
| The `hooks` subsection configures the logging hooks' behavior. This subsection | ||||
| includes a sequence handler which you can use for sending mail, for example. | ||||
| Refer to `loglevel` to configure the level of messages printed. | ||||
| 
 | ||||
| ## loglevel | ||||
| 
 | ||||
|  |  | |||
|  | @ -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.
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  | @ -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 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue