mirror of https://github.com/velour/catbase.git
Merge pull request #181 from velour/slacklog
slackApp: add logging function
This commit is contained in:
commit
22e271af75
|
@ -69,3 +69,4 @@ util/*/files
|
||||||
util/*/files
|
util/*/files
|
||||||
run.sh
|
run.sh
|
||||||
.idea
|
.idea
|
||||||
|
logs
|
||||||
|
|
|
@ -13,9 +13,13 @@ type Messages []Message
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
User *user.User
|
User *user.User
|
||||||
Channel, Body string
|
// With Slack, channel is the ID of a channel
|
||||||
|
Channel string
|
||||||
|
// With slack, channelName is the nice name of a channel
|
||||||
|
ChannelName string
|
||||||
|
Body string
|
||||||
IsIM bool
|
IsIM bool
|
||||||
Raw string
|
Raw interface{}
|
||||||
Command bool
|
Command bool
|
||||||
Action bool
|
Action bool
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package slackapp
|
package slackapp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/nlopes/slack"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ import (
|
||||||
// • Strips < and > surrounding links.
|
// • Strips < and > surrounding links.
|
||||||
//
|
//
|
||||||
// This was directly bogarted from velour/chat with emoji conversion removed.
|
// This was directly bogarted from velour/chat with emoji conversion removed.
|
||||||
func fixText(findUser func(id string) (string, error), text string) string {
|
func fixText(findUser func(id string) (*slack.User, error), text string) string {
|
||||||
var output []rune
|
var output []rune
|
||||||
for len(text) > 0 {
|
for len(text) > 0 {
|
||||||
r, i := utf8.DecodeRuneInString(text)
|
r, i := utf8.DecodeRuneInString(text)
|
||||||
|
@ -48,7 +49,7 @@ func fixText(findUser func(id string) (string, error), text string) string {
|
||||||
return string(output)
|
return string(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixTag(findUser func(string) (string, error), tag []rune) ([]rune, bool) {
|
func fixTag(findUser func(string) (*slack.User, error), tag []rune) ([]rune, bool) {
|
||||||
switch {
|
switch {
|
||||||
case hasPrefix(tag, "@U"):
|
case hasPrefix(tag, "@U"):
|
||||||
if i := indexRune(tag, '|'); i >= 0 {
|
if i := indexRune(tag, '|'); i >= 0 {
|
||||||
|
@ -56,7 +57,7 @@ func fixTag(findUser func(string) (string, error), tag []rune) ([]rune, bool) {
|
||||||
}
|
}
|
||||||
if findUser != nil {
|
if findUser != nil {
|
||||||
if u, err := findUser(string(tag[1:])); err == nil {
|
if u, err := findUser(string(tag[1:])); err == nil {
|
||||||
return []rune(u), true
|
return []rune(u.Name), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tag, true
|
return tag, true
|
||||||
|
|
|
@ -9,9 +9,12 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -25,7 +28,15 @@ import (
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DEFAULT_RING = 5
|
const DefaultRing = 5
|
||||||
|
const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Action}}* {{.User.Name}}{{else}}<{{.User.Name}}>{{end}} {{.Body}}\n"
|
||||||
|
|
||||||
|
// 11:10AM DBG connectors/slackapp/slackApp.go:496 > Slack event dir=logs raw={"Action":false,"AdditionalData":
|
||||||
|
// {"RAW_SLACK_TIMESTAMP":"1559920235.001100"},"Body":"aoeu","Channel":"C0S04SMRC","ChannelName":"test",
|
||||||
|
// "Command":false,"Host":"","IsIM":false,"Raw":{"channel":"C0S04SMRC","channel_type":"channel",
|
||||||
|
// "event_ts":1559920235.001100,"files":null,"text":"aoeu","thread_ts":"","ts":"1559920235.001100",
|
||||||
|
// "type":"message","upload":false,"user":"U0RLUDELD"},"Time":"2019-06-07T11:10:35.0000011-04:00",
|
||||||
|
// "User":{"Admin":false,"ID":"U0RLUDELD","Name":"flyngpngn"}}
|
||||||
|
|
||||||
type SlackApp struct {
|
type SlackApp struct {
|
||||||
bot bot.Bot
|
bot bot.Bot
|
||||||
|
@ -40,14 +51,20 @@ type SlackApp struct {
|
||||||
lastRecieved time.Time
|
lastRecieved time.Time
|
||||||
|
|
||||||
myBotID string
|
myBotID string
|
||||||
users map[string]string
|
users map[string]*slack.User
|
||||||
emoji map[string]string
|
emoji map[string]string
|
||||||
|
channels map[string]*slack.Channel
|
||||||
|
|
||||||
event bot.Callback
|
event bot.Callback
|
||||||
|
|
||||||
msgIDBuffer *ring.Ring
|
msgIDBuffer *ring.Ring
|
||||||
|
|
||||||
|
logFormat *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixDate(input time.Time, format string) string {
|
||||||
|
return input.Format(format)
|
||||||
|
}
|
||||||
func New(c *config.Config) *SlackApp {
|
func New(c *config.Config) *SlackApp {
|
||||||
token := c.Get("slack.token", "NONE")
|
token := c.Get("slack.token", "NONE")
|
||||||
if token == "NONE" {
|
if token == "NONE" {
|
||||||
|
@ -56,12 +73,18 @@ func New(c *config.Config) *SlackApp {
|
||||||
|
|
||||||
api := slack.New(token, slack.OptionDebug(false))
|
api := slack.New(token, slack.OptionDebug(false))
|
||||||
|
|
||||||
idBuf := ring.New(c.GetInt("ringSize", DEFAULT_RING))
|
idBuf := ring.New(c.GetInt("ringSize", DefaultRing))
|
||||||
for i := 0; i < idBuf.Len(); i++ {
|
for i := 0; i < idBuf.Len(); i++ {
|
||||||
idBuf.Value = ""
|
idBuf.Value = ""
|
||||||
idBuf = idBuf.Next()
|
idBuf = idBuf.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tplTxt := c.GetString("slackapp.log.format", defaultLogFormat)
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"fixDate": fixDate,
|
||||||
|
}
|
||||||
|
tpl := template.Must(template.New("log").Funcs(funcs).Parse(tplTxt))
|
||||||
|
|
||||||
return &SlackApp{
|
return &SlackApp{
|
||||||
api: api,
|
api: api,
|
||||||
config: c,
|
config: c,
|
||||||
|
@ -70,9 +93,11 @@ func New(c *config.Config) *SlackApp {
|
||||||
verification: c.Get("slack.verification", "NONE"),
|
verification: c.Get("slack.verification", "NONE"),
|
||||||
myBotID: c.Get("slack.botid", ""),
|
myBotID: c.Get("slack.botid", ""),
|
||||||
lastRecieved: time.Now(),
|
lastRecieved: time.Now(),
|
||||||
users: make(map[string]string),
|
users: make(map[string]*slack.User),
|
||||||
emoji: make(map[string]string),
|
emoji: make(map[string]string),
|
||||||
|
channels: make(map[string]*slack.Channel),
|
||||||
msgIDBuffer: idBuf,
|
msgIDBuffer: idBuf,
|
||||||
|
logFormat: tpl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,11 +112,6 @@ func (s *SlackApp) Serve() error {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
buf.ReadFrom(r.Body)
|
buf.ReadFrom(r.Body)
|
||||||
body := buf.String()
|
body := buf.String()
|
||||||
var raw interface{}
|
|
||||||
json.Unmarshal(json.RawMessage(body), &raw)
|
|
||||||
log.Debug().
|
|
||||||
Interface("raw", raw).
|
|
||||||
Msg("Slack event")
|
|
||||||
eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification}))
|
eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification}))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Error().Err(e)
|
log.Error().Err(e)
|
||||||
|
@ -163,9 +183,13 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) {
|
||||||
Msg("Got a duplicate message from server")
|
Msg("Got a duplicate message from server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isItMe := msg.BotID != "" && msg.BotID == s.myBotID
|
isItMe := msg.BotID != "" && msg.BotID == s.myBotID
|
||||||
if !isItMe && msg.ThreadTimeStamp == "" {
|
if !isItMe && msg.ThreadTimeStamp == "" {
|
||||||
m := s.buildMessage(msg)
|
m := s.buildMessage(msg)
|
||||||
|
if err := s.log(m); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error logging message")
|
||||||
|
}
|
||||||
if m.Time.Before(s.lastRecieved) {
|
if m.Time.Before(s.lastRecieved) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Time("ts", m.Time).
|
Time("ts", m.Time).
|
||||||
|
@ -380,21 +404,28 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
|
|
||||||
isAction := m.SubType == "me_message"
|
isAction := m.SubType == "me_message"
|
||||||
|
|
||||||
|
// We have to try a few layers to get a valid name for the user because Slack
|
||||||
|
name := "UNKNOWN"
|
||||||
u, _ := s.getUser(m.User)
|
u, _ := s.getUser(m.User)
|
||||||
if m.Username != "" {
|
if u != nil {
|
||||||
u = m.Username
|
name = u.Name
|
||||||
}
|
}
|
||||||
|
if m.Username != "" && u == nil {
|
||||||
|
name = m.Username
|
||||||
|
}
|
||||||
|
ch, _ := s.getChannel(m.Channel)
|
||||||
|
|
||||||
tstamp := slackTStoTime(m.TimeStamp)
|
tstamp := slackTStoTime(m.TimeStamp)
|
||||||
|
|
||||||
return msg.Message{
|
return msg.Message{
|
||||||
User: &user.User{
|
User: &user.User{
|
||||||
ID: m.User,
|
ID: m.User,
|
||||||
Name: u,
|
Name: name,
|
||||||
},
|
},
|
||||||
Body: text,
|
Body: text,
|
||||||
Raw: m.Text,
|
Raw: m,
|
||||||
Channel: m.Channel,
|
Channel: m.Channel,
|
||||||
|
ChannelName: ch.Name,
|
||||||
IsIM: m.ChannelType == "im",
|
IsIM: m.ChannelType == "im",
|
||||||
Command: isCmd,
|
Command: isCmd,
|
||||||
Action: isAction,
|
Action: isAction,
|
||||||
|
@ -405,8 +436,25 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) getChannel(id string) (*slack.Channel, error) {
|
||||||
|
if ch, ok := s.channels[id]; ok {
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("Channel not known, requesting info")
|
||||||
|
|
||||||
|
ch, err := s.api.GetChannelInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.channels[id] = ch
|
||||||
|
return s.channels[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get username for Slack user ID
|
// Get username for Slack user ID
|
||||||
func (s *SlackApp) getUser(id string) (string, error) {
|
func (s *SlackApp) getUser(id string) (*slack.User, error) {
|
||||||
if name, ok := s.users[id]; ok {
|
if name, ok := s.users[id]; ok {
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
@ -416,9 +464,9 @@ func (s *SlackApp) getUser(id string) (string, error) {
|
||||||
Msg("User not already found, requesting info")
|
Msg("User not already found, requesting info")
|
||||||
u, err := s.api.GetUserInfo(id)
|
u, err := s.api.GetUserInfo(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "UNKNOWN", err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.users[id] = u.Name
|
s.users[id] = u
|
||||||
return s.users[id], nil
|
return s.users[id], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,8 +501,44 @@ func (s *SlackApp) Who(id string) []string {
|
||||||
Str("user", m).
|
Str("user", m).
|
||||||
Msg("Couldn't get user")
|
Msg("Couldn't get user")
|
||||||
continue
|
continue
|
||||||
|
/**/
|
||||||
}
|
}
|
||||||
ret = append(ret, u)
|
ret = append(ret, u.Name)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// log writes to a <slackapp.log.dir>/<channel>.log
|
||||||
|
// Uses slackapp.log.format to write entries
|
||||||
|
func (s *SlackApp) log(raw msg.Message) error {
|
||||||
|
dir := path.Join(s.config.Get("slackapp.log.dir", "logs"), raw.ChannelName)
|
||||||
|
now := time.Now()
|
||||||
|
fname := now.Format("20060102") + ".log"
|
||||||
|
path := path.Join(dir, fname)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Interface("raw", raw).
|
||||||
|
Str("dir", dir).
|
||||||
|
Msg("Slack event")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not create log directory")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().
|
||||||
|
Err(err).
|
||||||
|
Msg("Error opening log file")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := s.logFormat.Execute(f, raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Sync()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue