Add channel logging feature

This commit is contained in:
Noah 2023-11-11 14:59:49 -08:00
parent 8004edb7b8
commit 1e702b0e1e
5 changed files with 182 additions and 0 deletions

View File

@ -50,6 +50,8 @@ type Config struct {
VIP VIP VIP VIP
MessageFilters []*MessageFilter MessageFilters []*MessageFilter
Logging Logging
} }
type TurnConfig struct { type TurnConfig struct {
@ -99,6 +101,14 @@ type WebhookURL struct {
URL string URL string
} }
// Logging configs to monitor channels or usernames.
type Logging struct {
Enabled bool
Directory string
Channels []string
Usernames []string
}
// Current loaded configuration. // Current loaded configuration.
var Current = DefaultConfig() var Current = DefaultConfig()
@ -175,6 +185,11 @@ func DefaultConfig() Config {
ChatServerResponse: "Watch your language.", ChatServerResponse: "Watch your language.",
}, },
}, },
Logging: Logging{
Directory: "./logs",
Channels: []string{"lobby", "offtopic"},
Usernames: []string{},
},
} }
c.JWT.Strict = true c.JWT.Strict = true
return c return c

View File

@ -220,12 +220,26 @@ func (s *Server) OnMessage(sub *Subscriber, msg messages.Message) {
return return
} }
// Log this conversation?
if IsLoggingUsername(sub) {
// The sender of this message is being logged.
LogMessage(sub, rcpt.Username, sub.Username, msg)
} else if IsLoggingUsername(rcpt) {
// The recipient of this message is being logged.
LogMessage(rcpt, sub.Username, sub.Username, msg)
}
if err := s.SendTo(msg.Channel, message); err != nil { if err := s.SendTo(msg.Channel, message); err != nil {
sub.ChatServer("Your message could not be delivered: %s", err) sub.ChatServer("Your message could not be delivered: %s", err)
} }
return return
} }
// Are we logging this public channel?
if IsLoggingChannel(msg.Channel) {
LogChannel(s, msg.Channel, sub.Username, msg)
}
// Broadcast a chat message to the room. // Broadcast a chat message to the room.
s.Broadcast(message) s.Broadcast(message)
} }

144
pkg/logging.go Normal file
View File

@ -0,0 +1,144 @@
package barertc
import (
"fmt"
"io"
"os"
"strings"
"time"
"git.kirsle.net/apps/barertc/pkg/config"
"git.kirsle.net/apps/barertc/pkg/log"
"git.kirsle.net/apps/barertc/pkg/messages"
)
// IsLoggingUsername checks whether the app is currently configured to log a user's DMs.
func IsLoggingUsername(sub *Subscriber) bool {
if !config.Current.Logging.Enabled {
return false
}
// Has a cached setting and writer.
if sub.log {
return true
}
// Check the server config.
for _, username := range config.Current.Logging.Usernames {
if username == sub.Username {
sub.log = true
}
}
return sub.log
}
// IsLoggingChannel checks whether the app is currently logging a public channel.
func IsLoggingChannel(channel string) bool {
if !config.Current.Logging.Enabled {
return false
}
for _, value := range config.Current.Logging.Channels {
if value == channel {
return true
}
}
return false
}
// LogMessage appends to a user's conversation log.
func LogMessage(sub *Subscriber, otherUsername, senderUsername string, msg messages.Message) {
if !sub.log {
return
}
// Create or get the filehandle.
fh, err := initLogFile(sub, "@"+sub.Username, otherUsername)
if err != nil {
log.Error("LogMessage(%s): %s", sub.Username, err)
return
}
fh.Write(
[]byte(fmt.Sprintf(
"%s [%s] %s\n",
time.Now().Format(time.RFC3339),
senderUsername,
msg.Message,
)),
)
}
// LogChannel appends to a channel's conversation log.
func LogChannel(s *Server, channel string, username string, msg messages.Message) {
fh, err := initLogFile(s, channel)
if err != nil {
log.Error("LogChannel(%s): %s", channel, err)
}
fh.Write(
[]byte(fmt.Sprintf(
"%s [%s] %s\n",
time.Now().Format(time.RFC3339),
username,
msg.Message,
)),
)
}
// Initialize a logging directory.
func initLogFile(sub LogCacheable, components ...string) (io.Writer, error) {
// Initialize the logfh cache?
var logfh = sub.GetLogFilehandleCache()
var (
suffix = components[len(components)-1]
middle = components[:len(components)-1]
paths = append([]string{
config.Current.Logging.Directory,
}, middle...,
)
filename = strings.Join(
append(paths, suffix+".txt"),
"/",
)
)
// Already have this conversation log open?
if fh, ok := logfh[suffix]; ok {
return fh, nil
}
log.Warn("Initialize log directory: path=%+v suffix=%s", paths, suffix)
if err := os.MkdirAll(strings.Join(paths, "/"), 0755); err != nil {
return nil, err
}
fh, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
logfh[suffix] = fh
return logfh[suffix], nil
}
// Interface for objects that hold log filehandle caches.
type LogCacheable interface {
GetLogFilehandleCache() map[string]io.Writer
}
// Implementations of LogCacheable.
func (sub *Subscriber) GetLogFilehandleCache() map[string]io.Writer {
if sub.logfh == nil {
sub.logfh = map[string]io.Writer{}
}
return sub.logfh
}
func (s *Server) GetLogFilehandleCache() map[string]io.Writer {
if s.logfh == nil {
s.logfh = map[string]io.Writer{}
}
return s.logfh
}

View File

@ -1,6 +1,7 @@
package barertc package barertc
import ( import (
"io"
"net/http" "net/http"
"sync" "sync"
) )
@ -16,6 +17,9 @@ type Server struct {
// Connected WebSocket subscribers. // Connected WebSocket subscribers.
subscribersMu sync.RWMutex subscribersMu sync.RWMutex
subscribers map[*Subscriber]struct{} subscribers map[*Subscriber]struct{}
// Cached filehandles for channel logging.
logfh map[string]io.Writer
} }
// NewServer initializes the Server. // NewServer initializes the Server.

View File

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
@ -44,6 +45,10 @@ type Subscriber struct {
// Record which message IDs belong to this user. // Record which message IDs belong to this user.
midMu sync.Mutex midMu sync.Mutex
messageIDs map[int64]struct{} messageIDs map[int64]struct{}
// Logging.
log bool
logfh map[string]io.Writer
} }
// ReadLoop spawns a goroutine that reads from the websocket connection. // ReadLoop spawns a goroutine that reads from the websocket connection.