Merge pull request #394 from xiekeyang/feature-panic-hook
Feature: Add Hook for Web Application Panic
This commit is contained in:
commit
0d40913b9a
7 changed files with 223 additions and 2 deletions
|
@ -4,6 +4,20 @@ log:
|
||||||
fields:
|
fields:
|
||||||
service: registry
|
service: registry
|
||||||
environment: development
|
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:
|
storage:
|
||||||
cache:
|
cache:
|
||||||
blobdescriptor: redis
|
blobdescriptor: redis
|
||||||
|
@ -41,5 +55,5 @@ notifications:
|
||||||
timeout: 1s
|
timeout: 1s
|
||||||
threshold: 10
|
threshold: 10
|
||||||
backoff: 1s
|
backoff: 1s
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ type Configuration struct {
|
||||||
// Fields allows users to specify static string fields to include in
|
// Fields allows users to specify static string fields to include in
|
||||||
// the logger context.
|
// the logger context.
|
||||||
Fields map[string]interface{} `yaml:"fields,omitempty"`
|
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
|
// Loglevel is the level at which registry operations are logged. This is
|
||||||
|
@ -126,6 +130,47 @@ type Configuration struct {
|
||||||
} `yaml:"redis,omitempty"`
|
} `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
|
// v0_1Configuration is a Version 0.1 Configuration struct
|
||||||
// This is currently aliased to Configuration, as it is the current version
|
// This is currently aliased to Configuration, as it is the current version
|
||||||
type v0_1Configuration Configuration
|
type v0_1Configuration Configuration
|
||||||
|
|
|
@ -20,6 +20,7 @@ var configStruct = Configuration{
|
||||||
Level Loglevel `yaml:"level"`
|
Level Loglevel `yaml:"level"`
|
||||||
Formatter string `yaml:"formatter,omitempty"`
|
Formatter string `yaml:"formatter,omitempty"`
|
||||||
Fields map[string]interface{} `yaml:"fields,omitempty"`
|
Fields map[string]interface{} `yaml:"fields,omitempty"`
|
||||||
|
Hooks []LogHook `yaml:"hooks,omitempty"`
|
||||||
}{
|
}{
|
||||||
Fields: map[string]interface{}{"environment": "test"},
|
Fields: map[string]interface{}{"environment": "test"},
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,6 +25,20 @@ log:
|
||||||
fields:
|
fields:
|
||||||
service: registry
|
service: registry
|
||||||
environment: staging
|
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"
|
loglevel: debug # deprecated: use "log"
|
||||||
storage:
|
storage:
|
||||||
filesystem:
|
filesystem:
|
||||||
|
@ -233,6 +247,28 @@ log:
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</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
|
## loglevel
|
||||||
|
|
||||||
|
|
|
@ -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
registry/handlers/hooks.go
Normal file
53
registry/handlers/hooks.go
Normal 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
registry/handlers/mail.go
Normal file
45
registry/handlers/mail.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue