Files
browser/plugins/examples/scum-server-plugin/companion/console_collector.go
T

119 lines
3.7 KiB
Go

package companion
import (
"context"
"fmt"
"strings"
"time"
)
type ConsoleLogStreamClient interface {
StreamLogEvents(context.Context, func(LogStreamEvent) error) error
}
type ConsoleLogStore interface {
EnsureSchema(context.Context) error
StoreConsoleRecords(context.Context, []ConsoleRecord) (int, error)
StoreSemanticEventBatch(context.Context, SemanticEventBatch) (int, error)
}
type ConsoleLogCollector struct {
Client ConsoleLogStreamClient
Store ConsoleLogStore
ServerInstanceID string
CorrelationSecret string
Backoff time.Duration
OnSemanticEvents func(context.Context, SemanticEventBatch) error
}
func NewConsoleLogCollector(client ConsoleLogStreamClient, store ConsoleLogStore, serverInstanceID string, correlationSecret string) *ConsoleLogCollector {
return &ConsoleLogCollector{Client: client, Store: store, ServerInstanceID: serverInstanceID, CorrelationSecret: correlationSecret, Backoff: 2 * time.Second}
}
func (collector *ConsoleLogCollector) Run(ctx context.Context) error {
if collector == nil || collector.Client == nil || collector.Store == nil || strings.TrimSpace(collector.ServerInstanceID) == "" {
return fmt.Errorf("console log collector is not configured")
}
if err := collector.Store.EnsureSchema(ctx); err != nil {
return err
}
backoff := collector.Backoff
if backoff <= 0 {
backoff = 2 * time.Second
}
for {
err := collector.Client.StreamLogEvents(ctx, collector.handleEvent(ctx))
if ctx.Err() != nil {
return ctx.Err()
}
if err != nil {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(backoff):
}
continue
}
}
}
func (collector *ConsoleLogCollector) handleEvent(ctx context.Context) func(LogStreamEvent) error {
return func(event LogStreamEvent) error {
record, ok := consoleRecordFromLogEvent(collector.ServerInstanceID, event)
if !ok {
return nil
}
if _, err := collector.Store.StoreConsoleRecords(ctx, []ConsoleRecord{record}); err != nil {
return err
}
batch := ParseConsoleRecords(collector.ServerInstanceID, []ConsoleRecord{record}, collector.CorrelationSecret)
if _, err := collector.Store.StoreSemanticEventBatch(ctx, batch); err != nil {
return err
}
if collector.OnSemanticEvents != nil {
if err := collector.OnSemanticEvents(ctx, batch); err != nil {
return err
}
}
return nil
}
}
func consoleRecordFromLogEvent(serverInstanceID string, event LogStreamEvent) (ConsoleRecord, bool) {
if event.ServerInstanceID != serverInstanceID || event.Entry.Seq == 0 || strings.TrimSpace(event.Entry.Line) == "" {
return ConsoleRecord{}, false
}
stream := consoleStreamName(event.StreamKey)
if stream == "" {
if !knownPluginLogStream(event.StreamKey) {
return ConsoleRecord{}, false
}
stream = strings.ToLower(strings.TrimSpace(event.StreamKey))
}
occurredAt := event.Entry.Timestamp
if occurredAt.IsZero() {
occurredAt = time.Now().UTC()
}
return ConsoleRecord{ServerID: serverInstanceID, Stream: stream, Sequence: event.Entry.Seq, OccurredAt: occurredAt.UTC(), Text: event.Entry.Line}, true
}
func knownPluginLogStream(streamKey string) bool {
switch strings.ToLower(strings.TrimSpace(streamKey)) {
case "scum.login", "scum.chat", "scum.server", "scum.kill", "scum.trade", "scum.admin", "scum.performance":
return true
default:
return false
}
}
func consoleStreamName(streamKey string) string {
key := strings.ToLower(strings.TrimSpace(streamKey))
if strings.Contains(key, "stderr") || strings.HasSuffix(key, ".err") || strings.HasSuffix(key, "-err") {
return "stderr"
}
if strings.Contains(key, "stdout") || strings.Contains(key, "console") || strings.HasSuffix(key, ".out") || strings.HasSuffix(key, "-out") {
return "stdout"
}
return ""
}