chore: apply gofmt formatting

This commit is contained in:
Houmgaor
2026-02-06 13:02:38 +01:00
parent 09f829f8be
commit 4960c5cb5d
16 changed files with 159 additions and 148 deletions

View File

@@ -127,17 +127,17 @@ type SaveDumpOptions struct {
// GameplayOptions has various gameplay modifiers // GameplayOptions has various gameplay modifiers
type GameplayOptions struct { type GameplayOptions struct {
FeaturedWeapons int // Number of Active Feature weapons to generate daily FeaturedWeapons int // Number of Active Feature weapons to generate daily
MaximumNP int // Maximum number of NP held by a player MaximumNP int // Maximum number of NP held by a player
MaximumRP uint16 // Maximum number of RP held by a player MaximumRP uint16 // Maximum number of RP held by a player
MaximumFP uint32 // Maximum number of Festa Points held by a player MaximumFP uint32 // Maximum number of Festa Points held by a player
DisableLoginBoost bool // Disables the Login Boost system DisableLoginBoost bool // Disables the Login Boost system
DisableBoostTime bool // Disables the daily NetCafe Boost Time DisableBoostTime bool // Disables the daily NetCafe Boost Time
BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for BoostTimeDuration int // The number of minutes NetCafe Boost Time lasts for
ClanMealDuration int // Seconds that a Clan Meal can be activated for after cooking ClanMealDuration int // Seconds that a Clan Meal can be activated for after cooking
ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [[Rank, Members], ...] ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [[Rank, Members], ...]
BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily
DailyQuestAllowance uint32 // Number of Daily Quests to allow daily DailyQuestAllowance uint32 // Number of Daily Quests to allow daily
} }
// Logging holds the logging configuration. // Logging holds the logging configuration.

View File

@@ -9,7 +9,8 @@
// Packet Structure: // Packet Structure:
// //
// MHF packets follow this wire format: // MHF packets follow this wire format:
// [2 bytes: Opcode][N bytes: Packet-specific data][2 bytes: Footer 0x00 0x10] //
// [2 bytes: Opcode][N bytes: Packet-specific data][2 bytes: Footer 0x00 0x10]
// //
// Each packet type defines its own structure matching the binary format expected // Each packet type defines its own structure matching the binary format expected
// by the Monster Hunter Frontier client. // by the Monster Hunter Frontier client.

View File

@@ -514,9 +514,9 @@ func TestMsgMhfUpdateEtcPointOpcode(t *testing.T) {
// TestAchievementPacketParse tests simple achievement packet parsing // TestAchievementPacketParse tests simple achievement packet parsing
func TestAchievementPacketParse(t *testing.T) { func TestAchievementPacketParse(t *testing.T) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint8(5) // AchievementID bf.WriteUint8(5) // AchievementID
bf.WriteUint16(100) // Unk1 bf.WriteUint16(100) // Unk1
bf.WriteUint16(200) // Unk2 bf.WriteUint16(200) // Unk2
bf.Seek(0, io.SeekStart) bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{} pkt := &MsgMhfAddAchievement{}

View File

@@ -36,9 +36,9 @@ func TestMsgMhfGetAchievementDetailedParse(t *testing.T) {
// TestMsgMhfAddAchievementDetailedParse tests MsgMhfAddAchievement parsing // TestMsgMhfAddAchievementDetailedParse tests MsgMhfAddAchievement parsing
func TestMsgMhfAddAchievementDetailedParse(t *testing.T) { func TestMsgMhfAddAchievementDetailedParse(t *testing.T) {
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
bf.WriteUint8(42) // AchievementID bf.WriteUint8(42) // AchievementID
bf.WriteUint16(12345) // Unk1 bf.WriteUint16(12345) // Unk1
bf.WriteUint16(0xFFFF) // Unk2 - max value bf.WriteUint16(0xFFFF) // Unk2 - max value
bf.Seek(0, io.SeekStart) bf.Seek(0, io.SeekStart)
pkt := &MsgMhfAddAchievement{} pkt := &MsgMhfAddAchievement{}
@@ -61,12 +61,12 @@ func TestMsgMhfAddAchievementDetailedParse(t *testing.T) {
// TestMsgSysCastBinaryDetailedParse tests MsgSysCastBinary parsing with various payloads // TestMsgSysCastBinaryDetailedParse tests MsgSysCastBinary parsing with various payloads
func TestMsgSysCastBinaryDetailedParse(t *testing.T) { func TestMsgSysCastBinaryDetailedParse(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
unk0 uint16 unk0 uint16
unk1 uint16 unk1 uint16
broadcastType uint8 broadcastType uint8
messageType uint8 messageType uint8
payload []byte payload []byte
}{ }{
{"empty payload", 0, 0, 1, 2, []byte{}}, {"empty payload", 0, 0, 1, 2, []byte{}},
{"typical payload", 100, 200, 0x10, 0x20, []byte{0x01, 0x02, 0x03}}, {"typical payload", 100, 200, 0x10, 0x20, []byte{0x01, 0x02, 0x03}},

View File

@@ -139,8 +139,8 @@ func TestGetAchData_ValueAccumulation(t *testing.T) {
func TestGetAchData_NextValueByLevel(t *testing.T) { func TestGetAchData_NextValueByLevel(t *testing.T) {
tests := []struct { tests := []struct {
level uint8 level uint8
wantNext uint16 wantNext uint16
approxScore int32 approxScore int32
}{ }{
{0, 5, 0}, {0, 5, 0},

View File

@@ -311,4 +311,3 @@ func TestEmptyHandlers_NoDb(t *testing.T) {
}) })
} }
} }

View File

@@ -44,13 +44,13 @@ import (
// Config holds configuration parameters for creating a new channel server. // Config holds configuration parameters for creating a new channel server.
type Config struct { type Config struct {
ID uint16 // Channel server ID (unique identifier) ID uint16 // Channel server ID (unique identifier)
Logger *zap.Logger // Logger instance for this channel server Logger *zap.Logger // Logger instance for this channel server
DB *sqlx.DB // Database connection pool DB *sqlx.DB // Database connection pool
DiscordBot *discordbot.DiscordBot // Optional Discord bot for chat integration DiscordBot *discordbot.DiscordBot // Optional Discord bot for chat integration
ErupeConfig *config.Config // Global Erupe configuration ErupeConfig *config.Config // Global Erupe configuration
Name string // Display name for the server (shown in broadcasts) Name string // Display name for the server (shown in broadcasts)
Enable bool // Whether this server is enabled Enable bool // Whether this server is enabled
} }
// userBinaryPartID is a composite key for identifying a specific part of a user's binary data. // userBinaryPartID is a composite key for identifying a specific part of a user's binary data.
@@ -86,15 +86,15 @@ type Server struct {
erupeConfig *config.Config // Global configuration erupeConfig *config.Config // Global configuration
// Connection management // Connection management
acceptConns chan net.Conn // Channel for new accepted connections acceptConns chan net.Conn // Channel for new accepted connections
deleteConns chan net.Conn // Channel for connections to be cleaned up deleteConns chan net.Conn // Channel for connections to be cleaned up
sessions map[net.Conn]*Session // Active sessions keyed by connection sessions map[net.Conn]*Session // Active sessions keyed by connection
listener net.Listener // TCP listener (created when Server.Start is called) listener net.Listener // TCP listener (created when Server.Start is called)
isShuttingDown bool // Shutdown flag to stop goroutines gracefully isShuttingDown bool // Shutdown flag to stop goroutines gracefully
// Stage (game room) management // Stage (game room) management
stagesLock sync.RWMutex // Protects stages map (RWMutex for concurrent reads) stagesLock sync.RWMutex // Protects stages map (RWMutex for concurrent reads)
stages map[string]*Stage // Active stages keyed by stage ID string stages map[string]*Stage // Active stages keyed by stage ID string
// Localization // Localization
dict map[string]string // Language string mappings for server messages dict map[string]string // Language string mappings for server messages
@@ -105,9 +105,9 @@ type Server struct {
userBinaryParts map[userBinaryPartID][]byte // Chunked binary data by character userBinaryParts map[userBinaryPartID][]byte // Chunked binary data by character
// Semaphore (multiplayer coordination) management // Semaphore (multiplayer coordination) management
semaphoreLock sync.RWMutex // Protects semaphore map and semaphoreIndex semaphoreLock sync.RWMutex // Protects semaphore map and semaphoreIndex
semaphore map[string]*Semaphore // Active semaphores keyed by semaphore ID semaphore map[string]*Semaphore // Active semaphores keyed by semaphore ID
semaphoreIndex uint32 // Auto-incrementing ID for new semaphores (starts at 7) semaphoreIndex uint32 // Auto-incrementing ID for new semaphores (starts at 7)
// Optional integrations // Optional integrations
discordBot *discordbot.DiscordBot // Discord bot for chat relay (nil if disabled) discordBot *discordbot.DiscordBot // Discord bot for chat relay (nil if disabled)

View File

@@ -48,32 +48,32 @@ type Session struct {
sync.Mutex // Protects session state during concurrent handler execution sync.Mutex // Protects session state during concurrent handler execution
// Core connection and logging // Core connection and logging
logger *zap.Logger // Logger with connection address logger *zap.Logger // Logger with connection address
server *Server // Parent server reference server *Server // Parent server reference
rawConn net.Conn // Underlying TCP connection rawConn net.Conn // Underlying TCP connection
cryptConn *network.CryptConn // Encrypted connection wrapper cryptConn *network.CryptConn // Encrypted connection wrapper
sendPackets chan packet // Outbound packet queue (buffered, size 20) sendPackets chan packet // Outbound packet queue (buffered, size 20)
clientContext *clientctx.ClientContext // Client version and capabilities clientContext *clientctx.ClientContext // Client version and capabilities
lastPacket time.Time // Timestamp of last received packet (for timeout detection) lastPacket time.Time // Timestamp of last received packet (for timeout detection)
// Stage (game area) state // Stage (game area) state
userEnteredStage bool // Whether player has entered any stage during this session userEnteredStage bool // Whether player has entered any stage during this session
stageID string // Current stage ID string (e.g., "sl1Ns200p0a0u0") stageID string // Current stage ID string (e.g., "sl1Ns200p0a0u0")
stage *Stage // Pointer to current stage object stage *Stage // Pointer to current stage object
reservationStage *Stage // Stage reserved for quest (used by unreserve packet) reservationStage *Stage // Stage reserved for quest (used by unreserve packet)
stagePass string // Temporary password storage for password-protected stages stagePass string // Temporary password storage for password-protected stages
stageMoveStack *stringstack.StringStack // Navigation history for "back" functionality stageMoveStack *stringstack.StringStack // Navigation history for "back" functionality
// Player identity and state // Player identity and state
charID uint32 // Character ID for this session charID uint32 // Character ID for this session
Name string // Character name (for debugging/logging) Name string // Character name (for debugging/logging)
prevGuildID uint32 // Last guild ID queried (cached for InfoGuild) prevGuildID uint32 // Last guild ID queried (cached for InfoGuild)
token string // Authentication token from sign server token string // Authentication token from sign server
logKey []byte // Logging encryption key logKey []byte // Logging encryption key
sessionStart int64 // Session start timestamp (Unix time) sessionStart int64 // Session start timestamp (Unix time)
courses []mhfcourse.Course // Active Monster Hunter courses (buffs/subscriptions) courses []mhfcourse.Course // Active Monster Hunter courses (buffs/subscriptions)
kqf []byte // Key Quest Flags (quest progress tracking) kqf []byte // Key Quest Flags (quest progress tracking)
kqfOverride bool // Whether KQF is being overridden kqfOverride bool // Whether KQF is being overridden
// Quest/event coordination // Quest/event coordination
semaphore *Semaphore // Semaphore for quest/event participation (if in a coordinated activity) semaphore *Semaphore // Semaphore for quest/event participation (if in a coordinated activity)

View File

@@ -13,10 +13,10 @@ import (
// other players in the same stage. Each object has an owner, position, and // other players in the same stage. Each object has an owner, position, and
// unique ID for client-server synchronization. // unique ID for client-server synchronization.
type Object struct { type Object struct {
sync.RWMutex // Protects object state during updates sync.RWMutex // Protects object state during updates
id uint32 // Unique object ID (see NextObjectID for ID generation) id uint32 // Unique object ID (see NextObjectID for ID generation)
ownerCharID uint32 // Character ID of the player who placed this object ownerCharID uint32 // Character ID of the player who placed this object
x, y, z float32 // 3D position coordinates x, y, z float32 // 3D position coordinates
} }
// stageBinaryKey is a composite key for identifying a specific piece of stage binary data. // stageBinaryKey is a composite key for identifying a specific piece of stage binary data.

View File

@@ -14,16 +14,16 @@ import (
// A session is identified by the combination of channel and IP:port, tracking // A session is identified by the combination of channel and IP:port, tracking
// all activities from when a player connects until they disconnect. // all activities from when a player connects until they disconnect.
type PlayerSession struct { type PlayerSession struct {
Name string // Player name Name string // Player name
IPPort string // Client IP address and port (e.g., "192.168.1.1:12345") IPPort string // Client IP address and port (e.g., "192.168.1.1:12345")
Channel string // Server channel (e.g., "channel-4") Channel string // Server channel (e.g., "channel-4")
FirstSeen time.Time // Timestamp of first activity FirstSeen time.Time // Timestamp of first activity
LastSeen time.Time // Timestamp of last activity LastSeen time.Time // Timestamp of last activity
Activities []string // List of player activities Activities []string // List of player activities
Stages []string // List of stage changes Stages []string // List of stage changes
Objects []string // List of objects broadcast by this player Objects []string // List of objects broadcast by this player
Errors int // Number of errors encountered during session Errors int // Number of errors encountered during session
SaveCount int // Number of save operations performed SaveCount int // Number of save operations performed
} }
// ConnectionStats aggregates statistics about player connections across all sessions. // ConnectionStats aggregates statistics about player connections across all sessions.
@@ -31,12 +31,12 @@ type PlayerSession struct {
// This structure tracks high-level metrics useful for understanding server usage // This structure tracks high-level metrics useful for understanding server usage
// patterns, peak times, and common connection issues. // patterns, peak times, and common connection issues.
type ConnectionStats struct { type ConnectionStats struct {
TotalConnections int // Total number of player sessions TotalConnections int // Total number of player sessions
UniqueIPs map[string]int // IP addresses to connection count UniqueIPs map[string]int // IP addresses to connection count
UniquePlayers map[string]bool // Set of unique player names UniquePlayers map[string]bool // Set of unique player names
ConnectionsPerDay map[string]int // Date to connection count ConnectionsPerDay map[string]int // Date to connection count
ChannelDistribution map[string]int // Channel to connection count ChannelDistribution map[string]int // Channel to connection count
DisconnectReasons map[string]int // Disconnect reason to count DisconnectReasons map[string]int // Disconnect reason to count
} }
// runConnections implements the connections command for analyzing player connection patterns. // runConnections implements the connections command for analyzing player connection patterns.
@@ -61,9 +61,10 @@ type ConnectionStats struct {
// - v: Verbose output including objects and stage changes // - v: Verbose output including objects and stage changes
// //
// Examples: // Examples:
// runConnections([]string{"-stats"}) //
// runConnections([]string{"-sessions", "-v"}) // runConnections([]string{"-stats"})
// runConnections([]string{"-player", "Sarah", "-sessions"}) // runConnections([]string{"-sessions", "-v"})
// runConnections([]string{"-player", "Sarah", "-sessions"})
func runConnections(args []string) { func runConnections(args []string) {
fs := flag.NewFlagSet("connections", flag.ExitOnError) fs := flag.NewFlagSet("connections", flag.ExitOnError)
@@ -160,7 +161,7 @@ func runConnections(args []string) {
// Track disconnections // Track disconnections
if strings.Contains(entry.Message, "Error on ReadPacket, exiting recv loop") || if strings.Contains(entry.Message, "Error on ReadPacket, exiting recv loop") ||
strings.Contains(entry.Message, "Error reading packet") { strings.Contains(entry.Message, "Error reading packet") {
sessionKey := extractSessionKey(entry.Logger) sessionKey := extractSessionKey(entry.Logger)
if session, exists := sessions[sessionKey]; exists { if session, exists := sessions[sessionKey]; exists {
session.Errors++ session.Errors++

View File

@@ -13,12 +13,12 @@ import (
// Errors can be grouped by message, caller, or logger to identify patterns // Errors can be grouped by message, caller, or logger to identify patterns
// and recurring issues in the logs. // and recurring issues in the logs.
type ErrorGroup struct { type ErrorGroup struct {
Message string // Primary message for this error group Message string // Primary message for this error group
Count int // Total number of occurrences Count int // Total number of occurrences
FirstSeen string // Timestamp of first occurrence FirstSeen string // Timestamp of first occurrence
LastSeen string // Timestamp of last occurrence LastSeen string // Timestamp of last occurrence
Examples []*LogEntry // Sample log entries (limited by the limit flag) Examples []*LogEntry // Sample log entries (limited by the limit flag)
Callers map[string]int // Map of caller locations to occurrence counts Callers map[string]int // Map of caller locations to occurrence counts
} }
// runErrors implements the errors command for extracting and analyzing errors. // runErrors implements the errors command for extracting and analyzing errors.
@@ -44,9 +44,10 @@ type ErrorGroup struct {
// - detailed: Show detailed information including examples and extra data // - detailed: Show detailed information including examples and extra data
// //
// Examples: // Examples:
// runErrors([]string{"-summary"}) //
// runErrors([]string{"-detailed", "-stack"}) // runErrors([]string{"-summary"})
// runErrors([]string{"-group", "caller", "-limit", "20"}) // runErrors([]string{"-detailed", "-stack"})
// runErrors([]string{"-group", "caller", "-limit", "20"})
func runErrors(args []string) { func runErrors(args []string) {
fs := flag.NewFlagSet("errors", flag.ExitOnError) fs := flag.NewFlagSet("errors", flag.ExitOnError)

View File

@@ -23,9 +23,10 @@ import (
// All filters are combined with AND logic. // All filters are combined with AND logic.
// //
// Examples: // Examples:
// runFilter([]string{"-level", "error"}) //
// runFilter([]string{"-since", "1h", "-logger", "channel-4*"}) // runFilter([]string{"-level", "error"})
// runFilter([]string{"-msg", "connection reset", "-count"}) // runFilter([]string{"-since", "1h", "-logger", "channel-4*"})
// runFilter([]string{"-msg", "connection reset", "-count"})
func runFilter(args []string) { func runFilter(args []string) {
fs := flag.NewFlagSet("filter", flag.ExitOnError) fs := flag.NewFlagSet("filter", flag.ExitOnError)
@@ -137,9 +138,10 @@ func runFilter(args []string) {
// - true if the string matches the pattern, false otherwise // - true if the string matches the pattern, false otherwise
// //
// Examples: // Examples:
// matchWildcard("channel-4", "channel-*") // returns true //
// matchWildcard("main.channel-4.error", "*channel-4*") // returns true // matchWildcard("channel-4", "channel-*") // returns true
// matchWildcard("test", "foo*") // returns false // matchWildcard("main.channel-4.error", "*channel-4*") // returns true
// matchWildcard("test", "foo*") // returns false
func matchWildcard(s, pattern string) bool { func matchWildcard(s, pattern string) bool {
if pattern == "*" { if pattern == "*" {
return true return true

View File

@@ -16,10 +16,11 @@ import (
// - tail: Follow logs in real-time (like tail -f) // - tail: Follow logs in real-time (like tail -f)
// //
// Usage: // Usage:
// loganalyzer <command> [options] //
// loganalyzer filter -level error -since 1h // loganalyzer <command> [options]
// loganalyzer errors -summary // loganalyzer filter -level error -since 1h
// loganalyzer stats -detailed // loganalyzer errors -summary
// loganalyzer stats -detailed
func main() { func main() {
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Erupe Log Analyzer - Suite of tools to analyze erupe.log files\n\n") fmt.Fprintf(os.Stderr, "Erupe Log Analyzer - Suite of tools to analyze erupe.log files\n\n")

View File

@@ -17,8 +17,8 @@ import (
// LogEntry represents a parsed log entry from either JSON or timestamp-based format. // LogEntry represents a parsed log entry from either JSON or timestamp-based format.
// //
// The parser supports two log formats: // The parser supports two log formats:
// 1. JSON format: {"level":"info","ts":1762989571.547817,"logger":"main","msg":"Starting"} // 1. JSON format: {"level":"info","ts":1762989571.547817,"logger":"main","msg":"Starting"}
// 2. Timestamp format: 2025-11-12T23:19:31.546Z INFO commands Command Help: Enabled // 2. Timestamp format: 2025-11-12T23:19:31.546Z INFO commands Command Help: Enabled
type LogEntry struct { type LogEntry struct {
Raw string // Original log line Raw string // Original log line
Level string // Log level: info, warn, error, fatal Level string // Log level: info, warn, error, fatal
@@ -50,11 +50,12 @@ type LogEntry struct {
// - An error if the file cannot be opened or read // - An error if the file cannot be opened or read
// //
// Example: // Example:
// entries, err := ParseLogFile("erupe.log") //
// if err != nil { // entries, err := ParseLogFile("erupe.log")
// log.Fatal(err) // if err != nil {
// } // log.Fatal(err)
// fmt.Printf("Parsed %d entries\n", len(entries)) // }
// fmt.Printf("Parsed %d entries\n", len(entries))
func ParseLogFile(filename string) ([]*LogEntry, error) { func ParseLogFile(filename string) ([]*LogEntry, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
@@ -92,9 +93,9 @@ func ParseLogFile(filename string) ([]*LogEntry, error) {
// ParseLogLine parses a single log line into a LogEntry. // ParseLogLine parses a single log line into a LogEntry.
// //
// This function attempts to parse the line in the following order: // This function attempts to parse the line in the following order:
// 1. JSON format: Lines starting with '{' are parsed as JSON objects // 1. JSON format: Lines starting with '{' are parsed as JSON objects
// 2. Timestamp format: Tab-delimited lines with RFC3339 timestamps // 2. Timestamp format: Tab-delimited lines with RFC3339 timestamps
// 3. Unknown format: Lines that don't match either format are marked as "unknown" level // 3. Unknown format: Lines that don't match either format are marked as "unknown" level
// //
// For JSON logs, all standard fields (level, ts, logger, caller, msg, error, stacktrace) // For JSON logs, all standard fields (level, ts, logger, caller, msg, error, stacktrace)
// are extracted, and any additional fields are stored in ExtraData. // are extracted, and any additional fields are stored in ExtraData.
@@ -106,8 +107,9 @@ func ParseLogFile(filename string) ([]*LogEntry, error) {
// - A LogEntry pointer containing the parsed data, or nil if the line is invalid // - A LogEntry pointer containing the parsed data, or nil if the line is invalid
// //
// Example: // Example:
// entry := ParseLogLine(`{"level":"info","ts":1762989571.547817,"msg":"Starting"}`) //
// fmt.Println(entry.Level, entry.Message) // entry := ParseLogLine(`{"level":"info","ts":1762989571.547817,"msg":"Starting"}`)
// fmt.Println(entry.Level, entry.Message)
func ParseLogLine(line string) *LogEntry { func ParseLogLine(line string) *LogEntry {
entry := &LogEntry{ entry := &LogEntry{
Raw: line, Raw: line,
@@ -152,7 +154,7 @@ func ParseLogLine(line string) *LogEntry {
// Store any extra fields // Store any extra fields
for k, v := range jsonData { for k, v := range jsonData {
if k != "level" && k != "ts" && k != "logger" && k != "caller" && if k != "level" && k != "ts" && k != "logger" && k != "caller" &&
k != "msg" && k != "error" && k != "stacktrace" { k != "msg" && k != "error" && k != "stacktrace" {
entry.ExtraData[k] = v entry.ExtraData[k] = v
} }
} }
@@ -201,12 +203,13 @@ func ParseLogLine(line string) *LogEntry {
// - An error if the file cannot be opened, read, or if the callback returns an error // - An error if the file cannot be opened, read, or if the callback returns an error
// //
// Example: // Example:
// err := StreamLogFile("erupe.log", func(entry *LogEntry) error { //
// if entry.Level == "error" { // err := StreamLogFile("erupe.log", func(entry *LogEntry) error {
// fmt.Println(entry.Message) // if entry.Level == "error" {
// } // fmt.Println(entry.Message)
// return nil // }
// }) // return nil
// })
func StreamLogFile(filename string, callback func(*LogEntry) error) error { func StreamLogFile(filename string, callback func(*LogEntry) error) error {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
@@ -259,9 +262,10 @@ func StreamLogFile(filename string, callback func(*LogEntry) error) error {
// - A formatted string representation of the log entry // - A formatted string representation of the log entry
// //
// Example: // Example:
// formatted := FormatLogEntry(entry, true) //
// fmt.Println(formatted) // formatted := FormatLogEntry(entry, true)
// // Output: 2025-11-12 23:19:31.546 INFO [main] Starting Erupe // fmt.Println(formatted)
// // Output: 2025-11-12 23:19:31.546 INFO [main] Starting Erupe
func FormatLogEntry(entry *LogEntry, colorize bool) string { func FormatLogEntry(entry *LogEntry, colorize bool) string {
var sb strings.Builder var sb strings.Builder

View File

@@ -15,19 +15,19 @@ import (
// message types, and server operations to provide insights into server behavior // message types, and server operations to provide insights into server behavior
// and activity patterns. // and activity patterns.
type LogStats struct { type LogStats struct {
TotalEntries int // Total number of log entries TotalEntries int // Total number of log entries
EntriesByLevel map[string]int // Log level to count EntriesByLevel map[string]int // Log level to count
EntriesByLogger map[string]int // Logger name to count EntriesByLogger map[string]int // Logger name to count
EntriesByDay map[string]int // Date string to count EntriesByDay map[string]int // Date string to count
EntriesByHour map[int]int // Hour (0-23) to count EntriesByHour map[int]int // Hour (0-23) to count
TopMessages map[string]int // Message text to count TopMessages map[string]int // Message text to count
FirstEntry time.Time // Timestamp of first entry FirstEntry time.Time // Timestamp of first entry
LastEntry time.Time // Timestamp of last entry LastEntry time.Time // Timestamp of last entry
SaveOperations int // Count of save operations SaveOperations int // Count of save operations
ObjectBroadcasts int // Count of object broadcasts ObjectBroadcasts int // Count of object broadcasts
StageChanges int // Count of stage changes StageChanges int // Count of stage changes
TerminalLogs int // Count of terminal log entries TerminalLogs int // Count of terminal log entries
UniqueCallers map[string]bool // Set of unique caller locations UniqueCallers map[string]bool // Set of unique caller locations
} }
// runStats implements the stats command for generating comprehensive log statistics. // runStats implements the stats command for generating comprehensive log statistics.
@@ -49,9 +49,10 @@ type LogStats struct {
// - detailed: Show detailed statistics including temporal patterns and top messages // - detailed: Show detailed statistics including temporal patterns and top messages
// //
// Examples: // Examples:
// runStats([]string{}) // Basic statistics //
// runStats([]string{"-detailed"}) // Full statistics with temporal analysis // runStats([]string{}) // Basic statistics
// runStats([]string{"-detailed", "-top", "20"}) // Show top 20 items // runStats([]string{"-detailed"}) // Full statistics with temporal analysis
// runStats([]string{"-detailed", "-top", "20"}) // Show top 20 items
func runStats(args []string) { func runStats(args []string) {
fs := flag.NewFlagSet("stats", flag.ExitOnError) fs := flag.NewFlagSet("stats", flag.ExitOnError)

View File

@@ -15,8 +15,8 @@ import (
// useful for real-time monitoring of server activity. // useful for real-time monitoring of server activity.
// //
// The command operates in two phases: // The command operates in two phases:
// 1. Initial display: Shows the last N matching entries from the file // 1. Initial display: Shows the last N matching entries from the file
// 2. Follow mode: Continuously monitors for new lines and displays them as they appear // 2. Follow mode: Continuously monitors for new lines and displays them as they appear
// //
// Both phases support filtering by log level and colorized output. // Both phases support filtering by log level and colorized output.
// //
@@ -30,10 +30,11 @@ import (
// The follow mode polls the file every 100ms for new content. Use Ctrl+C to stop. // The follow mode polls the file every 100ms for new content. Use Ctrl+C to stop.
// //
// Examples: // Examples:
// runTail([]string{}) // Show last 10 lines and follow //
// runTail([]string{"-n", "50"}) // Show last 50 lines and follow // runTail([]string{}) // Show last 10 lines and follow
// runTail([]string{"-level", "error"}) // Only show errors // runTail([]string{"-n", "50"}) // Show last 50 lines and follow
// runTail([]string{"-follow=false", "-n", "20"}) // Just show last 20 lines, don't follow // runTail([]string{"-level", "error"}) // Only show errors
// runTail([]string{"-follow=false", "-n", "20"}) // Just show last 20 lines, don't follow
func runTail(args []string) { func runTail(args []string) {
fs := flag.NewFlagSet("tail", flag.ExitOnError) fs := flag.NewFlagSet("tail", flag.ExitOnError)