forked from TrueCloudLab/distribution
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:
|
||||
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.
|
||||
|
||||
|
|
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