add alert to logmania
This commit is contained in:
parent
b4487cdde4
commit
d7ed44e809
|
@ -59,6 +59,8 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
go notifyState.Alert(config.Notify.AlertCheck.Duration, log.Save)
|
||||
|
||||
log.Info("starting logmania")
|
||||
|
||||
receiver = allReceiver.Init(&config.Receive, logChannel)
|
||||
|
|
|
@ -16,8 +16,9 @@ type Config struct {
|
|||
}
|
||||
|
||||
type NotifyConfig struct {
|
||||
StateFile string `toml:"state_file"`
|
||||
XMPP struct {
|
||||
StateFile string `toml:"state_file"`
|
||||
AlertCheck Duration `toml:"alert_check"`
|
||||
XMPP struct {
|
||||
Host string `toml:"host"`
|
||||
Username string `toml:"username"`
|
||||
Password string `toml:"password"`
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration is a TOML datatype
|
||||
// A duration string is a possibly signed sequence of
|
||||
// decimal numbers and a unit suffix,
|
||||
// such as "300s", "1.5h" or "5d".
|
||||
// Valid time units are "s", "m", "h", "d", "w".
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
// UnmarshalTOML parses a duration string.
|
||||
func (d *Duration) UnmarshalTOML(dataInterface interface{}) error {
|
||||
var data string
|
||||
switch dataInterface.(type) {
|
||||
case string:
|
||||
data = dataInterface.(string)
|
||||
default:
|
||||
return fmt.Errorf("invalid duration: \"%s\"", dataInterface)
|
||||
}
|
||||
// " + int + unit + "
|
||||
if len(data) < 2 {
|
||||
return fmt.Errorf("invalid duration: \"%s\"", data)
|
||||
}
|
||||
|
||||
unit := data[len(data)-1]
|
||||
value, err := strconv.Atoi(string(data[:len(data)-1]))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse duration %s: %s", data, err)
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case 's':
|
||||
d.Duration = time.Duration(value) * time.Second
|
||||
case 'm':
|
||||
d.Duration = time.Duration(value) * time.Minute
|
||||
case 'h':
|
||||
d.Duration = time.Duration(value) * time.Hour
|
||||
case 'd':
|
||||
d.Duration = time.Duration(value) * time.Hour * 24
|
||||
case 'w':
|
||||
d.Duration = time.Duration(value) * time.Hour * 24 * 7
|
||||
case 'y':
|
||||
d.Duration = time.Duration(value) * time.Hour * 24 * 365
|
||||
default:
|
||||
return fmt.Errorf("invalid duration unit: %s", string(unit))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var tests = []struct {
|
||||
input string
|
||||
err string
|
||||
duration time.Duration
|
||||
}{
|
||||
{"", "invalid duration: \"\"", 0},
|
||||
{"1x", "invalid duration unit: x", 0},
|
||||
{"1s", "", time.Second},
|
||||
{"73s", "", time.Second * 73},
|
||||
{"1m", "", time.Minute},
|
||||
{"73m", "", time.Minute * 73},
|
||||
{"1h", "", time.Hour},
|
||||
{"43h", "", time.Hour * 43},
|
||||
{"1d", "", time.Hour * 24},
|
||||
{"8d", "", time.Hour * 24 * 8},
|
||||
{"1w", "", time.Hour * 24 * 7},
|
||||
{"52w", "", time.Hour * 24 * 7 * 52},
|
||||
{"1y", "", time.Hour * 24 * 365},
|
||||
{"3y", "", time.Hour * 24 * 365 * 3},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
d := Duration{}
|
||||
err := d.UnmarshalTOML(test.input)
|
||||
duration := d.Duration
|
||||
|
||||
if test.err == "" {
|
||||
assert.NoError(err)
|
||||
assert.Equal(test.duration, duration)
|
||||
} else {
|
||||
assert.EqualError(err, test.err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,18 +10,22 @@ import (
|
|||
"github.com/genofire/logmania/log"
|
||||
)
|
||||
|
||||
const AlertMsg = "alert service from logmania, device did not send new message for a while"
|
||||
|
||||
type NotifyState struct {
|
||||
Hostname map[string]string `json:"hostname"`
|
||||
HostTo map[string]map[string]bool `json:"host_to"`
|
||||
MaxPrioIn map[string]log.LogLevel `json:"maxLevel"`
|
||||
RegexIn map[string]map[string]*regexp.Regexp `json:"regexIn"`
|
||||
Lastseen map[string]time.Time `json:"lastseen,omitempty"`
|
||||
LastseenNotify map[string]time.Time `json:"lastseen_notify,omitempty"`
|
||||
LastseenNotify map[string]time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (state *NotifyState) SendTo(e *log.Entry) []string {
|
||||
if to, ok := state.HostTo[e.Hostname]; ok {
|
||||
state.Lastseen[e.Hostname] = time.Now()
|
||||
if e.Text != AlertMsg && e.Hostname != "" {
|
||||
state.Lastseen[e.Hostname] = time.Now()
|
||||
}
|
||||
var toList []string
|
||||
for toEntry, _ := range to {
|
||||
if lvl := state.MaxPrioIn[toEntry]; e.Level < lvl {
|
||||
|
@ -108,6 +112,26 @@ func (state *NotifyState) Saver(path string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (state *NotifyState) Alert(expired time.Duration, send func(e *log.Entry)) {
|
||||
c := time.Tick(time.Minute)
|
||||
|
||||
for range c {
|
||||
now := time.Now()
|
||||
for host, time := range state.Lastseen {
|
||||
if time.Before(now.Add(expired * -2)) {
|
||||
if timeNotify, ok := state.LastseenNotify[host]; !ok || !time.Before(timeNotify) {
|
||||
state.LastseenNotify[host] = now
|
||||
send(&log.Entry{
|
||||
Hostname: host,
|
||||
Level: log.ErrorLevel,
|
||||
Text: AlertMsg,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SaveJSON to path
|
||||
func (state *NotifyState) SaveJSON(outputFile string) {
|
||||
tmpFile := outputFile + ".tmp"
|
||||
|
|
Loading…
Reference in New Issue