diff --git a/config.go b/config.go index c4ebe20..b1e4e6c 100644 --- a/config.go +++ b/config.go @@ -15,9 +15,15 @@ type Trap struct { Format string `yaml:"format"` } +type Mackerel struct { + ApiKey string `yaml:"x-api-key"` + HostID string `yaml:"host-id"` +} + type Config struct { MIB *MIB `yaml:"mib"` TrapServer *TrapServer `yaml:"snmp"` Trap []*Trap `yaml:"trap"` Debug bool `yaml:"debug"` + Mackerel *Mackerel `yaml:"mackerel"` } diff --git a/config.yaml.sample b/config.yaml.sample index b0292b0..44737c3 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -22,4 +22,6 @@ trap: format: '{{ addr }} {{ read "IF-MIB::ifDescr" }} is linkdown' - ident: .1.3.6.1.6.3.1.1.5.4 format: '{{ addr }} {{ read "IF-MIB::ifDescr" }} is linkup' - +mackerel: + x-api-key: **** + host-id: **** diff --git a/go.mod b/go.mod index b7ede28..4cc38b5 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/gosnmp/gosnmp v1.35.0 + github.com/mackerelio/mackerel-client-go v0.24.0 github.com/sleepinggenius2/gosmi v0.4.4 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index d57a72d..9f3b2f2 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gosnmp/gosnmp v1.35.0 h1:EuWWNPxTCdAUx2/NbQcSa3WdNxjzpy4Phv57b4MWpJM= github.com/gosnmp/gosnmp v1.35.0/go.mod h1:2AvKZ3n9aEl5TJEo/fFmf/FGO4Nj4cVeEc5yuk88CYc= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/mackerelio/mackerel-client-go v0.24.0 h1:Y9FeTgrQlDdtLU7FtMM6hd5mL5T4TvGCVUY0LH+bceQ= +github.com/mackerelio/mackerel-client-go v0.24.0/go.mod h1:b4qVMQi+w4rxtKQIFycLWXNBtIi9d0r571RzYmg/aXo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/mackerel.go b/mackerel.go new file mode 100644 index 0000000..262bafc --- /dev/null +++ b/mackerel.go @@ -0,0 +1,47 @@ +package main + +import ( + "context" + "fmt" + "log" + "time" + + mackerel "github.com/mackerelio/mackerel-client-go" +) + +type mackerelCheck struct { + Addr string + Message string +} + +func sendToMackerel(ctx context.Context, client *mackerel.Client, hostId string) { + if buffers.Len() == 0 { + return + } + + e := buffers.Front() + // log.Infof("send current value: %#v", e.Value) + // log.Infof("buffers len: %d", buffers.Len()) + + v := e.Value.(mackerelCheck) + + reports := []*mackerel.CheckReport{ + { + Source: mackerel.NewCheckSourceHost(hostId), + Status: mackerel.CheckStatusWarning, + Name: fmt.Sprintf("snmptrap %s", v.Addr), + Message: v.Message, + OccurredAt: time.Now().Unix(), + }, + } + err := client.PostCheckReports(&mackerel.CheckReports{Reports: reports}) + if err != nil { + log.Println(err) + return + } else { + log.Printf("mackerel success: %q %q", v.Addr, v.Message) + } + mutex.Lock() + buffers.Remove(e) + mutex.Unlock() +} diff --git a/trap.go b/trap.go index 34e28f2..fe6197b 100644 --- a/trap.go +++ b/trap.go @@ -1,14 +1,21 @@ package main import ( + "bytes" + "container/list" + "context" "fmt" "log" "net" "os" + "os/signal" "strings" + "sync" "text/template" + "time" g "github.com/gosnmp/gosnmp" + mackerel "github.com/mackerelio/mackerel-client-go" "github.com/sleepinggenius2/gosmi/types" "gopkg.in/yaml.v3" ) @@ -17,23 +24,27 @@ const SnmpTrapOIDPrefix = ".1.3.6.1.6.3.1.1.4.1" var mibParser SMI var c Config +var buffers = list.New() +var mutex = &sync.Mutex{} func main() { - defer func() { - mibParser.Close() - }() - // TODO args. f, err := os.ReadFile("config.yaml") + // load config. err = yaml.Unmarshal(f, &c) if err != nil { log.Fatalf("error: %v", err) } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) + defer stop() + + // init mib parser mibParser.Modules = c.MIB.LoadModules mibParser.Paths = c.MIB.Directory mibParser.Init() + defer mibParser.Close() // template tests. funcmap := template.FuncMap{ @@ -53,6 +64,7 @@ func main() { } } + // trapListner trapListner := g.NewTrapListener() trapListner.OnNewTrap = trapHandler trapListner.Params = g.Default @@ -60,10 +72,40 @@ func main() { trapListner.Params.Logger = g.NewLogger(log.New(os.Stdout, "", 0)) } - err = trapListner.Listen(net.JoinHostPort(c.TrapServer.Address, c.TrapServer.Port)) - if err != nil { - log.Fatalf("error in listen: %s", err) - } + client := mackerel.NewClient(c.Mackerel.ApiKey) + + wg := sync.WaitGroup{} + + t := time.NewTicker(500 * time.Millisecond) + defer t.Stop() + + wg.Add(1) + go func() { + err = trapListner.Listen(net.JoinHostPort(c.TrapServer.Address, c.TrapServer.Port)) + if err != nil { + log.Fatalf("error in listen: %s", err) + } + wg.Done() + }() + + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-t.C: + sendToMackerel(ctx, client, c.Mackerel.HostID) + + case <-ctx.Done(): + trapListner.Close() + log.Println("trapListner is close.") + log.Println("cancellation from context:", ctx.Err()) + return + } + } + }() + log.Println("initialized.") + wg.Wait() } func trapHandler(packet *g.SnmpPacket, addr *net.UDPAddr) { @@ -144,7 +186,15 @@ func trapHandler(packet *g.SnmpPacket, addr *net.UDPAddr) { var tpl = template.New("").Funcs(funcmap) - if err := template.Must(tpl.Parse(specificTrapFormat)).Execute(os.Stdout, pad); err != nil { + var wr bytes.Buffer + if err := template.Must(tpl.Parse(specificTrapFormat)).Execute(&wr, pad); err != nil { log.Println(err) } + + mutex.Lock() + buffers.PushBack(mackerelCheck{ + Addr: addr.IP.String(), + Message: wr.String(), + }) + mutex.Unlock() }