mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): working on alert notification and image rendering
This commit is contained in:
parent
f9ddfb4337
commit
2b276d5cd1
@ -3,7 +3,6 @@ package imguploader
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
@ -11,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Uploader interface {
|
type Uploader interface {
|
||||||
Upload(imgUrl string) (string, error)
|
Upload(path string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type S3Uploader struct {
|
type S3Uploader struct {
|
||||||
@ -28,13 +27,7 @@ func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *S3Uploader) Upload(imgUrl string) (string, error) {
|
func (u *S3Uploader) Upload(path string) (string, error) {
|
||||||
client := http.Client{Timeout: time.Duration(60 * time.Second)}
|
|
||||||
|
|
||||||
res, err := client.Get(imgUrl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
s3util.DefaultConfig.AccessKey = u.accessKey
|
s3util.DefaultConfig.AccessKey = u.accessKey
|
||||||
s3util.DefaultConfig.SecretKey = u.secretKey
|
s3util.DefaultConfig.SecretKey = u.secretKey
|
||||||
@ -53,7 +46,7 @@ func (u *S3Uploader) Upload(imgUrl string) (string, error) {
|
|||||||
|
|
||||||
defer writer.Close()
|
defer writer.Close()
|
||||||
|
|
||||||
imgData, err := ioutil.ReadAll(res.Body)
|
imgData, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RenderOpts struct {
|
type RenderOpts struct {
|
||||||
@ -23,8 +24,10 @@ type RenderOpts struct {
|
|||||||
Timeout string
|
Timeout string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rendererLog log.Logger = log.New("png-renderer")
|
||||||
|
|
||||||
func RenderToPng(params *RenderOpts) (string, error) {
|
func RenderToPng(params *RenderOpts) (string, error) {
|
||||||
log.Info("PhantomRenderer::renderToPng url %v", params.Url)
|
rendererLog.Info("Rendering", "url", params.Url)
|
||||||
|
|
||||||
var executable = "phantomjs"
|
var executable = "phantomjs"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
@ -71,11 +74,12 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
|||||||
select {
|
select {
|
||||||
case <-time.After(time.Duration(timeout) * time.Second):
|
case <-time.After(time.Duration(timeout) * time.Second):
|
||||||
if err := cmd.Process.Kill(); err != nil {
|
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)
|
return "", fmt.Errorf("PhantomRenderer::renderToPng timeout (>%vs)", timeout)
|
||||||
case <-done:
|
case <-done:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rendererLog.Debug("Image rendered", "path", pngPath)
|
||||||
return pngPath, nil
|
return pngPath, nil
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,31 @@
|
|||||||
package alerting
|
package alerting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EvalContext struct {
|
type EvalContext struct {
|
||||||
Firing bool
|
Firing bool
|
||||||
IsTestRun bool
|
IsTestRun bool
|
||||||
Events []*Event
|
Events []*Event
|
||||||
Logs []*ResultLogEntry
|
Logs []*ResultLogEntry
|
||||||
Error error
|
Error error
|
||||||
Description string
|
Description string
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
EndTime time.Time
|
EndTime time.Time
|
||||||
Rule *Rule
|
Rule *Rule
|
||||||
DoneChan chan bool
|
DoneChan chan bool
|
||||||
CancelChan chan bool
|
CancelChan chan bool
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
dashboardSlug string
|
||||||
|
ImagePublicUrl string
|
||||||
|
ImageOnDiskPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *EvalContext) GetDurationMs() float64 {
|
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 {
|
func NewEvalContext(rule *Rule) *EvalContext {
|
||||||
return &EvalContext{
|
return &EvalContext{
|
||||||
StartTime: time.Now(),
|
StartTime: time.Now(),
|
||||||
|
@ -4,8 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"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"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootNotifier struct {
|
type RootNotifier struct {
|
||||||
@ -35,12 +38,51 @@ func (n *RootNotifier) Notify(context *EvalContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = n.uploadImage(context)
|
||||||
|
if err != nil {
|
||||||
|
n.log.Error("Failed to upload alert panel image", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, notifier := range notifiers {
|
for _, notifier := range notifiers {
|
||||||
n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType())
|
n.log.Info("Sending notification", "firing", context.Firing, "type", notifier.GetType())
|
||||||
go notifier.Notify(context)
|
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) {
|
func (n *RootNotifier) getNotifiers(orgId int64, notificationIds []int64) ([]Notifier, error) {
|
||||||
query := &m.GetAlertNotificationsQuery{OrgId: orgId, Ids: notificationIds}
|
query := &m.GetAlertNotificationsQuery{OrgId: orgId, Ids: notificationIds}
|
||||||
|
|
||||||
|
@ -1,20 +1 @@
|
|||||||
package notifiers
|
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) {
|
func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
|
||||||
this.log.Info("Sending alert notification to", "addresses", this.Addresses)
|
this.log.Info("Sending alert notification to", "addresses", this.Addresses)
|
||||||
|
|
||||||
ruleLink, err := getRuleLink(context.Rule)
|
ruleUrl, err := context.GetRuleUrl()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.log.Error("Failed get rule link", "error", err)
|
this.log.Error("Failed get rule link", "error", err)
|
||||||
return
|
return
|
||||||
@ -50,7 +50,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
|
|||||||
"RuleState": context.Rule.State,
|
"RuleState": context.Rule.State,
|
||||||
"RuleName": context.Rule.Name,
|
"RuleName": context.Rule.Name,
|
||||||
"Severity": context.Rule.Severity,
|
"Severity": context.Rule.Severity,
|
||||||
"RuleLink": ruleLink,
|
"RuleUrl": ruleUrl,
|
||||||
},
|
},
|
||||||
To: this.Addresses,
|
To: this.Addresses,
|
||||||
Template: "alert_notification.html",
|
Template: "alert_notification.html",
|
||||||
|
@ -41,7 +41,7 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
|
|||||||
|
|
||||||
rule := context.Rule
|
rule := context.Rule
|
||||||
|
|
||||||
ruleLink, err := getRuleLink(rule)
|
ruleUrl, err := context.GetRuleUrl()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.log.Error("Failed get rule link", "error", err)
|
this.log.Error("Failed get rule link", "error", err)
|
||||||
return
|
return
|
||||||
@ -69,10 +69,10 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
|
|||||||
// "author_link": "http://flickr.com/bobby/",
|
// "author_link": "http://flickr.com/bobby/",
|
||||||
// "author_icon": "http://flickr.com/icons/bobby.jpg",
|
// "author_icon": "http://flickr.com/icons/bobby.jpg",
|
||||||
"title": "[" + context.GetStateText() + "] " + rule.Name,
|
"title": "[" + context.GetStateText() + "] " + rule.Name,
|
||||||
"title_link": ruleLink,
|
"title_link": ruleUrl,
|
||||||
// "text": "Optional text that appears within the attachment",
|
// "text": "Optional text that appears within the attachment",
|
||||||
"fields": fields,
|
"fields": fields,
|
||||||
// "image_url": "http://my-website.com/path/to/image.jpg",
|
"image_url": context.ImagePublicUrl,
|
||||||
// "thumb_url": "http://example.com/path/to/thumb.png",
|
// "thumb_url": "http://example.com/path/to/thumb.png",
|
||||||
"footer": "Grafana v4.0.0",
|
"footer": "Grafana v4.0.0",
|
||||||
"footer_icon": "http://grafana.org/assets/img/fav32.png",
|
"footer_icon": "http://grafana.org/assets/img/fav32.png",
|
||||||
|
@ -148,6 +148,11 @@ var (
|
|||||||
|
|
||||||
// Grafana.NET URL
|
// Grafana.NET URL
|
||||||
GrafanaNetUrl string
|
GrafanaNetUrl string
|
||||||
|
|
||||||
|
// S3 temp image store
|
||||||
|
S3TempImageStoreBucketUrl string
|
||||||
|
S3TempImageStoreAccessKey string
|
||||||
|
S3TempImageStoreSecretKey string
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandLineArgs struct {
|
type CommandLineArgs struct {
|
||||||
@ -534,6 +539,10 @@ func NewConfigContext(args *CommandLineArgs) error {
|
|||||||
|
|
||||||
GrafanaNetUrl = Cfg.Section("grafana.net").Key("url").MustString("https://grafana.net")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
vendor/phantomjs/render.js
vendored
11
vendor/phantomjs/render.js
vendored
@ -35,6 +35,17 @@
|
|||||||
page.open(params.url, function (status) {
|
page.open(params.url, function (status) {
|
||||||
// console.log('Loading a web page: ' + params.url + ' status: ' + 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() {
|
function checkIsReady() {
|
||||||
var panelsRendered = page.evaluate(function() {
|
var panelsRendered = page.evaluate(function() {
|
||||||
if (!window.angular) { return false; }
|
if (!window.angular) { return false; }
|
||||||
|
Loading…
Reference in New Issue
Block a user