mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(annotations): working on alert annotations, #5694
This commit is contained in:
parent
c5e90b1801
commit
357358898d
@ -4,7 +4,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/kr/s3/s3util"
|
||||
)
|
||||
@ -31,8 +30,6 @@ func (u *S3Uploader) Upload(path string) (string, error) {
|
||||
|
||||
s3util.DefaultConfig.AccessKey = u.accessKey
|
||||
s3util.DefaultConfig.SecretKey = u.secretKey
|
||||
log.Info("AccessKey: %s", u.accessKey)
|
||||
log.Info("SecretKey: %s", u.secretKey)
|
||||
|
||||
header := make(http.Header)
|
||||
header.Add("x-amz-acl", "public-read")
|
||||
|
@ -23,6 +23,7 @@ const (
|
||||
AlertSeverityCritical AlertSeverityType = "critical"
|
||||
AlertSeverityWarning AlertSeverityType = "warning"
|
||||
AlertSeverityInfo AlertSeverityType = "info"
|
||||
AlertSeverityOK AlertSeverityType = "ok"
|
||||
)
|
||||
|
||||
func (s AlertSeverityType) IsValid() bool {
|
||||
|
@ -1,24 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
)
|
||||
|
||||
type AnnotationType string
|
||||
|
||||
type Annotation struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
Type AnnotationType
|
||||
Title string
|
||||
Text string
|
||||
AlertId int64
|
||||
UserId int64
|
||||
PreviousState string
|
||||
NewState string
|
||||
Timestamp time.Time
|
||||
|
||||
Data *simplejson.Json
|
||||
}
|
@ -38,8 +38,8 @@ func (e *Engine) Start() {
|
||||
e.log.Info("Starting Alerting Engine")
|
||||
|
||||
go e.alertingTicker()
|
||||
go e.execDispatch()
|
||||
go e.resultDispatch()
|
||||
go e.execDispatcher()
|
||||
go e.resultDispatcher()
|
||||
}
|
||||
|
||||
func (e *Engine) Stop() {
|
||||
@ -70,7 +70,7 @@ func (e *Engine) alertingTicker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) execDispatch() {
|
||||
func (e *Engine) execDispatcher() {
|
||||
for job := range e.execQueue {
|
||||
e.log.Debug("Starting executing alert rule %s", job.Rule.Name)
|
||||
go e.executeJob(job)
|
||||
@ -92,10 +92,10 @@ func (e *Engine) executeJob(job *Job) {
|
||||
e.resultQueue <- context
|
||||
}
|
||||
|
||||
func (e *Engine) resultDispatch() {
|
||||
func (e *Engine) resultDispatcher() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
e.log.Error("Engine Panic, stopping resultHandler", "error", err, "stack", log.Stack(1))
|
||||
e.log.Error("Panic in resultDispatcher", "error", err, "stack", log.Stack(1))
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -56,6 +56,10 @@ func (c *EvalContext) GetStateText() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EvalContext) GetNotificationTitle() string {
|
||||
return "[" + c.GetStateText() + "] " + c.Rule.Name
|
||||
}
|
||||
|
||||
func (c *EvalContext) getDashboardSlug() (string, error) {
|
||||
if c.dashboardSlug != "" {
|
||||
return c.dashboardSlug, nil
|
||||
|
@ -47,6 +47,7 @@ func (this *EmailNotifier) Notify(context *alerting.EvalContext) {
|
||||
|
||||
cmd := &m.SendEmailCommand{
|
||||
Data: map[string]interface{}{
|
||||
"Title": context.GetNotificationTitle(),
|
||||
"RuleState": context.Rule.State,
|
||||
"RuleName": context.Rule.Name,
|
||||
"Severity": context.Rule.Severity,
|
||||
|
@ -39,8 +39,6 @@ type SlackNotifier struct {
|
||||
func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
|
||||
this.log.Info("Executing slack notification", "ruleId", context.Rule.Id, "notification", this.Name)
|
||||
|
||||
rule := context.Rule
|
||||
|
||||
ruleUrl, err := context.GetRuleUrl()
|
||||
if err != nil {
|
||||
this.log.Error("Failed get rule link", "error", err)
|
||||
@ -68,7 +66,7 @@ func (this *SlackNotifier) Notify(context *alerting.EvalContext) {
|
||||
// "author_name": "Bobby Tables",
|
||||
// "author_link": "http://flickr.com/bobby/",
|
||||
// "author_icon": "http://flickr.com/icons/bobby.jpg",
|
||||
"title": "[" + context.GetStateText() + "] " + rule.Name,
|
||||
"title": context.GetNotificationTitle(),
|
||||
"title_link": ruleUrl,
|
||||
// "text": "Optional text that appears within the attachment",
|
||||
"fields": fields,
|
||||
|
@ -42,7 +42,9 @@ func (this *WebhookNotifier) Notify(context *alerting.EvalContext) {
|
||||
this.log.Info("Sending webhook")
|
||||
|
||||
bodyJSON := simplejson.New()
|
||||
bodyJSON.Set("name", context.Rule.Name)
|
||||
bodyJSON.Set("title", context.GetNotificationTitle())
|
||||
bodyJSON.Set("ruleId", context.Rule.Id)
|
||||
bodyJSON.Set("ruleName", context.Rule.Name)
|
||||
bodyJSON.Set("firing", context.Firing)
|
||||
bodyJSON.Set("severity", context.Rule.Severity)
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"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/services/annotations"
|
||||
)
|
||||
|
||||
type ResultHandler interface {
|
||||
Handle(result *EvalContext)
|
||||
Handle(ctx *EvalContext)
|
||||
}
|
||||
|
||||
type DefaultResultHandler struct {
|
||||
@ -22,32 +25,47 @@ func NewResultHandler() *DefaultResultHandler {
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *DefaultResultHandler) Handle(result *EvalContext) {
|
||||
var newState m.AlertStateType
|
||||
func (handler *DefaultResultHandler) Handle(ctx *EvalContext) {
|
||||
oldState := ctx.Rule.State
|
||||
|
||||
if result.Error != nil {
|
||||
handler.log.Error("Alert Rule Result Error", "ruleId", result.Rule.Id, "error", result.Error)
|
||||
newState = m.AlertStatePending
|
||||
} else if result.Firing {
|
||||
newState = m.AlertStateFiring
|
||||
if ctx.Error != nil {
|
||||
handler.log.Error("Alert Rule Result Error", "ruleId", ctx.Rule.Id, "error", ctx.Error)
|
||||
ctx.Rule.State = m.AlertStatePending
|
||||
} else if ctx.Firing {
|
||||
ctx.Rule.State = m.AlertStateFiring
|
||||
} else {
|
||||
newState = m.AlertStateOK
|
||||
ctx.Rule.State = m.AlertStateOK
|
||||
}
|
||||
|
||||
if result.Rule.State != newState {
|
||||
handler.log.Info("New state change", "alertId", result.Rule.Id, "newState", newState, "oldState", result.Rule.State)
|
||||
if ctx.Rule.State != oldState {
|
||||
handler.log.Info("New state change", "alertId", ctx.Rule.Id, "newState", ctx.Rule.State, "oldState", oldState)
|
||||
|
||||
cmd := &m.SetAlertStateCommand{
|
||||
AlertId: result.Rule.Id,
|
||||
OrgId: result.Rule.OrgId,
|
||||
State: newState,
|
||||
AlertId: ctx.Rule.Id,
|
||||
OrgId: ctx.Rule.OrgId,
|
||||
State: ctx.Rule.State,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
handler.log.Error("Failed to save state", "error", err)
|
||||
}
|
||||
|
||||
result.Rule.State = newState
|
||||
handler.notifier.Notify(result)
|
||||
// save annotation
|
||||
item := annotations.Item{
|
||||
OrgId: ctx.Rule.OrgId,
|
||||
Type: annotations.AlertType,
|
||||
AlertId: ctx.Rule.Id,
|
||||
Title: ctx.Rule.Name,
|
||||
Text: ctx.GetStateText(),
|
||||
NewState: string(ctx.Rule.State),
|
||||
PrevState: string(oldState),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
annotationRepo := annotations.GetRepository()
|
||||
if err := annotationRepo.Save(&item); err != nil {
|
||||
handler.log.Error("Failed to save annotation for new alert state", "error", err)
|
||||
}
|
||||
|
||||
handler.notifier.Notify(ctx)
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func NewScheduler() Scheduler {
|
||||
}
|
||||
|
||||
func (s *SchedulerImpl) Update(rules []*Rule) {
|
||||
s.log.Debug("Scheduling update", "rules.count", len(rules))
|
||||
s.log.Debug("Scheduling update", "ruleCount", len(rules))
|
||||
|
||||
jobs := make(map[int64]*Job, 0)
|
||||
|
||||
|
44
pkg/services/annotations/annotations.go
Normal file
44
pkg/services/annotations/annotations.go
Normal file
@ -0,0 +1,44 @@
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
)
|
||||
|
||||
type Repository interface {
|
||||
Save(item *Item) error
|
||||
}
|
||||
|
||||
var repositoryInstance Repository
|
||||
|
||||
func GetRepository() Repository {
|
||||
return repositoryInstance
|
||||
}
|
||||
|
||||
func SetRepository(rep Repository) {
|
||||
repositoryInstance = rep
|
||||
}
|
||||
|
||||
type ItemType string
|
||||
|
||||
const (
|
||||
AlertType ItemType = "alert"
|
||||
)
|
||||
|
||||
type Item struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
PanelLinkId string `json:"panelLinkId"`
|
||||
Type ItemType `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Text string `json:"text"`
|
||||
Metric string `json:"metric"`
|
||||
AlertId int64 `json:"alertId"`
|
||||
UserId int64 `json:"userId"`
|
||||
PrevState string `json:"prevState"`
|
||||
NewState string `json:"newState"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
Data *simplejson.Json `json:"data"`
|
||||
}
|
21
pkg/services/sqlstore/annotation.go
Normal file
21
pkg/services/sqlstore/annotation.go
Normal file
@ -0,0 +1,21 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
)
|
||||
|
||||
type SqlAnnotationRepo struct {
|
||||
}
|
||||
|
||||
func (r *SqlAnnotationRepo) Save(item *annotations.Item) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
|
||||
if _, err := sess.Table("annotation").Insert(item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
@ -31,21 +31,6 @@ func addAlertMigrations(mg *Migrator) {
|
||||
// create table
|
||||
mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1))
|
||||
|
||||
alert_state_log := Table{
|
||||
Name: "alert_state",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "alert_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "org_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "state", Type: DB_NVarchar, Length: 50, Nullable: false},
|
||||
{Name: "info", Type: DB_Text, Nullable: true},
|
||||
{Name: "triggered_alerts", Type: DB_Text, Nullable: true},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create alert_state_log table v1", NewAddTableMigration(alert_state_log))
|
||||
|
||||
alert_heartbeat := Table{
|
||||
Name: "alert_heartbeat",
|
||||
Columns: []*Column{
|
||||
|
40
pkg/services/sqlstore/migrations/annotation_mig.go
Normal file
40
pkg/services/sqlstore/migrations/annotation_mig.go
Normal file
@ -0,0 +1,40 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
)
|
||||
|
||||
func addAnnotationMig(mg *Migrator) {
|
||||
table := Table{
|
||||
Name: "annotation",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "alert_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "panel_link_id", Type: DB_NVarchar, Length: 32, Nullable: false},
|
||||
{Name: "type", Type: DB_NVarchar, Length: 25, Nullable: false},
|
||||
{Name: "title", Type: DB_Text, Nullable: false},
|
||||
{Name: "text", Type: DB_Text, Nullable: false},
|
||||
{Name: "metric", Type: DB_NVarchar, Length: 255, Nullable: true},
|
||||
{Name: "prev_state", Type: DB_NVarchar, Length: 25, Nullable: false},
|
||||
{Name: "new_state", Type: DB_NVarchar, Length: 25, Nullable: false},
|
||||
{Name: "data", Type: DB_Text, Nullable: false},
|
||||
{Name: "timestamp", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id", "alert_id"}, Type: IndexType},
|
||||
{Cols: []string{"org_id", "type"}, Type: IndexType},
|
||||
{Cols: []string{"org_id", "panel_link_id"}, Type: IndexType},
|
||||
{Cols: []string{"timestamp"}, Type: IndexType},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create annotation table v1", NewAddTableMigration(table))
|
||||
|
||||
// create indices
|
||||
mg.AddMigration("add index annotation org_id & alert_id ", NewAddIndexMigration(table, table.Indices[0]))
|
||||
mg.AddMigration("add index annotation org_id & type", NewAddIndexMigration(table, table.Indices[1]))
|
||||
mg.AddMigration("add index annotation org_id & panel_link_id ", NewAddIndexMigration(table, table.Indices[2]))
|
||||
mg.AddMigration("add index annotation timestamp", NewAddIndexMigration(table, table.Indices[3]))
|
||||
}
|
@ -23,6 +23,7 @@ func AddMigrations(mg *Migrator) {
|
||||
addPlaylistMigrations(mg)
|
||||
addPreferencesMigrations(mg)
|
||||
addAlertMigrations(mg)
|
||||
addAnnotationMig(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/annotations"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -97,6 +98,8 @@ func SetEngine(engine *xorm.Engine) (err error) {
|
||||
return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
|
||||
}
|
||||
|
||||
annotations.SetRepository(&SqlAnnotationRepo{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
var alertQueryDef = new QueryPartDef({
|
||||
type: 'query',
|
||||
params: [
|
||||
{name: "queryRefId", type: 'string', options: ['#A', '#B', '#C', '#D']},
|
||||
{name: "queryRefId", type: 'string', options: ['A', 'B', 'C', 'D', 'E', 'F']},
|
||||
{name: "from", type: "string", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']},
|
||||
{name: "to", type: "string", options: ['now']},
|
||||
],
|
||||
@ -142,7 +142,7 @@ export class AlertTabCtrl {
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
///this.panelCtrl.editingAlert = true;
|
||||
this.panelCtrl.editingAlert = true;
|
||||
this.syncThresholds();
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user