commit f37dbedab69a659aeb0e836d2dee6fa47f63d9d8
Author: Wim <wim@42.be>
Date: Sat Feb 23 22:51:27 2019 +0000
diff --git a/bridge/bridge.go b/bridge/bridge.go
index 6b955a9..fdc1ec8 100644
--- a/bridge/bridge.go
+++ b/bridge/bridge.go
@@ -210 +210 @@ package bridge
import (
"strings"
+ "sync"
"github.com/42wim/matterbridge/bridge/config"
"github.com/sirupsen/logrus"
- "sync"
)
type Bridger interface {
@@ -176 +178 @@ type Bridger interface {
type Bridge struct {
Bridger
+ *sync.RWMutex
+
Name string
Account string
Protocol string
@@ -2637 +2834 @@ type Bridge struct {
Log *logrus.Entry
Config config.Config
General *config.Protocol
- *sync.RWMutex
}
type Config struct {
- // General *config.Protocol
- Remote chan config.Message
- Log *logrus.Entry
*Bridge
+
+ Remote chan config.Message
}
// Factory is the factory function to create a bridge
type Factory func(*Config) Bridger
func New(bridge *config.Bridge) *Bridge {
- b := &Bridge{
- Channels: make(map[string]config.ChannelInfo),
- RWMutex: new(sync.RWMutex),
- Joined: make(map[string]bool),
- }
accInfo := strings.Split(bridge.Account, ".")
protocol := accInfo[0]
name := accInfo[1]
- b.Name = name
- b.Protocol = protocol
- b.Account = bridge.Account
- return b
+
+ return &Bridge{
+ RWMutex: new(sync.RWMutex),
+ Channels: make(map[string]config.ChannelInfo),
+ Name: name,
+ Protocol: protocol,
+ Account: bridge.Account,
+ Joined: make(map[string]bool),
+ }
}
func (b *Bridge) JoinChannels() error {
- err := b.joinChannels(b.Channels, b.Joined)
- return err
+ return b.joinChannels(b.Channels, b.Joined)
}
// SetChannelMembers sets the newMembers to the bridge ChannelMembers
diff --git a/bridge/config/config.go b/bridge/config/config.go
index 4791495..61ffe91 100644
--- a/bridge/config/config.go
+++ b/bridge/config/config.go
@@ -87 +86 @@ import (
"time"
"github.com/fsnotify/fsnotify"
- prefixed "github.com/matterbridge/logrus-prefixed-formatter"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
@@ -20463 +20358 @@ type Config interface {
}
type config struct {
- v *viper.Viper
sync.RWMutex
- cv *BridgeValues
+ logger *logrus.Entry
+ v *viper.Viper
+ cv *BridgeValues
}
-func NewConfig(cfgfile string) Config {
- logrus.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false})
- flog := logrus.WithFields(logrus.Fields{"prefix": "config"})
+// NewConfig instantiates a new configuration based on the specified configuration file path.
+func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config {
+ logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
+
viper.SetConfigFile(cfgfile)
- input, err := getFileContents(cfgfile)
+ input, err := ioutil.ReadFile(cfgfile)
if err != nil {
- logrus.Fatal(err)
+ logger.Fatalf("Failed to read configuration file: %#v", err)
}
- mycfg := newConfigFromString(input)
+
+ mycfg := newConfigFromString(logger, input)
if mycfg.cv.General.MediaDownloadSize == 0 {
mycfg.cv.General.MediaDownloadSize = 1000000
}
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
- flog.Println("Config file changed:", e.Name)
+ logger.Println("Config file changed:", e.Name)
})
return mycfg
}
-func getFileContents(filename string) ([]byte, error) {
- input, err := ioutil.ReadFile(filename)
- if err != nil {
- logrus.Fatal(err)
- return []byte(nil), err
- }
- return input, nil
-}
-
-func NewConfigFromString(input []byte) Config {
- return newConfigFromString(input)
+// NewConfigFromString instantiates a new configuration based on the specified string.
+func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config {
+ logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"})
+ return newConfigFromString(logger, input)
}
-func newConfigFromString(input []byte) *config {
+func newConfigFromString(logger *logrus.Entry, input []byte) *config {
viper.SetConfigType("toml")
viper.SetEnvPrefix("matterbridge")
- viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
viper.AutomaticEnv()
- err := viper.ReadConfig(bytes.NewBuffer(input))
- if err != nil {
- logrus.Fatal(err)
+
+ if err := viper.ReadConfig(bytes.NewBuffer(input)); err != nil {
+ logger.Fatalf("Failed to parse the configuration: %#v", err)
}
cfg := &BridgeValues{}
- err = viper.Unmarshal(cfg)
- if err != nil {
- logrus.Fatal(err)
+ if err := viper.Unmarshal(cfg); err != nil {
+ logger.Fatalf("Failed to load the configuration: %#v", err)
}
return &config{
- v: viper.GetViper(),
- cv: cfg,
+ logger: logger,
+ v: viper.GetViper(),
+ cv: cfg,
}
}
@@ -27146 +26544 @@ func (c *config) BridgeValues() *BridgeValues {
func (c *config) GetBool(key string) (bool, bool) {
c.RLock()
defer c.RUnlock()
- // log.Debugf("getting bool %s = %#v", key, c.v.GetBool(key))
return c.v.GetBool(key), c.v.IsSet(key)
}
func (c *config) GetInt(key string) (int, bool) {
c.RLock()
defer c.RUnlock()
- // log.Debugf("getting int %s = %d", key, c.v.GetInt(key))
return c.v.GetInt(key), c.v.IsSet(key)
}
func (c *config) GetString(key string) (string, bool) {
c.RLock()
defer c.RUnlock()
- // log.Debugf("getting String %s = %s", key, c.v.GetString(key))
return c.v.GetString(key), c.v.IsSet(key)
}
func (c *config) GetStringSlice(key string) ([]string, bool) {
c.RLock()
defer c.RUnlock()
- // log.Debugf("getting StringSlice %s = %#v", key, c.v.GetStringSlice(key))
return c.v.GetStringSlice(key), c.v.IsSet(key)
}
func (c *config) GetStringSlice2D(key string) ([][]string, bool) {
c.RLock()
defer c.RUnlock()
- result := [][]string{}
- if res, ok := c.v.Get(key).([]interface{}); ok {
- for _, entry := range res {
- result2 := []string{}
- for _, entry2 := range entry.([]interface{}) {
- result2 = append(result2, entry2.(string))
- }
- result = append(result, result2)
+
+ res, ok := c.v.Get(key).([]interface{})
+ if !ok {
+ return nil, false
+ }
+ var result [][]string
+ for _, entry := range res {
+ result2 := []string{}
+ for _, entry2 := range entry.([]interface{}) {
+ result2 = append(result2, entry2.(string))
}
- return result, true
+ result = append(result, result2)
}
- return result, false
+ return result, true
}
func GetIconURL(msg *Message, iconURL string) string {
diff --git a/bridge/helper/helper.go b/bridge/helper/helper.go
index 12a587f..3836556 100644
--- a/bridge/helper/helper.go
+++ b/bridge/helper/helper.go
@@ -1510 +1512 @@ import (
"gitlab.com/golang-commonmark/markdown"
)
+// DownloadFile downloads the given non-authenticated URL.
func DownloadFile(url string) (*[]byte, error) {
return DownloadFileAuth(url, "")
}
+// DownloadFileAuth downloads the given URL using the specified authentication token.
func DownloadFileAuth(url string, auth string) (*[]byte, error) {
var buf bytes.Buffer
client := &http.Client{
@@ -428 +448 @@ func DownloadFileAuth(url string, auth string) (*[]byte, error) {
}
// GetSubLines splits messages in newline-delimited lines. If maxLineLength is
-// specified as non-zero GetSubLines will and also clip long lines to the
-// maximum length and insert a warning marker that the line was clipped.
+// specified as non-zero GetSubLines will also clip long lines to the maximum
+// length and insert a warning marker that the line was clipped.
//
// TODO: The current implementation has the inconvenient that it disregards
// word boundaries when splitting but this is hard to solve without potentially
@@ -7918 +8124 @@ func GetSubLines(message string, maxLineLength int) []string {
return lines
}
-// handle all the stuff we put into extra
+// HandleExtra manages the supplementary details stored inside a message's 'Extra' field map.
func HandleExtra(msg *config.Message, general *config.Protocol) []config.Message {
extra := msg.Extra
rmsg := []config.Message{}
for _, f := range extra[config.EventFileFailureSize] {
fi := f.(config.FileInfo)
text := fmt.Sprintf("file %s too big to download (%#v > allowed size: %#v)", fi.Name, fi.Size, general.MediaDownloadSize)
- rmsg = append(rmsg, config.Message{Text: text, Username: "<system> ", Channel: msg.Channel, Account: msg.Account})
+ rmsg = append(rmsg, config.Message{
+ Text: text,
+ Username: "<system> ",
+ Channel: msg.Channel,
+ Account: msg.Account,
+ })
}
return rmsg
}
+// GetAvatar constructs a URL for a given user-avatar if it is available in the cache.
func GetAvatar(av map[string]string, userid string, general *config.Protocol) string {
if sha, ok := av[userid]; ok {
return general.MediaServerDownload + "/" + sha + "/" + userid + ".png"
@@ -9813 +10615 @@ func GetAvatar(av map[string]string, userid string, general *config.Protocol) st
return ""
}
-func HandleDownloadSize(flog *logrus.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
+// HandleDownloadSize checks a specified filename against the configured download blacklist
+// and checks a specified file-size against the configure limit.
+func HandleDownloadSize(logger *logrus.Entry, msg *config.Message, name string, size int64, general *config.Protocol) error {
// check blacklist here
for _, entry := range general.MediaDownloadBlackList {
if entry != "" {
re, err := regexp.Compile(entry)
if err != nil {
- flog.Errorf("incorrect regexp %s for %s", entry, msg.Account)
+ logger.Errorf("incorrect regexp %s for %s", entry, msg.Account)
continue
}
if re.MatchString(name) {
@@ -11243 +12253 @@ func HandleDownloadSize(flog *logrus.Entry, msg *config.Message, name string, si
}
}
}
- flog.Debugf("Trying to download %#v with size %#v", name, size)
+ logger.Debugf("Trying to download %#v with size %#v", name, size)
if int(size) > general.MediaDownloadSize {
msg.Event = config.EventFileFailureSize
- msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{Name: name, Comment: msg.Text, Size: size})
+ msg.Extra[msg.Event] = append(msg.Extra[msg.Event], config.FileInfo{
+ Name: name,
+ Comment: msg.Text,
+ Size: size,
+ })
return fmt.Errorf("File %#v to large to download (%#v). MediaDownloadSize is %#v", name, size, general.MediaDownloadSize)
}
return nil
}
-func HandleDownloadData(flog *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
+// HandleDownloadData adds the data for a remote file into a Matterbridge gateway message.
+func HandleDownloadData(logger *logrus.Entry, msg *config.Message, name, comment, url string, data *[]byte, general *config.Protocol) {
var avatar bool
- flog.Debugf("Download OK %#v %#v", name, len(*data))
+ logger.Debugf("Download OK %#v %#v", name, len(*data))
if msg.Event == config.EventAvatarDownload {
avatar = true
}
- msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data, URL: url, Comment: comment, Avatar: avatar})
+ msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{
+ Name: name,
+ Data: data,
+ URL: url,
+ Comment: comment,
+ Avatar: avatar,
+ })
}
+var emptyLineMatcher = regexp.MustCompile("\n+")
+
+// RemoveEmptyNewLines collapses consecutive newline characters into a single one and
+// trims any preceding or trailing newline characters as well.
func RemoveEmptyNewLines(msg string) string {
- lines := ""
- for _, line := range strings.Split(msg, "\n") {
- if line != "" {
- lines += line + "\n"
- }
- }
- lines = strings.TrimRight(lines, "\n")
- return lines
+ return emptyLineMatcher.ReplaceAllString(strings.Trim(msg, "\n"), "\n")
}
+// ClipMessage trims a message to the specified length if it exceeds it and adds a warning
+// to the message in case it does so.
func ClipMessage(text string, length int) string {
- // clip too long messages
+ const clippingMessage = " <clipped message>"
if len(text) > length {
- text = text[:length-len(" *message clipped*")]
+ text = text[:length-len(clippingMessage)]
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
text = text[:len(text)-size]
}
- text += " *message clipped*"
+ text += clippingMessage
}
return text
}
diff --git a/bridge/slack/helpers_test.go b/bridge/slack/helpers_test.go
index c9ff647..fe3ba41 100644
--- a/bridge/slack/helpers_test.go
+++ b/bridge/slack/helpers_test.go
@@ -257 +257 @@ func TestExtractTopicOrPurpose(t *testing.T) {
logger := logrus.New()
logger.SetOutput(ioutil.Discard)
- cfg := &bridge.Config{Log: logger.WithFields(nil)}
+ cfg := &bridge.Config{Bridge: &bridge.Bridge{Log: logrus.NewEntry(logger)}}
b := newBridge(cfg)
for name, tc := range testcases {
gotChangeType, gotOutput := b.extractTopicOrPurpose(tc.input)
diff --git a/bridge/sshchat/sshchat.go b/bridge/sshchat/sshchat.go
index 5a8029c..3a4512c 100644
--- a/bridge/sshchat/sshchat.go
+++ b/bridge/sshchat/sshchat.go
@@ -97 +96 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/shazow/ssh-chat/sshd"
- "github.com/sirupsen/logrus"
)
type Bsshchat struct {
@@ -1347 +1337 @@ func (b *Bsshchat) handleSSHChat() error {
res := strings.Split(stripPrompt(b.r.Text()), ":")
if res[0] == "-> Set theme" {
wait = false
- logrus.Debugf("mono found, allowing")
+ b.Log.Debugf("mono found, allowing")
continue
}
if !wait {
diff --git a/gateway/gateway.go b/gateway/gateway.go
index 72d7c72..cb7d94c 100644
--- a/gateway/gateway.go
+++ b/gateway/gateway.go
@@ -107 +107 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
"github.com/d5/tengo/script"
- "github.com/hashicorp/golang-lru"
+ lru "github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib"
"github.com/sirupsen/logrus"
)
@@ -266 +268 @@ type Gateway struct {
Message chan config.Message
Name string
Messages *lru.Cache
+
+ logger *logrus.Entry
}
type BrMsgID struct {
@@ -3425 +3630 @@ type BrMsgID struct {
ChannelID string
}
-var flog *logrus.Entry
+const apiProtocol = "api"
-const (
- apiProtocol = "api"
-)
+// New creates a new Gateway object associated with the specified router and
+// following the given configuration.
+func New(rootLogger *logrus.Logger, cfg *config.Gateway, r *Router) *Gateway {
+ logger := rootLogger.WithFields(logrus.Fields{"prefix": "gateway"})
-func New(cfg config.Gateway, r *Router) *Gateway {
- flog = logrus.WithFields(logrus.Fields{"prefix": "gateway"})
- gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message,
- Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config}
cache, _ := lru.New(5000)
- gw.Messages = cache
- if err := gw.AddConfig(&cfg); err != nil {
- flog.Errorf("AddConfig failed: %s", err)
+ gw := &Gateway{
+ Channels: make(map[string]*config.ChannelInfo),
+ Message: r.Message,
+ Router: r,
+ Bridges: make(map[string]*bridge.Bridge),
+ Config: r.Config,
+ Messages: cache,
+ logger: logger,
+ }
+ if err := gw.AddConfig(cfg); err != nil {
+ logger.Errorf("Failed to add configuration to gateway: %#v", err)
}
return gw
}
-// Find the canonical ID that the message is keyed under in cache
+// FindCanonicalMsgID returns the ID under which a message was stored in the cache.
func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
ID := protocol + " " + mID
if gw.Messages.Contains(ID) {
@@ -7215 +7918 @@ func (gw *Gateway) FindCanonicalMsgID(protocol string, mID string) string {
return ""
}
+// AddBridge sets up a new bridge in the gateway object with the specified configuration.
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
br := gw.Router.getBridge(cfg.Account)
if br == nil {
br = bridge.New(cfg)
br.Config = gw.Router.Config
br.General = &gw.BridgeValues().General
- // set logging
- br.Log = logrus.WithFields(logrus.Fields{"prefix": "bridge"})
- brconfig := &bridge.Config{Remote: gw.Message, Log: logrus.WithFields(logrus.Fields{"prefix": br.Protocol}), Bridge: br}
+ br.Log = gw.logger.WithFields(logrus.Fields{"prefix": br.Protocol})
+ brconfig := &bridge.Config{
+ Remote: gw.Message,
+ Bridge: br,
+ }
// add the actual bridger for this protocol to this bridge using the bridgeMap
br.Bridger = gw.Router.BridgeMap[br.Protocol](brconfig)
}
@@ -8911 +9912 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
return nil
}
+// AddConfig associates a new configuration with the gateway object.
func (gw *Gateway) AddConfig(cfg *config.Gateway) error {
gw.Name = cfg.Name
gw.MyConfig = cfg
if err := gw.mapChannels(); err != nil {
- flog.Errorf("mapChannels() failed: %s", err)
+ gw.logger.Errorf("mapChannels() failed: %s", err)
}
for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) {
br := br //scopelint
@@ -11520 +12620 @@ func (gw *Gateway) mapChannelsToBridge(br *bridge.Bridge) {
func (gw *Gateway) reconnectBridge(br *bridge.Bridge) {
if err := br.Disconnect(); err != nil {
- flog.Errorf("Disconnect() %s failed: %s", br.Account, err)
+ gw.logger.Errorf("Disconnect() %s failed: %s", br.Account, err)
}
time.Sleep(time.Second * 5)
RECONNECT:
- flog.Infof("Reconnecting %s", br.Account)
+ gw.logger.Infof("Reconnecting %s", br.Account)
err := br.Connect()
if err != nil {
- flog.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err)
+ gw.logger.Errorf("Reconnection failed: %s. Trying again in 60 seconds", err)
time.Sleep(time.Second * 60)
goto RECONNECT
}
br.Joined = make(map[string]bool)
if err := br.JoinChannels(); err != nil {
- flog.Errorf("JoinChannels() %s failed: %s", br.Account, err)
+ gw.logger.Errorf("JoinChannels() %s failed: %s", br.Account, err)
}
}
@@ -14213 +15319 @@ func (gw *Gateway) mapChannelConfig(cfg []config.Bridge, direction string) {
br.Channel = strings.ToLower(br.Channel)
}
if strings.HasPrefix(br.Account, "mattermost.") && strings.HasPrefix(br.Channel, "#") {
- flog.Errorf("Mattermost channels do not start with a #: remove the # in %s", br.Channel)
+ gw.logger.Errorf("Mattermost channels do not start with a #: remove the # in %s", br.Channel)
os.Exit(1)
}
ID := br.Channel + br.Account
if _, ok := gw.Channels[ID]; !ok {
- channel := &config.ChannelInfo{Name: br.Channel, Direction: direction, ID: ID, Options: br.Options, Account: br.Account,
- SameChannel: make(map[string]bool)}
+ channel := &config.ChannelInfo{
+ Name: br.Channel,
+ Direction: direction,
+ ID: ID,
+ Options: br.Options,
+ Account: br.Account,
+ SameChannel: make(map[string]bool),
+ }
channel.SameChannel[gw.Name] = br.SameChannel
gw.Channels[channel.ID] = channel
} else {
@@ -2077 +2247 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
// if source channel is in only, do nothing
for _, channel := range gw.Channels {
// lookup the channel from the message
- if channel.ID == getChannelID(*msg) {
+ if channel.ID == getChannelID(msg) {
// we only have destinations if the original message is from an "in" (sending) channel
if !strings.Contains(channel.Direction, "in") {
return channels
@@ -21611 +23311 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
}
}
for _, channel := range gw.Channels {
- if _, ok := gw.Channels[getChannelID(*msg)]; !ok {
+ if _, ok := gw.Channels[getChannelID(msg)]; !ok {
continue
}
- // do samechannelgateway flogic
+ // do samechannelgateway logic
if channel.SameChannel[msg.Gateway] {
if msg.Channel == channel.Name && msg.Account != dest.Account {
channels = append(channels, *channel)
@@ -2347 +2517 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
return channels
}
-func (gw *Gateway) getDestMsgID(msgID string, dest *bridge.Bridge, channel config.ChannelInfo) string {
+func (gw *Gateway) getDestMsgID(msgID string, dest *bridge.Bridge, channel *config.ChannelInfo) string {
if res, ok := gw.Messages.Get(msgID); ok {
IDs := res.([]*BrMsgID)
for _, id := range IDs {
@@ -2637 +2807 @@ func (gw *Gateway) ignoreTextEmpty(msg *config.Message) bool {
len(msg.Extra[config.EventFileFailureSize]) > 0) {
return false
}
- flog.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
+ gw.logger.Debugf("ignoring empty message %#v from %s", msg, msg.Account)
return true
}
@@ -2827 +2997 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
return false
}
-func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) string {
+func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) string {
br := gw.Bridges[msg.Account]
msg.Protocol = br.Protocol
if dest.GetBool("StripNick") {
@@ -2987 +3157 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
// TODO move compile to bridge init somewhere
re, err := regexp.Compile(search)
if err != nil {
- flog.Errorf("regexp in %s failed: %s", msg.Account, err)
+ gw.logger.Errorf("regexp in %s failed: %s", msg.Account, err)
break
}
msg.Username = re.ReplaceAllString(msg.Username, replace)
@@ -3267 +3437 @@ func (gw *Gateway) modifyUsername(msg config.Message, dest *bridge.Bridge) strin
return nick
}
-func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string {
+func (gw *Gateway) modifyAvatar(msg *config.Message, dest *bridge.Bridge) string {
iconurl := dest.GetString("IconURL")
iconurl = strings.Replace(iconurl, "{NICK}", msg.Username, -1)
if msg.Avatar == "" {
@@ -3377 +3547 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string
func (gw *Gateway) modifyMessage(msg *config.Message) {
if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
- flog.Errorf("TengoModifyMessage failed: %s", err)
+ gw.logger.Errorf("TengoModifyMessage failed: %s", err)
}
// replace :emoji: to unicode
@@ -3517 +3687 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
// TODO move compile to bridge init somewhere
re, err := regexp.Compile(search)
if err != nil {
- flog.Errorf("regexp in %s failed: %s", msg.Account, err)
+ gw.logger.Errorf("regexp in %s failed: %s", msg.Account, err)
break
}
msg.Text = re.ReplaceAllString(msg.Text, replace)
@@ -36546 +38251 @@ func (gw *Gateway) modifyMessage(msg *config.Message) {
}
}
-// SendMessage sends a message (with specified parentID) to the channel on the selected destination bridge.
-// returns a message id and error.
-func (gw *Gateway) SendMessage(origmsg config.Message, dest *bridge.Bridge, channel config.ChannelInfo, canonicalParentMsgID string) (string, error) {
- msg := origmsg
+// SendMessage sends a message (with specified parentID) to the channel on the selected
+// destination bridge and returns a message ID or an error.
+func (gw *Gateway) SendMessage(
+ rmsg *config.Message,
+ dest *bridge.Bridge,
+ channel *config.ChannelInfo,
+ canonicalParentMsgID string,
+) (string, error) {
+ msg := *rmsg
// Only send the avatar download event to ourselves.
if msg.Event == config.EventAvatarDownload {
- if channel.ID != getChannelID(origmsg) {
+ if channel.ID != getChannelID(rmsg) {
return "", nil
}
} else {
// do not send to ourself for any other event
- if channel.ID == getChannelID(origmsg) {
+ if channel.ID == getChannelID(rmsg) {
return "", nil
}
}
// Too noisy to log like other events
if msg.Event != config.EventUserTyping {
- flog.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, origmsg.Channel, dest.Account, channel.Name)
+ gw.logger.Debugf("=> Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, rmsg.Channel, dest.Account, channel.Name)
}
msg.Channel = channel.Name
- msg.Avatar = gw.modifyAvatar(origmsg, dest)
- msg.Username = gw.modifyUsername(origmsg, dest)
+ msg.Avatar = gw.modifyAvatar(rmsg, dest)
+ msg.Username = gw.modifyUsername(rmsg, dest)
- msg.ID = gw.getDestMsgID(origmsg.Protocol+" "+origmsg.ID, dest, channel)
+ msg.ID = gw.getDestMsgID(rmsg.Protocol+" "+rmsg.ID, dest, channel)
// for api we need originchannel as channel
if dest.Protocol == apiProtocol {
- msg.Channel = origmsg.Channel
+ msg.Channel = rmsg.Channel
}
- msg.ParentID = gw.getDestMsgID(origmsg.Protocol+" "+canonicalParentMsgID, dest, channel)
+ msg.ParentID = gw.getDestMsgID(rmsg.Protocol+" "+canonicalParentMsgID, dest, channel)
if msg.ParentID == "" {
msg.ParentID = canonicalParentMsgID
}
// if the parentID is still empty and we have a parentID set in the original message
// this means that we didn't find it in the cache so set it "msg-parent-not-found"
- if msg.ParentID == "" && origmsg.ParentID != "" {
+ if msg.ParentID == "" && rmsg.ParentID != "" {
msg.ParentID = "msg-parent-not-found"
}
@@ -4217 +4437 @@ func (gw *Gateway) SendMessage(origmsg config.Message, dest *bridge.Bridge, chan
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
if mID != "" {
- flog.Debugf("mID %s: %s", dest.Account, mID)
+ gw.logger.Debugf("mID %s: %s", dest.Account, mID)
return mID, nil
//brMsgIDs = append(brMsgIDs, &BrMsgID{dest, dest.Protocol + " " + mID, channel.ID})
}
@@ -4327 +4547 @@ func (gw *Gateway) validGatewayDest(msg *config.Message) bool {
return msg.Gateway == gw.Name
}
-func getChannelID(msg config.Message) string {
+func getChannelID(msg *config.Message) string {
return msg.Channel + msg.Account
}
@@ -44911 +47111 @@ func (gw *Gateway) ignoreText(text string, input []string) bool {
// TODO do not compile regexps everytime
re, err := regexp.Compile(entry)
if err != nil {
- flog.Errorf("incorrect regexp %s", entry)
+ gw.logger.Errorf("incorrect regexp %s", entry)
continue
}
if re.MatchString(text) {
- flog.Debugf("matching %s. ignoring %s", entry, text)
+ gw.logger.Debugf("matching %s. ignoring %s", entry, text)
return true
}
}
diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go
index 677afde..b9bb5b9 100644
--- a/gateway/gateway_test.go
+++ b/gateway/gateway_test.go
@@ -212 +215 @@ package gateway
import (
"fmt"
+ "io/ioutil"
"strconv"
"testing"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/gateway/bridgemap"
+ "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
)
var testconfig = []byte(`
@@ -1598 +16210 @@ const (
)
func maketestRouter(input []byte) *Router {
- cfg := config.NewConfigFromString(input)
- r, err := NewRouter(cfg, bridgemap.FullMap)
+ logger := logrus.New()
+ logger.SetOutput(ioutil.Discard)
+ cfg := config.NewConfigFromString(logger, input)
+ r, err := NewRouter(logger, cfg, bridgemap.FullMap)
if err != nil {
fmt.Println(err)
}
@@ -3877 +39223 @@ func TestGetDestChannelAdvanced(t *testing.T) {
assert.Equal(t, map[string]int{"bridge3": 4, "bridge": 9, "announcements": 3, "bridge2": 4}, hits)
}
-func TestIgnoreTextEmpty(t *testing.T) {
+type ignoreTestSuite struct {
+ suite.Suite
+
+ gw *Gateway
+}
+
+func TestIgnoreSuite(t *testing.T) {
+ s := &ignoreTestSuite{}
+ suite.Run(t, s)
+}
+
+func (s *ignoreTestSuite) SetupSuite() {
+ logger := logrus.New()
+ logger.SetOutput(ioutil.Discard)
+ s.gw = &Gateway{logger: logrus.NewEntry(logger)}
+}
+func (s *ignoreTestSuite) TestIgnoreTextEmpty() {
extraFile := make(map[string][]interface{})
extraAttach := make(map[string][]interface{})
extraFailure := make(map[string][]interface{})
@@ -42415 +44514 @@ func TestIgnoreTextEmpty(t *testing.T) {
output: true,
},
}
- gw := &Gateway{}
for testname, testcase := range msgTests {
- output := gw.ignoreTextEmpty(testcase.input)
- assert.Equalf(t, testcase.output, output, "case '%s' failed", testname)
+ output := s.gw.ignoreTextEmpty(testcase.input)
+ s.Assert().Equalf(testcase.output, output, "case '%s' failed", testname)
}
}
-func TestIgnoreTexts(t *testing.T) {
+func (s *ignoreTestSuite) TestIgnoreTexts() {
msgTests := map[string]struct {
input string
re []string
@@ -45914 +47913 @@ func TestIgnoreTexts(t *testing.T) {
output: true,
},
}
- gw := &Gateway{}
for testname, testcase := range msgTests {
- output := gw.ignoreText(testcase.input, testcase.re)
- assert.Equalf(t, testcase.output, output, "case '%s' failed", testname)
+ output := s.gw.ignoreText(testcase.input, testcase.re)
+ s.Assert().Equalf(testcase.output, output, "case '%s' failed", testname)
}
}
-func TestIgnoreNicks(t *testing.T) {
+func (s *ignoreTestSuite) TestIgnoreNicks() {
msgTests := map[string]struct {
input string
re []string
@@ -49310 +5129 @@ func TestIgnoreNicks(t *testing.T) {
output: false,
},
}
- gw := &Gateway{}
for testname, testcase := range msgTests {
- output := gw.ignoreText(testcase.input, testcase.re)
- assert.Equalf(t, testcase.output, output, "case '%s' failed", testname)
+ output := s.gw.ignoreText(testcase.input, testcase.re)
+ s.Assert().Equalf(testcase.output, output, "case '%s' failed", testname)
}
}
diff --git a/gateway/handlers.go b/gateway/handlers.go
index dfec2ab..74bf433 100644
--- a/gateway/handlers.go
+++ b/gateway/handlers.go
@@ -407 +407 @@ func (r *Router) handleEventGetChannelMembers(msg *config.Message) {
for _, br := range gw.Bridges {
if msg.Account == br.Account {
cMembers := msg.Extra[config.EventGetChannelMembers][0].(config.ChannelMembers)
- flog.Debugf("Syncing channelmembers from %s", msg.Account)
+ r.logger.Debugf("Syncing channelmembers from %s", msg.Account)
br.SetChannelMembers(&cMembers)
return
}
@@ -587 +587 @@ func (r *Router) handleEventRejoinChannels(msg *config.Message) {
if msg.Account == br.Account {
br.Joined = make(map[string]bool)
if err := br.JoinChannels(); err != nil {
- flog.Errorf("channel join failed for %s: %s", msg.Account, err)
+ r.logger.Errorf("channel join failed for %s: %s", msg.Account, err)
}
}
}
@@ -9413 +9413 @@ func (gw *Gateway) handleFiles(msg *config.Message) {
if gw.BridgeValues().General.MediaServerUpload != "" {
// Use MediaServerUpload. Upload using a PUT HTTP request and basicauth.
if err := gw.handleFilesUpload(&fi); err != nil {
- flog.Error(err)
+ gw.logger.Error(err)
continue
}
} else {
// Use MediaServerPath. Place the file on the current filesystem.
if err := gw.handleFilesLocal(&fi); err != nil {
- flog.Error(err)
+ gw.logger.Error(err)
continue
}
}
@@ -1087 +1087 @@ func (gw *Gateway) handleFiles(msg *config.Message) {
// Download URL.
durl := gw.BridgeValues().General.MediaServerDownload + "/" + sha1sum + "/" + fi.Name
- flog.Debugf("mediaserver download URL = %s", durl)
+ gw.logger.Debugf("mediaserver download URL = %s", durl)
// We uploaded/placed the file successfully. Add the SHA and URL.
extra := msg.Extra["file"][i].(config.FileInfo)
@@ -1337 +1337 @@ func (gw *Gateway) handleFilesUpload(fi *config.FileInfo) error {
return fmt.Errorf("mediaserver upload failed, could not create request: %#v", err)
}
- flog.Debugf("mediaserver upload url: %s", url)
+ gw.logger.Debugf("mediaserver upload url: %s", url)
req.Header.Set("Content-Type", "binary/octet-stream")
_, err = client.Do(req)
@@ -1547 +1547 @@ func (gw *Gateway) handleFilesLocal(fi *config.FileInfo) error {
}
path := dir + "/" + fi.Name
- flog.Debugf("mediaserver path placing file: %s", path)
+ gw.logger.Debugf("mediaserver path placing file: %s", path)
err = ioutil.WriteFile(path, *fi.Data, os.ModePerm)
if err != nil {
@@ -18736 +18736 @@ func (gw *Gateway) ignoreEvent(event string, dest *bridge.Bridge) bool {
// handleMessage makes sure the message get sent to the correct bridge/channels.
// Returns an array of msg ID's
-func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
+func (gw *Gateway) handleMessage(rmsg *config.Message, dest *bridge.Bridge) []*BrMsgID {
var brMsgIDs []*BrMsgID
// if we have an attached file, or other info
- if msg.Extra != nil && len(msg.Extra[config.EventFileFailureSize]) != 0 && msg.Text == "" {
+ if rmsg.Extra != nil && len(rmsg.Extra[config.EventFileFailureSize]) != 0 && rmsg.Text == "" {
return brMsgIDs
}
- if gw.ignoreEvent(msg.Event, dest) {
+ if gw.ignoreEvent(rmsg.Event, dest) {
return brMsgIDs
}
// broadcast to every out channel (irc QUIT)
- if msg.Channel == "" && msg.Event != config.EventJoinLeave {
- flog.Debug("empty channel")
+ if rmsg.Channel == "" && rmsg.Event != config.EventJoinLeave {
+ gw.logger.Debug("empty channel")
return brMsgIDs
}
// Get the ID of the parent message in thread
var canonicalParentMsgID string
- if msg.ParentID != "" && dest.GetBool("PreserveThreading") {
- canonicalParentMsgID = gw.FindCanonicalMsgID(msg.Protocol, msg.ParentID)
+ if rmsg.ParentID != "" && dest.GetBool("PreserveThreading") {
+ canonicalParentMsgID = gw.FindCanonicalMsgID(rmsg.Protocol, rmsg.ParentID)
}
- origmsg := msg
- channels := gw.getDestChannel(&msg, *dest)
- for _, channel := range channels {
- msgID, err := gw.SendMessage(origmsg, dest, channel, canonicalParentMsgID)
+ channels := gw.getDestChannel(rmsg, *dest)
+ for idx := range channels {
+ channel := &channels[idx]
+ msgID, err := gw.SendMessage(rmsg, dest, channel, canonicalParentMsgID)
if err != nil {
- flog.Errorf("SendMessage failed: %s", err)
+ gw.logger.Errorf("SendMessage failed: %s", err)
continue
}
if msgID == "" {
@@ -2357 +2357 @@ func (gw *Gateway) handleExtractNicks(msg *config.Message) {
replace := outer[1]
msg.Username, msg.Text, err = extractNick(search, replace, msg.Username, msg.Text)
if err != nil {
- flog.Errorf("regexp in %s failed: %s", msg.Account, err)
+ gw.logger.Errorf("regexp in %s failed: %s", msg.Account, err)
break
}
}
diff --git a/gateway/router.go b/gateway/router.go
index 425960c..7d16b07 100644
--- a/gateway/router.go
+++ b/gateway/router.go
@@ -731 +740 @@ import (
"github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
- samechannelgateway "github.com/42wim/matterbridge/gateway/samechannel"
+ "github.com/42wim/matterbridge/gateway/samechannel"
+ "github.com/sirupsen/logrus"
)
type Router struct {
config.Config
+ sync.RWMutex
BridgeMap map[string]bridge.Factory
Gateways map[string]*Gateway
Message chan config.Message
MattermostPlugin chan config.Message
- sync.RWMutex
+
+ logger *logrus.Entry
}
-func NewRouter(cfg config.Config, bridgeMap map[string]bridge.Factory) (*Router, error) {
+// NewRouter initializes a new Matterbridge router for the specified configuration and
+// sets up all required gateways.
+func NewRouter(rootLogger *logrus.Logger, cfg config.Config, bridgeMap map[string]bridge.Factory) (*Router, error) {
+ logger := rootLogger.WithFields(logrus.Fields{"prefix": "router"})
+
r := &Router{
Config: cfg,
BridgeMap: bridgeMap,
Message: make(chan config.Message),
MattermostPlugin: make(chan config.Message),
Gateways: make(map[string]*Gateway),
+ logger: logger,
}
- sgw := samechannelgateway.New(cfg)
- gwconfigs := sgw.GetConfig()
+ sgw := samechannel.New(cfg)
+ gwconfigs := append(sgw.GetConfig(), cfg.BridgeValues().Gateway...)
- for _, entry := range append(gwconfigs, cfg.BridgeValues().Gateway...) {
+ for idx := range gwconfigs {
+ entry := &gwconfigs[idx]
if !entry.Enable {
continue
}
@@ -4121 +5023 @@ func NewRouter(cfg config.Config, bridgeMap map[string]bridge.Factory) (*Router,
if _, ok := r.Gateways[entry.Name]; ok {
return nil, fmt.Errorf("Gateway with name %s already exists", entry.Name)
}
- r.Gateways[entry.Name] = New(entry, r)
+ r.Gateways[entry.Name] = New(rootLogger, entry, r)
}
return r, nil
}
+// Start will connect all gateways belonging to this router and subsequently route messages
+// between them.
func (r *Router) Start() error {
m := make(map[string]*bridge.Bridge)
for _, gw := range r.Gateways {
- flog.Infof("Parsing gateway %s", gw.Name)
+ r.logger.Infof("Parsing gateway %s", gw.Name)
for _, br := range gw.Bridges {
m[br.Account] = br
}
}
for _, br := range m {
- flog.Infof("Starting bridge: %s ", br.Account)
+ r.logger.Infof("Starting bridge: %s ", br.Account)
err := br.Connect()
if err != nil {
e := fmt.Errorf("Bridge %s failed to start: %v", br.Account, err)
@@ -777 +887 @@ func (r *Router) Start() error {
for _, gw := range r.Gateways {
for i, br := range gw.Bridges {
if br.Bridger == nil {
- flog.Errorf("removing failed bridge %s", i)
+ r.logger.Errorf("removing failed bridge %s", i)
delete(gw.Bridges, i)
}
}
@@ -917 +1027 @@ func (r *Router) Start() error {
// otherwise returns false
func (r *Router) disableBridge(br *bridge.Bridge, err error) bool {
if r.BridgeValues().General.IgnoreFailureOnStart {
- flog.Error(err)
+ r.logger.Error(err)
// setting this bridge empty
*br = bridge.Bridge{}
return true
@@ -1247 +1357 @@ func (r *Router) handleReceive() {
gw.modifyMessage(&msg)
gw.handleFiles(&msg)
for _, br := range gw.Bridges {
- msgIDs = append(msgIDs, gw.handleMessage(msg, br)...)
+ msgIDs = append(msgIDs, gw.handleMessage(&msg, br)...)
}
// only add the message ID if it doesn't already exists
if _, ok := gw.Messages.Get(msg.Protocol + " " + msg.ID); !ok && msg.ID != "" {
@@ -1469 +1579 @@ func (r *Router) updateChannelMembers() {
if br.Protocol != "slack" {
continue
}
- flog.Debugf("sending %s to %s", config.EventGetChannelMembers, br.Account)
+ r.logger.Debugf("sending %s to %s", config.EventGetChannelMembers, br.Account)
if _, err := br.Send(config.Message{Event: config.EventGetChannelMembers}); err != nil {
- flog.Errorf("updateChannelMembers: %s", err)
+ r.logger.Errorf("updateChannelMembers: %s", err)
}
}
}
diff --git a/gateway/samechannel/samechannel.go b/gateway/samechannel/samechannel.go
index 1d85ea7..4b6016c 100644
--- a/gateway/samechannel/samechannel.go
+++ b/gateway/samechannel/samechannel.go
@@ -14 +14 @@
-package samechannelgateway
+package samechannel
import (
"github.com/42wim/matterbridge/bridge/config"
diff --git a/gateway/samechannel/samechannel_test.go b/gateway/samechannel/samechannel_test.go
index bbfb057..17d816a 100644
--- a/gateway/samechannel/samechannel_test.go
+++ b/gateway/samechannel/samechannel_test.go
@@ -19 +111 @@
-package samechannelgateway
+package samechannel
import (
+ "io/ioutil"
"testing"
"github.com/42wim/matterbridge/bridge/config"
+ "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
@@ -667 +689 @@ var (
)
func TestGetConfig(t *testing.T) {
- cfg := config.NewConfigFromString([]byte(testConfig))
+ logger := logrus.New()
+ logger.SetOutput(ioutil.Discard)
+ cfg := config.NewConfigFromString(logger, []byte(testConfig))
sgw := New(cfg)
configs := sgw.GetConfig()
assert.Equal(t, []config.Gateway{expectedConfig}, configs)
diff --git a/matterbridge.go b/matterbridge.go
index 0dac8af..6c6b11f 100644
--- a/matterbridge.go
+++ b/matterbridge.go
@@ -1746 +1769 @@ import (
var (
version = "1.14.0-dev"
githash string
+
+ flagConfig = flag.String("conf", "matterbridge.toml", "config file")
+ flagDebug = flag.Bool("debug", false, "enable debug")
+ flagVersion = flag.Bool("version", false, "show version")
+ flagGops = flag.Bool("gops", false, "enable gops agent")
)
func main() {
- logrus.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: true})
- flog := logrus.WithFields(logrus.Fields{"prefix": "main"})
- flagConfig := flag.String("conf", "matterbridge.toml", "config file")
- flagDebug := flag.Bool("debug", false, "enable debug")
- flagVersion := flag.Bool("version", false, "show version")
- flagGops := flag.Bool("gops", false, "enable gops agent")
flag.Parse()
+ if *flagVersion {
+ fmt.Printf("version: %s %s\n", version, githash)
+ return
+ }
+
+ rootLogger := setupLogger()
+ logger := rootLogger.WithFields(logrus.Fields{"prefix": "main"})
+
if *flagGops {
if err := agent.Listen(agent.Options{}); err != nil {
- flog.Errorf("failed to start gops agent: %#v", err)
+ logger.Errorf("Failed to start gops agent: %#v", err)
} else {
defer agent.Close()
}
}
- if *flagVersion {
- fmt.Printf("version: %s %s\n", version, githash)
- return
- }
- if *flagDebug || os.Getenv("DEBUG") == "1" {
- logrus.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
- flog.Info("Enabling debug")
- logrus.SetLevel(logrus.DebugLevel)
- }
- flog.Printf("Running version %s %s", version, githash)
+
+ logger.Printf("Running version %s %s", version, githash)
if strings.Contains(version, "-dev") {
- flog.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
+ logger.Println("WARNING: THIS IS A DEVELOPMENT VERSION. Things may break.")
}
- cfg := config.NewConfig(*flagConfig)
+
+ cfg := config.NewConfig(rootLogger, *flagConfig)
cfg.BridgeValues().General.Debug = *flagDebug
- r, err := gateway.NewRouter(cfg, bridgemap.FullMap)
+
+ r, err := gateway.NewRouter(rootLogger, cfg, bridgemap.FullMap)
if err != nil {
- flog.Fatalf("Starting gateway failed: %s", err)
+ logger.Fatalf("Starting gateway failed: %s", err)
}
- err = r.Start()
- if err != nil {
- flog.Fatalf("Starting gateway failed: %s", err)
+ if err = r.Start(); err != nil {
+ logger.Fatalf("Starting gateway failed: %s", err)
}
- flog.Printf("Gateway(s) started succesfully. Now relaying messages")
+ logger.Printf("Gateway(s) started succesfully. Now relaying messages")
select {}
}
+
+func setupLogger() *logrus.Logger {
+ logger := &logrus.Logger{
+ Out: os.Stdout,
+ Formatter: &prefixed.TextFormatter{
+ PrefixPadding: 13,
+ DisableColors: true,
+ FullTimestamp: true,
+ },
+ Level: logrus.InfoLevel,
+ }
+ if *flagDebug || os.Getenv("DEBUG") == "1" {
+ logger.Formatter = &prefixed.TextFormatter{
+ PrefixPadding: 13,
+ DisableColors: true,
+ FullTimestamp: false,
+ ForceFormatting: true,
+ }
+ logger.Level = logrus.DebugLevel
+ logger.WithFields(logrus.Fields{"prefix": "main"}).Info("Enabling debug logging.")
+ }
+ return logger
+}
diff --git a/matterclient/channels.go b/matterclient/channels.go
index ddd4d00..655efe9 100644
--- a/matterclient/channels.go
+++ b/matterclient/channels.go
@@ -57 +56 @@ import (
"strings"
"github.com/mattermost/mattermost-server/model"
- "github.com/sirupsen/logrus"
)
// GetChannels returns all channels we're members off
@@ -15511 +15411 @@ func (m *MMClient) JoinChannel(channelId string) error { //nolint:golint
defer m.RUnlock()
for _, c := range m.Team.Channels {
if c.Id == channelId {
- m.log.Debug("Not joining ", channelId, " already joined.")
+ m.logger.Debug("Not joining ", channelId, " already joined.")
return nil
}
}
- m.log.Debug("Joining ", channelId)
+ m.logger.Debug("Joining ", channelId)
_, resp := m.Client.AddChannelMember(channelId, m.User.Id)
if resp.Error != nil {
return resp.Error
@@ -18919 +18819 @@ func (m *MMClient) UpdateChannels() error {
func (m *MMClient) UpdateChannelHeader(channelId string, header string) { //nolint:golint
channel := &model.Channel{Id: channelId, Header: header}
- m.log.Debugf("updating channelheader %#v, %#v", channelId, header)
+ m.logger.Debugf("updating channelheader %#v, %#v", channelId, header)
_, resp := m.Client.UpdateChannel(channel)
if resp.Error != nil {
- logrus.Error(resp.Error)
+ m.logger.Error(resp.Error)
}
}
func (m *MMClient) UpdateLastViewed(channelId string) error { //nolint:golint
- m.log.Debugf("posting lastview %#v", channelId)
+ m.logger.Debugf("posting lastview %#v", channelId)
view := &model.ChannelView{ChannelId: channelId}
_, resp := m.Client.ViewChannel(m.User.Id, view)
if resp.Error != nil {
- m.log.Errorf("ChannelView update for %s failed: %s", channelId, resp.Error)
+ m.logger.Errorf("ChannelView update for %s failed: %s", channelId, resp.Error)
return resp.Error
}
return nil
diff --git a/matterclient/helpers.go b/matterclient/helpers.go
index 625fffa..b3d4346 100644
--- a/matterclient/helpers.go
+++ b/matterclient/helpers.go
@@ -227 +227 @@ func (m *MMClient) doLogin(firstConnection bool, b *backoff.Backoff) error {
var logmsg = "trying login"
var err error
for {
- m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
+ m.logger.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
if m.Credentials.Token != "" {
resp, err = m.doLoginToken()
if err != nil {
@@ -3414 +3414 @@ func (m *MMClient) doLogin(firstConnection bool, b *backoff.Backoff) error {
appErr = resp.Error
if appErr != nil {
d := b.Duration()
- m.log.Debug(appErr.DetailedError)
+ m.logger.Debug(appErr.DetailedError)
if firstConnection {
if appErr.Message == "" {
return errors.New(appErr.DetailedError)
}
return errors.New(appErr.Message)
}
- m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d)
+ m.logger.Debugf("LOGIN: %s, reconnecting in %s", appErr, d)
time.Sleep(d)
logmsg = "retrying login"
continue
@@ -5917 +5917 @@ func (m *MMClient) doLoginToken() (*model.Response, error) {
m.Client.AuthType = model.HEADER_BEARER
m.Client.AuthToken = m.Credentials.Token
if m.Credentials.CookieToken {
- m.log.Debugf(logmsg + " with cookie (MMAUTH) token")
+ m.logger.Debugf(logmsg + " with cookie (MMAUTH) token")
m.Client.HttpClient.Jar = m.createCookieJar(m.Credentials.Token)
} else {
- m.log.Debugf(logmsg + " with personal token")
+ m.logger.Debugf(logmsg + " with personal token")
}
m.User, resp = m.Client.GetMe("")
if resp.Error != nil {
return resp, resp.Error
}
if m.User == nil {
- m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
+ m.logger.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
return resp, errors.New("invalid token")
}
return resp, nil
@@ -1267 +1267 @@ func (m *MMClient) initUser() error {
defer m.Unlock()
// we only load all team data on initial login.
// all other updates are for channels from our (primary) team only.
- //m.log.Debug("initUser(): loading all team data")
+ //m.logger.Debug("initUser(): loading all team data")
teams, resp := m.Client.GetTeamsForUser(m.User.Id, "")
if resp.Error != nil {
return resp.Error
@@ -1567 +1567 @@ func (m *MMClient) initUser() error {
m.OtherTeams = append(m.OtherTeams, t)
if team.Name == m.Credentials.Team {
m.Team = t
- m.log.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
+ m.logger.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
}
// add all users
for k, v := range t.Users {
@@ -18010 +18010 @@ func (m *MMClient) serverAlive(firstConnection bool, b *backoff.Backoff) error {
}
m.ServerVersion = resp.ServerVersion
if m.ServerVersion == "" {
- m.log.Debugf("Server not up yet, reconnecting in %s", d)
+ m.logger.Debugf("Server not up yet, reconnecting in %s", d)
time.Sleep(d)
} else {
- m.log.Infof("Found version %s", m.ServerVersion)
+ m.logger.Infof("Found version %s", m.ServerVersion)
return nil
}
}
@@ -2077 +2077 @@ func (m *MMClient) wsConnect() {
header := http.Header{}
header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken)
- m.log.Debugf("WsClient: making connection: %s", wsurl)
+ m.logger.Debugf("WsClient: making connection: %s", wsurl)
for {
wsDialer := &websocket.Dialer{
TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}, //nolint:gosec
@@ -21714 +21714 @@ func (m *MMClient) wsConnect() {
m.WsClient, _, err = wsDialer.Dial(wsurl, header)
if err != nil {
d := b.Duration()
- m.log.Debugf("WSS: %s, reconnecting in %s", err, d)
+ m.logger.Debugf("WSS: %s, reconnecting in %s", err, d)
time.Sleep(d)
continue
}
break
}
- m.log.Debug("WsClient: connected")
+ m.logger.Debug("WsClient: connected")
m.WsSequence = 1
m.WsPingChan = make(chan *model.WebSocketResponse)
// only start to parse WS messages when login is completely done
@@ -2527 +2527 @@ func (m *MMClient) checkAlive() error {
if resp.Error != nil {
return resp.Error
}
- m.log.Debug("WS PING")
+ m.logger.Debug("WS PING")
return m.sendWSRequest("ping", nil)
}
@@ -2627 +2627 @@ func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) err
req.Action = action
req.Data = data
m.WsSequence++
- m.log.Debugf("sendWsRequest %#v", req)
+ m.logger.Debugf("sendWsRequest %#v", req)
return m.WsClient.WriteJSON(req)
}
diff --git a/matterclient/matterclient.go b/matterclient/matterclient.go
index 07994c6..18006c7 100644
--- a/matterclient/matterclient.go
+++ b/matterclient/matterclient.go
@@ -87 +87 @@ import (
"time"
"github.com/gorilla/websocket"
- "github.com/hashicorp/golang-lru"
+ lru "github.com/hashicorp/golang-lru"
"github.com/jpillora/backoff"
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
"github.com/mattermost/mattermost-server/model"
@@ -4913 +4913 @@ type Team struct {
type MMClient struct {
sync.RWMutex
*Credentials
+
Team *Team
OtherTeams []*Team
Client *model.Client4
User *model.User
Users map[string]*model.User
MessageChan chan *Message
- log *logrus.Entry
WsClient *websocket.Conn
WsQuit bool
WsAway bool
@@ -6431 +6461 @@ type MMClient struct {
WsPingChan chan *model.WebSocketResponse
ServerVersion string
OnWsConnect func()
- lruCache *lru.Cache
+
+ logger *logrus.Entry
+ rootLogger *logrus.Logger
+ lruCache *lru.Cache
}
-func New(login, pass, team, server string) *MMClient {
- cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server}
- mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)}
- logrus.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true})
- mmclient.log = logrus.WithFields(logrus.Fields{"prefix": "matterclient"})
- mmclient.lruCache, _ = lru.New(500)
- return mmclient
+// New will instantiate a new Matterclient with the specified login details without connecting.
+func New(login string, pass string, team string, server string) *MMClient {
+ rootLogger := logrus.New()
+ rootLogger.SetFormatter(&prefixed.TextFormatter{
+ PrefixPadding: 13,
+ DisableColors: true,
+ })
+
+ cred := &Credentials{
+ Login: login,
+ Pass: pass,
+ Team: team,
+ Server: server,
+ }
+
+ cache, _ := lru.New(500)
+ return &MMClient{
+ Credentials: cred,
+ MessageChan: make(chan *Message, 100),
+ Users: make(map[string]*model.User),
+ rootLogger: rootLogger,
+ lruCache: cache,
+ logger: rootLogger.WithFields(logrus.Fields{"prefix": "matterclient"}),
+ }
}
+// SetDebugLog activates debugging logging on all Matterclient log output.
func (m *MMClient) SetDebugLog() {
- logrus.SetFormatter(&prefixed.TextFormatter{PrefixPadding: 13, DisableColors: true, FullTimestamp: false, ForceFormatting: true})
+ m.rootLogger.SetFormatter(&prefixed.TextFormatter{
+ PrefixPadding: 13,
+ DisableColors: true,
+ FullTimestamp: false,
+ ForceFormatting: true,
+ })
}
+// SetLogLevel tries to parse the specified level and if successful sets
+// the log level accordingly. Accepted levels are: 'debug', 'info', 'warn',
+// 'error', 'fatal' and 'panic'.
func (m *MMClient) SetLogLevel(level string) {
l, err := logrus.ParseLevel(level)
if err != nil {
- logrus.SetLevel(logrus.InfoLevel)
- return
+ m.logger.Warnf("Failed to parse specified log-level '%s': %#v", level, err)
+ } else {
+ m.rootLogger.SetLevel(l)
}
- logrus.SetLevel(l)
}
+// Login tries to connect the client with the loging details with which it was initialized.
func (m *MMClient) Login() error {
// check if this is a first connect or a reconnection
firstConnection := true
@@ -13113 +16114 @@ func (m *MMClient) Login() error {
return nil
}
+// Logout disconnects the client from the chat server.
func (m *MMClient) Logout() error {
- m.log.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server)
+ m.logger.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server)
m.WsQuit = true
m.WsClient.Close()
m.WsClient.UnderlyingConn().Close()
if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) {
- m.log.Debug("Not invalidating session in logout, credential is a token")
+ m.logger.Debug("Not invalidating session in logout, credential is a token")
return nil
}
_, resp := m.Client.Logout()
@@ -14713 +17816 @@ func (m *MMClient) Logout() error {
return nil
}
+// WsReceiver implements the core loop that manages the connection to the chat server. In
+// case of a disconnect it will try to reconnect. A call to this method is blocking until
+// the 'WsQuite' field of the MMClient object is set to 'true'.
func (m *MMClient) WsReceiver() {
for {
var rawMsg json.RawMessage
var err error
if m.WsQuit {
- m.log.Debug("exiting WsReceiver")
+ m.logger.Debug("exiting WsReceiver")
return
}
@@ -16314 +19714 @@ func (m *MMClient) WsReceiver() {
}
if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil {
- m.log.Error("error:", err)
+ m.logger.Error("error:", err)
// reconnect
m.wsConnect()
}
var event model.WebSocketEvent
if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() {
- m.log.Debugf("WsReceiver event: %#v", event)
+ m.logger.Debugf("WsReceiver event: %#v", event)
msg := &Message{Raw: &event, Team: m.Credentials.Team}
m.parseMessage(msg)
// check if we didn't empty the message
@@ -18940 +22342 @@ func (m *MMClient) WsReceiver() {
var response model.WebSocketResponse
if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() {
- m.log.Debugf("WsReceiver response: %#v", response)
+ m.logger.Debugf("WsReceiver response: %#v", response)
m.parseResponse(response)
- continue
}
}
}
+// StatusLoop implements a ping-cycle that ensures that the connection to the chat servers
+// remains alive. In case of a disconnect it will try to reconnect. A call to this method
+// is blocking until the 'WsQuite' field of the MMClient object is set to 'true'.
func (m *MMClient) StatusLoop() {
retries := 0
backoff := time.Second * 60
if m.OnWsConnect != nil {
m.OnWsConnect()
}
- m.log.Debug("StatusLoop:", m.OnWsConnect != nil)
+ m.logger.Debug("StatusLoop:", m.OnWsConnect != nil)
for {
if m.WsQuit {
return
}
if m.WsConnected {
if err := m.checkAlive(); err != nil {
- logrus.Errorf("Connection is not alive: %#v", err)
+ m.logger.Errorf("Connection is not alive: %#v", err)
}
select {
case <-m.WsPingChan:
- m.log.Debug("WS PONG received")
+ m.logger.Debug("WS PONG received")
backoff = time.Second * 60
case <-time.After(time.Second * 5):
if retries > 3 {
- m.log.Debug("StatusLoop() timeout")
+ m.logger.Debug("StatusLoop() timeout")
m.Logout()
m.WsQuit = false
err := m.Login()
if err != nil {
- logrus.Errorf("Login failed: %#v", err)
+ m.logger.Errorf("Login failed: %#v", err)
break
}
if m.OnWsConnect != nil {
diff --git a/matterclient/messages.go b/matterclient/messages.go
index c2325c0..b46feff 100644
--- a/matterclient/messages.go
+++ b/matterclient/messages.go
@@ -1014 +1014 @@ func (m *MMClient) parseActionPost(rmsg *Message) {
// add post to cache, if it already exists don't relay this again.
// this should fix reposts
if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok {
- m.log.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string))
+ m.logger.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string))
rmsg.Text = ""
return
}
data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string)))
// we don't have the user, refresh the userlist
if m.GetUser(data.UserId) == nil {
- m.log.Infof("User '%v' is not known, ignoring message '%#v'",
+ m.logger.Infof("User '%v' is not known, ignoring message '%#v'",
data.UserId, data)
return
}
@@ -547 +547 @@ func (m *MMClient) parseMessage(rmsg *Message) {
}
case "group_added":
if err := m.UpdateChannels(); err != nil {
- m.log.Errorf("failed to update channels: %#v", err)
+ m.logger.Errorf("failed to update channels: %#v", err)
}
/*
case model.ACTION_USER_REMOVED:
@@ -17818 +17818 @@ func (m *MMClient) SendDirectMessage(toUserId string, msg string, rootId string)
}
func (m *MMClient) SendDirectMessageProps(toUserId string, msg string, rootId string, props map[string]interface{}) { //nolint:golint
- m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg)
+ m.logger.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg)
// create DM channel (only happens on first message)
_, resp := m.Client.CreateDirectChannel(m.User.Id, toUserId)
if resp.Error != nil {
- m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, resp.Error)
+ m.logger.Debugf("SendDirectMessage to %#v failed: %s", toUserId, resp.Error)
return
}
channelName := model.GetDMNameFromIds(toUserId, m.User.Id)
// update our channels
if err := m.UpdateChannels(); err != nil {
- m.log.Errorf("failed to update channels: %#v", err)
+ m.logger.Errorf("failed to update channels: %#v", err)
}
// build & send the message
diff --git a/matterclient/users.go b/matterclient/users.go
index 3dea7ce..11f22aa 100644
--- a/matterclient/users.go
+++ b/matterclient/users.go
@@ -1247 +1247 @@ func (m *MMClient) UpdateUserNick(nick string) error {
func (m *MMClient) UsernamesInChannel(channelId string) []string { //nolint:golint
res, resp := m.Client.GetChannelMembers(channelId, 0, 50000, "")
if resp.Error != nil {
- m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, resp.Error)
+ m.logger.Errorf("UsernamesInChannel(%s) failed: %s", channelId, resp.Error)
return []string{}
}
allusers := m.GetUsers()