Skip to content

Commit ad4d60f

Browse files
authored
Merge pull request #169 from mackerelio/add-alerts-logs-api
Implemented the alert logs API
2 parents 1dcc797 + f1ef2e4 commit ad4d60f

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed

alerts.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,36 @@ type UpdateAlertResponse struct {
5959
Memo string `json:"memo,omitempty"`
6060
}
6161

62+
// AlertLog is the log of alert
63+
// See https://mackerel.io/api-docs/entry/alerts#logs
64+
type AlertLog struct {
65+
ID string `json:"id"`
66+
CreatedAt int64 `json:"createdAt"`
67+
Status string `json:"status"`
68+
Trigger string `json:"trigger"`
69+
MonitorID *string `json:"monitorId"`
70+
TargetValue *float64 `json:"targetValue"`
71+
StatusDetail *struct {
72+
Type string `json:"type"`
73+
Detail struct {
74+
Message string `json:"message"`
75+
Memo string `json:"memo"`
76+
} `json:"detail"`
77+
} `json:"statusDetail,omitempty"`
78+
}
79+
80+
// FindAlertLogsParam is the parameters for FindAlertLogs
81+
type FindAlertLogsParam struct {
82+
NextId *string
83+
Limit *int
84+
}
85+
86+
// FindAlertLogsResp is for FindAlertLogs
87+
type FindAlertLogsResp struct {
88+
AlertLogs []*AlertLog `json:"logs"`
89+
NextID string `json:"nextId,omitempty"`
90+
}
91+
6292
func (c *Client) findAlertsWithParams(params url.Values) (*AlertsResp, error) {
6393
return requestGetWithParams[AlertsResp](c, "/api/v0/alerts", params)
6494
}
@@ -107,3 +137,23 @@ func (c *Client) UpdateAlert(alertID string, param UpdateAlertParam) (*UpdateAle
107137
path := fmt.Sprintf("/api/v0/alerts/%s", alertID)
108138
return requestPut[UpdateAlertResponse](c, path, param)
109139
}
140+
141+
func (p FindAlertLogsParam) toValues() url.Values {
142+
values := url.Values{}
143+
if p.NextId != nil {
144+
values.Set("nextId", *p.NextId)
145+
}
146+
if p.Limit != nil {
147+
values.Set("limit", fmt.Sprintf("%d", *p.Limit))
148+
}
149+
return values
150+
}
151+
152+
// FindAlertLogs gets alert logs.
153+
func (c *Client) FindAlertLogs(alertId string, params *FindAlertLogsParam) (*FindAlertLogsResp, error) {
154+
path := fmt.Sprintf("/api/v0/alerts/%s/logs", alertId)
155+
if params == nil {
156+
return requestGet[FindAlertLogsResp](c, path)
157+
}
158+
return requestGetWithParams[FindAlertLogsResp](c, path, params.toValues())
159+
}

alerts_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,3 +381,167 @@ func TestGetAlert(t *testing.T) {
381381
t.Errorf("Wrong data for alert: %v", alert)
382382
}
383383
}
384+
385+
func TestFindAlertLogs(t *testing.T) {
386+
ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
387+
url := fmt.Sprintf("/api/v0/alerts/%s/logs", "2wpLU5fBXbG")
388+
if req.URL.Path != url {
389+
t.Error("request URL should be /api/v0/alerts/<ID>/logs but: ", req.URL.Path)
390+
}
391+
392+
if req.Method != "GET" {
393+
t.Error("request method should be GET but: ", req.Method)
394+
}
395+
396+
respJSON, _ := json.Marshal(map[string]interface{}{
397+
"logs": []map[string]interface{}{
398+
{
399+
"id": "5m7fewuu5tS",
400+
"createdAt": 1735290407,
401+
"status": "WARNING",
402+
"trigger": "monitoring",
403+
"monitorId": "5m72DB7s7sU",
404+
"targetValue": (*float64)(nil),
405+
"statusDetail": map[string]interface{}{
406+
"type": "check",
407+
"detail": map[string]interface{}{
408+
"message": "Uptime WARNING: 0 day(s) 0 hour(s) 6 minute(s) (398 second(s))",
409+
"memo": "",
410+
},
411+
},
412+
}, {
413+
"id": "5m7fewuu5tS",
414+
"createdAt": 1735290407,
415+
"status": "WARNING",
416+
"trigger": "monitoring",
417+
"monitorId": "5m72DB7s7sU",
418+
"targetValue": (*float64)(nil),
419+
"statusDetail": nil,
420+
},
421+
},
422+
"nextId": "2fsf8jRxFG1",
423+
})
424+
425+
res.Header()["Content-Type"] = []string{"application/json"}
426+
fmt.Fprint(res, string(respJSON))
427+
}))
428+
defer ts.Close()
429+
430+
client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
431+
logs, err := client.FindAlertLogs("2wpLU5fBXbG", nil)
432+
if err != nil {
433+
t.Error("err should be nil but: ", err)
434+
}
435+
436+
if len(logs.AlertLogs) != 2 {
437+
t.Error("logs should have 1 elements but: ", len(logs.AlertLogs))
438+
}
439+
440+
if logs.NextID != "2fsf8jRxFG1" {
441+
t.Error("request sends json including nextId but: ", logs.NextID)
442+
}
443+
444+
if logs.AlertLogs[0].ID != "5m7fewuu5tS" {
445+
t.Error("alert id should be \"5m7fewuu5tS\" but: ", logs.AlertLogs[0].ID)
446+
}
447+
448+
if logs.AlertLogs[0].CreatedAt != 1735290407 {
449+
t.Error("createdAt should be 1735290407 but: ", logs.AlertLogs[0].CreatedAt)
450+
}
451+
452+
if logs.AlertLogs[0].Trigger != "monitoring" {
453+
t.Error("trigger should be \"monitoring\" but: ", logs.AlertLogs[0].Trigger)
454+
}
455+
456+
if *logs.AlertLogs[0].MonitorID != "5m72DB7s7sU" {
457+
t.Error("monitorId should be \"5m72DB7s7sU\" but: ", *logs.AlertLogs[0].MonitorID)
458+
}
459+
460+
if logs.AlertLogs[0].TargetValue != nil {
461+
t.Error("targetValue should be nil but: ", logs.AlertLogs[0].TargetValue)
462+
}
463+
464+
if logs.AlertLogs[0].Status != "WARNING" {
465+
t.Error("alert status should be \"WARNING\" but: ", logs.AlertLogs[0].Status)
466+
}
467+
468+
if logs.AlertLogs[0].StatusDetail.Type != "check" {
469+
t.Error("statusDetail type should be \"check\" but: ", logs.AlertLogs[0].StatusDetail.Type)
470+
}
471+
472+
if logs.AlertLogs[0].StatusDetail.Detail.Message != "Uptime WARNING: 0 day(s) 0 hour(s) 6 minute(s) (398 second(s))" {
473+
t.Error("statusDetail message should be \"Uptime WARNING: 0 day(s) 0 hour(s) 6 minute(s) (398 second(s))\" but: ", logs.AlertLogs[0].StatusDetail.Detail.Message)
474+
}
475+
476+
if logs.AlertLogs[0].StatusDetail.Detail.Memo != "" {
477+
t.Error("statusDetail memo should be empty but: ", logs.AlertLogs[0].StatusDetail.Detail.Memo)
478+
}
479+
480+
if logs.AlertLogs[1].StatusDetail != nil {
481+
t.Error("statusDetail should be nil but: ", logs.AlertLogs[1].StatusDetail)
482+
}
483+
}
484+
485+
func TestFindAlertLogsWithOption(t *testing.T) {
486+
ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
487+
url := fmt.Sprintf("/api/v0/alerts/%s/logs", "2wpLU5fBXbG")
488+
if req.URL.Path != url {
489+
t.Error("request URL should be /api/v0/alerts/<ID>/logs but: ", req.URL.Path)
490+
}
491+
492+
q := req.URL.Query()
493+
if q.Get("nextId") != "2fsf8jRxFG1" {
494+
t.Error("request nextId should be 2fsf8jRxFG1 but: ", q.Get("nextId"))
495+
}
496+
497+
if q.Get("limit") != "10" {
498+
t.Error("request limit should be 10 but: ", q.Get("limit"))
499+
}
500+
501+
if req.Method != "GET" {
502+
t.Error("request method should be GET but: ", req.Method)
503+
}
504+
505+
respJSON, _ := json.Marshal(map[string]interface{}{
506+
"logs": []map[string]interface{}{
507+
{
508+
"id": "5m7fewuu5tS",
509+
"createdAt": 1735290407,
510+
"status": "WARNING",
511+
"trigger": "monitoring",
512+
"monitorId": "5m72DB7s7sU",
513+
"targetValue": (*float64)(nil),
514+
"statusDetail": map[string]interface{}{
515+
"type": "check",
516+
"detail": map[string]interface{}{
517+
"message": "Uptime WARNING: 0 day(s) 0 hour(s) 6 minute(s) (398 second(s))",
518+
"memo": "",
519+
},
520+
},
521+
}, {
522+
"id": "5m7fewuu5tS",
523+
"createdAt": 1735290407,
524+
"status": "WARNING",
525+
"trigger": "monitoring",
526+
"monitorId": "5m72DB7s7sU",
527+
"targetValue": (*float64)(nil),
528+
"statusDetail": nil,
529+
},
530+
},
531+
"nextId": "2fsf8jRxFG1",
532+
})
533+
534+
res.Header()["Content-Type"] = []string{"application/json"}
535+
fmt.Fprint(res, string(respJSON))
536+
}))
537+
defer ts.Close()
538+
539+
client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
540+
_, err := client.FindAlertLogs("2wpLU5fBXbG", &FindAlertLogsParam{
541+
NextId: ToPtr("2fsf8jRxFG1"),
542+
Limit: ToPtr(10),
543+
})
544+
if err != nil {
545+
t.Error("err should be nil but: ", err)
546+
}
547+
}

mackerel.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ func (c *Client) compatRequestJSON(method string, path string, payload interface
210210
return c.Request(req)
211211
}
212212

213+
// ToPtr returns a pointer to the given value of any type.
214+
func ToPtr[T any](v T) *T {
215+
return &v
216+
}
217+
213218
// Deprecated: use other prefered method.
214219
func (c *Client) PostJSON(path string, payload interface{}) (*http.Response, error) {
215220
return c.compatRequestJSON(http.MethodPost, path, payload)

0 commit comments

Comments
 (0)