From 2b276d5cd1101871f42f8f513bfe5717b3e2aadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 30 Jul 2016 13:36:21 +0200 Subject: [PATCH] feat(alerting): working on alert notification and image rendering --- pkg/components/imguploader/imguploader.go | 13 ++--- pkg/components/renderer/renderer.go | 10 ++-- pkg/services/alerting/eval_context.go | 62 ++++++++++++++++++----- pkg/services/alerting/notifier.go | 42 +++++++++++++++ pkg/services/alerting/notifiers/common.go | 19 ------- pkg/services/alerting/notifiers/email.go | 4 +- pkg/services/alerting/notifiers/slack.go | 8 +-- pkg/setting/setting.go | 9 ++++ vendor/phantomjs/render.js | 11 ++++ 9 files changed, 128 insertions(+), 50 deletions(-) diff --git a/pkg/components/imguploader/imguploader.go b/pkg/components/imguploader/imguploader.go index d14d86279d3..19ce6a17f57 100644 --- a/pkg/components/imguploader/imguploader.go +++ b/pkg/components/imguploader/imguploader.go @@ -3,7 +3,6 @@ package imguploader import ( "io/ioutil" "net/http" - "time" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/util" @@ -11,7 +10,7 @@ import ( ) type Uploader interface { - Upload(imgUrl string) (string, error) + Upload(path string) (string, error) } type S3Uploader struct { @@ -28,13 +27,7 @@ func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader { } } -func (u *S3Uploader) Upload(imgUrl string) (string, error) { - client := http.Client{Timeout: time.Duration(60 * time.Second)} - - res, err := client.Get(imgUrl) - if err != nil { - return "", err - } +func (u *S3Uploader) Upload(path string) (string, error) { s3util.DefaultConfig.AccessKey = u.accessKey s3util.DefaultConfig.SecretKey = u.secretKey @@ -53,7 +46,7 @@ func (u *S3Uploader) Upload(imgUrl string) (string, error) { defer writer.Close() - imgData, err := ioutil.ReadAll(res.Body) + imgData, err := ioutil.ReadFile(path) if err != nil { return "", err } diff --git a/pkg/components/renderer/renderer.go b/pkg/components/renderer/renderer.go index d6091a3cb19..ad8f76e03aa 100644 --- a/pkg/components/renderer/renderer.go +++ b/pkg/components/renderer/renderer.go @@ -9,10 +9,11 @@ import ( "runtime" "time" + "strconv" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" - "strconv" ) type RenderOpts struct { @@ -23,8 +24,10 @@ type RenderOpts struct { Timeout string } +var rendererLog log.Logger = log.New("png-renderer") + func RenderToPng(params *RenderOpts) (string, error) { - log.Info("PhantomRenderer::renderToPng url %v", params.Url) + rendererLog.Info("Rendering", "url", params.Url) var executable = "phantomjs" if runtime.GOOS == "windows" { @@ -71,11 +74,12 @@ func RenderToPng(params *RenderOpts) (string, error) { select { case <-time.After(time.Duration(timeout) * time.Second): if err := cmd.Process.Kill(); err != nil { - log.Error(4, "failed to kill: %v", err) + rendererLog.Error("failed to kill", "error", err) } return "", fmt.Errorf("PhantomRenderer::renderToPng timeout (>%vs)", timeout) case <-done: } + rendererLog.Debug("Image rendered", "path", pngPath) return pngPath, nil } diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index 9dab0d9cf20..7c614fe82a2 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -1,25 +1,31 @@ package alerting import ( + "fmt" "time" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" ) type EvalContext struct { - Firing bool - IsTestRun bool - Events []*Event - Logs []*ResultLogEntry - Error error - Description string - StartTime time.Time - EndTime time.Time - Rule *Rule - DoneChan chan bool - CancelChan chan bool - log log.Logger + Firing bool + IsTestRun bool + Events []*Event + Logs []*ResultLogEntry + Error error + Description string + StartTime time.Time + EndTime time.Time + Rule *Rule + DoneChan chan bool + CancelChan chan bool + log log.Logger + dashboardSlug string + ImagePublicUrl string + ImageOnDiskPath string } func (a *EvalContext) GetDurationMs() float64 { @@ -50,6 +56,38 @@ func (c *EvalContext) GetStateText() string { } } +func (c *EvalContext) getDashboardSlug() (string, error) { + if c.dashboardSlug != "" { + return c.dashboardSlug, nil + } + + slugQuery := &m.GetDashboardSlugByIdQuery{Id: c.Rule.DashboardId} + if err := bus.Dispatch(slugQuery); err != nil { + return "", err + } + + c.dashboardSlug = slugQuery.Result + return c.dashboardSlug, nil +} + +func (c *EvalContext) GetRuleUrl() (string, error) { + if slug, err := c.getDashboardSlug(); err != nil { + return "", err + } else { + ruleUrl := fmt.Sprintf("%sdashboard/db/%s?fullscreen&edit&tab=alert&panelId=%d", setting.AppUrl, slug, c.Rule.PanelId) + return ruleUrl, nil + } +} + +func (c *EvalContext) GetImageUrl() (string, error) { + if slug, err := c.getDashboardSlug(); err != nil { + return "", err + } else { + ruleUrl := fmt.Sprintf("%sdashboard-solo/db/%s?&panelId=%d", setting.AppUrl, slug, c.Rule.PanelId) + return ruleUrl, nil + } +} + func NewEvalContext(rule *Rule) *EvalContext { return &EvalContext{ StartTime: time.Now(), diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 50743c96363..787543f3473 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -4,8 +4,11 @@ import ( "errors" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/imguploader" + "github.com/grafana/grafana/pkg/components/renderer" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" ) type RootNotifier struct { @@ -35,12 +38,51 @@ func (n *RootNotifier) Notify(context *EvalContext) { return } + err = n.uploadImage(context) + if err != nil { + n.log.Error("Failed to upload alert panel image", "error", err) + } + for _, notifier := range notifiers { n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType()) go notifier.Notify(context) } } +func (n *RootNotifier) uploadImage(context *EvalContext) error { + uploader := imguploader.NewS3Uploader( + setting.S3TempImageStoreBucketUrl, + setting.S3TempImageStoreAccessKey, + setting.S3TempImageStoreSecretKey) + + imageUrl, err := context.GetImageUrl() + if err != nil { + return err + } + + renderOpts := &renderer.RenderOpts{ + Url: imageUrl, + Width: "800", + Height: "400", + SessionId: "123", + Timeout: "10", + } + + if imagePath, err := renderer.RenderToPng(renderOpts); err != nil { + return err + } else { + context.ImageOnDiskPath = imagePath + } + + context.ImagePublicUrl, err = uploader.Upload(context.ImageOnDiskPath) + if err != nil { + return err + } + + n.log.Info("uploaded", "url", context.ImagePublicUrl) + return nil +} + func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Notifier, error) { query := &m.GetAlertNotificationsQuery{OrgId: orgId, Ids: notificationIds} diff --git a/pkg/services/alerting/notifiers/common.go b/pkg/services/alerting/notifiers/common.go index ef7c7c06865..48b634c44d7 100644 --- a/pkg/services/alerting/notifiers/common.go +++ b/pkg/services/alerting/notifiers/common.go @@ -1,20 +1 @@ package notifiers - -import ( - "fmt" - - "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/alerting" - "github.com/grafana/grafana/pkg/setting" -) - -func getRuleLink(rule *alerting.Rule) (string, error) { - slugQuery := &m.GetDashboardSlugByIdQuery{Id: rule.DashboardId} - if err := bus.Dispatch(slugQuery); err != nil { - return "", err - } - - ruleLink := fmt.Sprintf("%sdashboard/db/%s?fullscreen&edit&tab=alert&panelId=%d", setting.AppUrl, slugQuery.Result, rule.PanelId) - return ruleLink, nil -} diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index 4fdac5dbfe1..0d05c88995d 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -39,7 +39,7 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) { func (this *EmailNotifier) Notify(context *alerting.EvalContext) { this.log.Info("Sending alert notification to", "addresses", this.Addresses) - ruleLink, err := getRuleLink(context.Rule) + ruleUrl, err := context.GetRuleUrl() if err != nil { this.log.Error("Failed get rule link", "error", err) return @@ -50,7 +50,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) { "RuleState": context.Rule.State, "RuleName": context.Rule.Name, "Severity": context.Rule.Severity, - "RuleLink": ruleLink, + "RuleUrl": ruleUrl, }, To: this.Addresses, Template: "alert_notification.html", diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index 5dcac06f6dc..17967215ddf 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -41,7 +41,7 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) { rule := context.Rule - ruleLink, err := getRuleLink(rule) + ruleUrl, err := context.GetRuleUrl() if err != nil { this.log.Error("Failed get rule link", "error", err) return @@ -69,10 +69,10 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) { // "author_link": "http://flickr.com/bobby/", // "author_icon": "http://flickr.com/icons/bobby.jpg", "title": "[" + context.GetStateText() + "] " + rule.Name, - "title_link": ruleLink, + "title_link": ruleUrl, // "text": "Optional text that appears within the attachment", - "fields": fields, - // "image_url": "http://my-website.com/path/to/image.jpg", + "fields": fields, + "image_url": context.ImagePublicUrl, // "thumb_url": "http://example.com/path/to/thumb.png", "footer": "Grafana v4.0.0", "footer_icon": "http://grafana.org/assets/img/fav32.png", diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3ba136fff24..e1c8cdf1c7d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -148,6 +148,11 @@ var ( // Grafana.NET URL GrafanaNetUrl string + + // S3 temp image store + S3TempImageStoreBucketUrl string + S3TempImageStoreAccessKey string + S3TempImageStoreSecretKey string ) type CommandLineArgs struct { @@ -534,6 +539,10 @@ func NewConfigContext(args *CommandLineArgs) error { GrafanaNetUrl = Cfg.Section("grafana.net").Key("url").MustString("https://grafana.net") + s3temp := Cfg.Section("s3-temp-image-store") + S3TempImageStoreBucketUrl = s3temp.Key("bucket_url").String() + S3TempImageStoreAccessKey = s3temp.Key("access_key").String() + S3TempImageStoreSecretKey = s3temp.Key("secret_key").String() return nil } diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 92000bb9f12..3e10ee852f9 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -35,6 +35,17 @@ page.open(params.url, function (status) { // console.log('Loading a web page: ' + params.url + ' status: ' + status); + page.onError = function(msg, trace) { + var msgStack = ['ERROR: ' + msg]; + if (trace && trace.length) { + msgStack.push('TRACE:'); + trace.forEach(function(t) { + msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); + }); + } + console.error(msgStack.join('\n')); + }; + function checkIsReady() { var panelsRendered = page.evaluate(function() { if (!window.angular) { return false; }