mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 10:32:41 +00:00
Map generator -verbose and -performance flags (#2721)
Resolves #2718 ## Description: Adds go-style error log levels, with an additional ALL log level. - WARN/ERROR - Only success output - INFO - Existing output - DEBUG - New output - ALL - New output (includes the logs from when removal/performance is enabled) In addition - Add `-verbose` (`-v`), `-log-level`, `-log-removal`, and `-log-performance` flags to map generator - No changes to default behavior of `go run .` without the new flags - excludes test maps from performance warnings (test maps already skip the removal steps) - updates readme with the different flags and how they impact the logger Default run (matches existing) `go run . >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365745/output.txt) Default run w/ `-verbose` (log level DEBUG) `go run . -v >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365812/output.txt) Default run w/ `-log-performance` `go run . -log-performance >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365971/output.txt) Run of just africa w/ all new logging enabled `go run . -maps=africa -log-level=all >> output.txt 2>&1` [output.txt](https://github.com/user-attachments/files/24365724/output.txt) ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: tidwell --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
// This is the custom logger providing the multi-level and flag-based logging for
|
||||
// the map-generator. It uses slog.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type LogFlags struct {
|
||||
logLevel string // The log-level (most -> least wordy): ALL, DEBUG, INFO (default), WARN, ERROR
|
||||
verbose bool // sets log-level=DEBUG
|
||||
performance bool // opts-in to performance checks and sets log-level=DEBUG
|
||||
removal bool // opts-in to island/lake removal logging and sets log-level=DEBUG
|
||||
}
|
||||
|
||||
// LevelAll is a custom log Level that outputs all messages, regardless of other passed flags
|
||||
const LevelAll = slog.Level(-8)
|
||||
|
||||
// PerformanceLogTag is a slog attribute used to tag performance-related log messages.
|
||||
var PerformanceLogTag = slog.String("tag", "performance")
|
||||
|
||||
// RemovalLogTag is a slog attribute used to tag land/water removal-related log messages.
|
||||
var RemovalLogTag = slog.String("tag", "removal")
|
||||
|
||||
// DetermineLogLevel determines the log level based on the LogFlags
|
||||
// It prioritizes the log level flag over the default, and switches to debug if performance or removal flags are set.
|
||||
func DetermineLogLevel(
|
||||
logFlags LogFlags) slog.Level {
|
||||
|
||||
var level = slog.LevelInfo
|
||||
if logFlags.verbose {
|
||||
level = slog.LevelDebug
|
||||
}
|
||||
|
||||
// switch to debug if any of the optional flags is enabled
|
||||
if logFlags.performance || logFlags.removal {
|
||||
level = slog.LevelDebug
|
||||
}
|
||||
|
||||
// parse the log-level input string to the slog.Level type
|
||||
if logFlags.logLevel != "" {
|
||||
switch strings.ToLower(logFlags.logLevel) {
|
||||
case "all":
|
||||
level = LevelAll
|
||||
case "debug":
|
||||
level = slog.LevelDebug
|
||||
case "info":
|
||||
level = slog.LevelInfo
|
||||
case "warn":
|
||||
level = slog.LevelWarn
|
||||
case "error":
|
||||
level = slog.LevelError
|
||||
default:
|
||||
fmt.Printf("invalid log level: %s, defaulting to info\n", logFlags.logLevel)
|
||||
level = slog.LevelInfo
|
||||
}
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
// GeneratorLogger is a custom slog.Handler that outputs logs based on log level and additional LogFlags.
|
||||
type GeneratorLogger struct {
|
||||
opts slog.HandlerOptions
|
||||
w io.Writer
|
||||
mu *sync.Mutex
|
||||
attrs []slog.Attr
|
||||
prefix string
|
||||
flags LogFlags
|
||||
}
|
||||
|
||||
// NewGeneratorLogger creates a new GeneratorLogger.
|
||||
// It initializes a handler with specific output, options, and flags
|
||||
func NewGeneratorLogger(
|
||||
out io.Writer,
|
||||
opts *slog.HandlerOptions,
|
||||
flags LogFlags) *GeneratorLogger {
|
||||
|
||||
h := &GeneratorLogger{
|
||||
w: out,
|
||||
mu: &sync.Mutex{},
|
||||
flags: flags,
|
||||
}
|
||||
if opts != nil {
|
||||
h.opts = *opts
|
||||
}
|
||||
if h.opts.Level == nil {
|
||||
h.opts.Level = slog.LevelInfo
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// Enabled checks if a given log level is enabled for this handler.
|
||||
func (h *GeneratorLogger) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return level >= h.opts.Level.Level()
|
||||
}
|
||||
|
||||
// Handle processes a log record.
|
||||
// It decides whether to output each record based on log level, flags, and if the map is a test map
|
||||
// On output, it formats the log message with any extra formatting
|
||||
func (h *GeneratorLogger) Handle(_ context.Context, r slog.Record) error {
|
||||
isPerformanceLog := false
|
||||
isRemovalLog := false
|
||||
isTestMap := false
|
||||
|
||||
var mapName string
|
||||
|
||||
findAttrs := func(a slog.Attr) {
|
||||
if a.Equal(PerformanceLogTag) {
|
||||
isPerformanceLog = true
|
||||
}
|
||||
if a.Equal(RemovalLogTag) {
|
||||
isRemovalLog = true
|
||||
}
|
||||
if a.Key == "map" {
|
||||
mapName = a.Value.String()
|
||||
}
|
||||
if a.Key == "isTest" {
|
||||
isTestMap = a.Value.Bool()
|
||||
}
|
||||
}
|
||||
|
||||
// Check record attributes for performance tag and map name
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
findAttrs(a)
|
||||
return true
|
||||
})
|
||||
|
||||
// Check handler's own attributes for performance tag and map name
|
||||
for _, a := range h.attrs {
|
||||
findAttrs(a)
|
||||
}
|
||||
|
||||
// Don't log messages if the flags are not set
|
||||
// If the log level is set to LevelAll, disregard
|
||||
if h.opts.Level != LevelAll && isPerformanceLog && !h.flags.performance {
|
||||
return nil
|
||||
}
|
||||
if h.opts.Level != LevelAll && (isRemovalLog && !h.flags.removal) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dont log performance messages for test maps
|
||||
if isPerformanceLog && isTestMap {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
// Add map name as a prefix in log Level DEBUG and ALL
|
||||
if (h.opts.Level == slog.LevelDebug || h.opts.Level == LevelAll) && mapName != "" {
|
||||
mapName = strings.Trim(mapName, `"`)
|
||||
fmt.Fprintf(buf, "[%s] ", mapName)
|
||||
}
|
||||
|
||||
// Add prefix for performance messages
|
||||
if isPerformanceLog {
|
||||
fmt.Fprintf(buf, "[PERF] ")
|
||||
}
|
||||
|
||||
if h.prefix != "" {
|
||||
fmt.Fprintf(buf, "%s ", h.prefix)
|
||||
}
|
||||
|
||||
fmt.Fprintln(buf, r.Message)
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
_, err := h.w.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// WithAttrs returns a new handler with the given attributes added.
|
||||
func (h *GeneratorLogger) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newHandler := *h
|
||||
newHandler.attrs = append(newHandler.attrs, attrs...)
|
||||
return &newHandler
|
||||
}
|
||||
|
||||
// WithGroup returns a new handler with the given group name.
|
||||
// The group name is added as a prefix to subsequent log messages.
|
||||
func (h *GeneratorLogger) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
newHandler := *h
|
||||
if newHandler.prefix != "" {
|
||||
newHandler.prefix += "."
|
||||
}
|
||||
newHandler.prefix += name
|
||||
return &newHandler
|
||||
}
|
||||
|
||||
type loggerKey struct{}
|
||||
|
||||
// LoggerFromContext retrieves the logger from the context.
|
||||
// If no logger is found, it returns the default logger.
|
||||
func LoggerFromContext(ctx context.Context) *slog.Logger {
|
||||
if logger, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok {
|
||||
return logger
|
||||
}
|
||||
return slog.Default()
|
||||
}
|
||||
|
||||
// ContextWithLogger returns a new context with the provided logger.
|
||||
func ContextWithLogger(ctx context.Context, logger *slog.Logger) context.Context {
|
||||
return context.WithValue(ctx, loggerKey{}, logger)
|
||||
}
|
||||
Reference in New Issue
Block a user