Skip to content

Commit

Permalink
feat: support the syslog notification (#129)
Browse files Browse the repository at this point in the history
* support the syslog notification

* update the README.md

* add name into the log

* fix the log info

* syslog doesn't support windows platform

* change based on review comment

* extract the IsSyslog and HasNetwork function

* modify the code acorrding the review
  • Loading branch information
haoel committed Jun 8, 2022
1 parent 4ee1d55 commit 6f566db
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 37 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,25 @@ Ease Probe supports the following notifications:
- **WeChat Work**. Support Enterprise WeChat Work notification.
- **DingTalk**. Support the DingTalk notification.
- **Lark**. Support the Lark(Feishu) notification.
- **Log File**. Write the notification into a log file
- **Log**. Write the notification into a log file or syslog.
- **SMS**. Support SMS notification with multiple SMS service providers - [Twilio](https://www.twilio.com/sms), [Vonage(Nexmo)](https://developer.vonage.com/messaging/sms/overview), [YunPain](https://www.yunpian.com/doc/en/domestic/list.html)
- **Teams**. Support the [Microsoft Teams](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#setting-up-a-custom-incoming-webhook) notification.

> **Note**:
>
> The notification is **Edge-Triggered Mode**, only notified while the status is changed.
> The Windows platform doesn't support syslog
```YAML
# Notification Configuration
notify:
log:
- name: log file # local log file
file: /var/log/easeprobe.log
- name: Remote syslog # syslog (!!! Not For Windows !!!)
file: syslog # <-- must be "syslog" keyword
host: 127.0.0.1:514 # remote syslog server - optional
network: udp #remote syslog network [tcp, udp] - optional
slack:
- name: "MegaEase#Alert"
webhook: "https://hooks.slack.com/services/........../....../....../"
Expand Down Expand Up @@ -757,6 +765,10 @@ notify:
- name: "Local Log"
file: "/tmp/easeprobe.log"
dry: true
- name: Remote syslog # syslog (!!! Not For Windows !!!)
file: syslog # <-- must be "syslog" keyword
host: 127.0.0.1:514 # remote syslog server - optional
network: udp #remote syslog network [tcp, udp] - optional
# Notify by sms using yunpian https://www.yunpian.com/official/document/sms/zh_cn/domestic_single_send
sms:
- name: "sms alert service - yunpian"
Expand Down
46 changes: 46 additions & 0 deletions notify/log/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022, MegaEase
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package log

import (
"fmt"
"time"

"github.com/megaease/easeprobe/global"

log "github.com/sirupsen/logrus"
)

//SysLogFormatter is log custom format
type SysLogFormatter struct {
Type Type `yaml:"-"`
}

//Format details
func (s *SysLogFormatter) Format(entry *log.Entry) ([]byte, error) {
if s.Type == SysLog {
return []byte(fmt.Sprintf("%s\n", entry.Message)), nil
}

timestamp := time.Now().Local().Format(time.RFC3339)
host := global.GetEaseProbe().Host
app := global.GetEaseProbe().Name

msg := fmt.Sprintf("%s %s %s %s %s\n", timestamp, host, app, entry.Level.String(), entry.Message)
return []byte(msg), nil
}
79 changes: 44 additions & 35 deletions notify/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,62 +18,71 @@
package log

import (
"log"
"bufio"
"os"
"strings"

"github.com/megaease/easeprobe/global"
"github.com/megaease/easeprobe/notify/base"
"github.com/megaease/easeprobe/probe"
"github.com/megaease/easeprobe/report"
"github.com/sirupsen/logrus"

log "github.com/sirupsen/logrus"
)

// Network protocols
const (
TCP = "tcp"
UDP = "udp"
)

// Type is the log type
type Type int

// Log Type
const (
FileLog = iota
SysLog
)

// NotifyConfig is the configuration of the Notify
type NotifyConfig struct {
base.DefaultNotify `yaml:",inline"`
File string `yaml:"file"`

File string `yaml:"file"`
Host string `yaml:"host"`
Network string `yaml:"network"`
Type Type `yaml:"-"`
logger *log.Logger
}

// Config configures the log files
func (c *NotifyConfig) Config(global global.NotifySettings) error {
func (c *NotifyConfig) configLogFile() error {
c.NotifyKind = "log"
c.NotifyFormat = report.Text
if c.Dry {
logrus.Infof("Notification [%s] - [%s] is running on Dry mode!", c.NotifyKind, c.NotifyName)
log.SetOutput(os.Stdout)
return nil
}
c.Type = FileLog
file, err := os.OpenFile(c.File, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
logrus.Errorf("error: %s", err)
log.Errorf("[%s] cannot open file: %s", c.NotifyKind, err)
return err
}
log.SetOutput(file)

logrus.Infof("Notification [%s] - [%s] is configured!", c.Kind(), c.NotifyName)
logrus.Debugf("Notification [%s] - [%s] configuration: %+v", c.Kind(), c.NotifyName, c)
c.logger.SetOutput(file)
log.Infof("[%s] %s - local log file(%s) configured", c.NotifyKind, c.NotifyName, c.File)
return nil
}

// Notify write the message into the file
func (c *NotifyConfig) Notify(result probe.Result) {
if c.Dry {
c.DryNotify(result)
return
// Config configures the log notification
func (c *NotifyConfig) Config(gConf global.NotifySettings) error {
if err := c.ConfigLog(); err != nil {
return err
}
log.Println(result.DebugJSON())
logrus.Infof("Logged the notification for %s (%s)!", result.Name, result.Endpoint)
return c.DefaultNotify.Config(gConf)
}

// NotifyStat write the stat message into the file
func (c *NotifyConfig) NotifyStat(probers []probe.Prober) {
if c.Dry {
c.DryNotifyStat(probers)
return
}
logrus.Infoln("LogFile Sending the Statstics...")
for _, p := range probers {
log.Println(p.Result())
// Log logs the message
func (c *NotifyConfig) Log(title, msg string) error {
scanner := bufio.NewScanner(strings.NewReader(msg))
for scanner.Scan() {
line := scanner.Text()
log.Debugf("[%s] %s", c.NotifyKind, line)
c.logger.Info(line)
}
logrus.Infof("Logged the Statstics into %s!", c.File)

return scanner.Err()
}
130 changes: 130 additions & 0 deletions notify/log/log_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//go:build !windows

/*
* Copyright (c) 2022, MegaEase
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package log

import (
"fmt"
"log/syslog"
"net"
"strconv"
"strings"

"github.com/megaease/easeprobe/global"
"github.com/megaease/easeprobe/report"

log "github.com/sirupsen/logrus"
)

const (
syslogIdentifier = "syslog"
)

func (c *NotifyConfig) checkNetworkProtocol() error {
if strings.TrimSpace(c.Network) == "" {
return fmt.Errorf("protocol is required")
}
if strings.TrimSpace(c.Host) == "" {
return fmt.Errorf("host is required")
}
if c.Network != TCP && c.Network != UDP {
return fmt.Errorf("[%s] invalid protocol: %s", c.NotifyKind, c.Network)
}
_, port, err := net.SplitHostPort(c.Host)
if err != nil {
return fmt.Errorf("[%s] invalid host: %s", c.NotifyKind, c.Host)
}
_, err = strconv.Atoi(port)
if err != nil {
return fmt.Errorf("[%s] invalid port: %s", c.NotifyKind, port)
}
return nil
}

// IsSyslog returns true if the log is syslog
func (c *NotifyConfig) IsSyslog() bool {
return strings.TrimSpace(c.File) == syslogIdentifier
}

// HasNetwork returns true if the log has network configuration
func (c *NotifyConfig) HasNetwork() (bool, error) {
// if is not syslog, then return false
if c.IsSyslog() == false {
return false, nil
}
// if is syslog, but not configured network, then return false
if strings.TrimSpace(c.Network) == "" || strings.TrimSpace(c.Host) == "" {
return false, nil
}
// if the network is configured error, then return false
if err := c.checkNetworkProtocol(); err != nil {
return false, err
}
return true, nil
}

// ConfigLog configures the log
// Unix platform support syslog and log file notification
func (c *NotifyConfig) ConfigLog() error {
c.NotifyKind = "log"
c.NotifyFormat = report.Log
c.NotifySendFunc = c.Log

c.logger = log.New()

isSyslog := c.IsSyslog()
hasNetwork, err := c.HasNetwork()
if err != nil {
return err
}
// syslog && network configuration error
if isSyslog == true && hasNetwork == true { // remote syslog
c.NotifyKind = syslogIdentifier
c.Type = SysLog
if err := c.checkNetworkProtocol(); err != nil {
return err
}
writer, err := syslog.Dial(c.Network, c.Host, syslog.LOG_NOTICE, global.GetEaseProbe().Name)
if err != nil {
log.Errorf("[%s] cannot dial syslog network: %s", c.NotifyKind, err)
return err
}
c.logger.SetOutput(writer)
log.Infof("[%s] %s - remote syslog (%s:%s) configured", c.NotifyKind, c.NotifyName, c.Network, c.Host)
} else if isSyslog == true { // only for local syslog
c.NotifyKind = syslogIdentifier
c.Type = SysLog
writer, err := syslog.New(syslog.LOG_NOTICE, global.GetEaseProbe().Name)
if err != nil {
log.Errorf("[%s] cannot open syslog: %s", c.NotifyKind, err)
return err
}
c.logger.SetOutput(writer)
log.Infof("[%s] %s - local syslog configured!", c.NotifyKind, c.NotifyName)
} else { // just log file
if err := c.configLogFile(); err != nil {
return err
}
}
c.logger.SetFormatter(&SysLogFormatter{
Type: c.Type,
})

return nil
}
27 changes: 27 additions & 0 deletions notify/log/log_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build windows
// +build windows

/*
* Copyright (c) 2022, MegaEase
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package log

// ConfigLog is the config for log
// Windows platform only support log file notification
func (c *NotifyConfig) ConfigLog() error {
return c.configLogFile()
}
10 changes: 9 additions & 1 deletion report/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ import (
log "github.com/sirupsen/logrus"
)

// ToLog convert the result object to Log format
func ToLog(r probe.Result) string {
tpl := `title="%s"; name="%s"; status="%s"; endpoint="%s"; rtt="%s"; time="%s"; message="%s"`
rtt := r.RoundTripTime.Round(time.Millisecond)
return fmt.Sprintf(tpl,
r.Title(), r.Name, r.Status.String(), r.Endpoint, rtt, r.StartTime.Format(r.TimeFormat), r.Message)
}

// ToText convert the result object to ToText
func ToText(r probe.Result) string {
tpl := "[%s] %s\n%s - ⏱ %s\n%s\n%s"
Expand Down Expand Up @@ -219,7 +227,7 @@ func ToLark(r probe.Result) string {
"elements": [
{
"tag": "plain_text",
"content": %s
"content": %s
}
]
}
Expand Down
Loading

0 comments on commit 6f566db

Please sign in to comment.