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