Commit 2b276d5c by Torkel Ödegaard

feat(alerting): working on alert notification and image rendering

parent f9ddfb43
......@@ -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
}
......
......@@ -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
}
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(),
......
......@@ -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}
......
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
}
......@@ -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",
......
......@@ -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",
......
......@@ -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
}
......
......@@ -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; }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment