Skip to content

Commit b1fe2e8

Browse files
authored
Merge pull request #42 from Unity-Technologies/feat/multi-log-targets-support
feat(simple-game-server-go): adding multiple log targets support for simple game server go
2 parents e489909 + cc0f03f commit b1fe2e8

File tree

9 files changed

+384
-77
lines changed

9 files changed

+384
-77
lines changed

.github/workflows/simple-game-server-go.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ jobs:
2020
with:
2121
go-version: '1.20'
2222
- name: Lint
23-
uses: golangci/golangci-lint-action@v2
23+
uses: golangci/golangci-lint-action@v8
2424
with:
25-
version: v1.52.2
2625
working-directory: './simple-game-server-go'
2726
skip-go-installation: true
27+
only-new-issues: true
2828
- name: Build
2929
uses: goreleaser/goreleaser-action@v2
3030
if: startsWith(github.ref, 'refs/tags/') == false

go.work.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
44
github.com/centrifugal/centrifuge v0.21.1/go.mod h1:uAFqaz85mlIw995eZblWzfuMj1Ok4jWNGZUMYbpQfbE=
55
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
66
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
7+
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
78
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
89
github.com/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE=
910
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,54 @@
1+
version: "2"
12
run:
23
concurrency: 4
3-
deadline: 5m
4-
5-
linters-settings:
6-
cyclop:
7-
max-complexity: 20
8-
gocyclo:
9-
min-complexity: 20
10-
114
linters:
12-
enable-all: true
5+
default: all
136
disable:
7+
- depguard
148
- exhaustive
159
- exhaustruct
16-
- exhaustivestruct
10+
- forcetypeassert
1711
- funlen
1812
- gochecknoglobals
13+
- godox
1914
- gomoddirectives
20-
- interfacer
21-
- maligned
22-
- gomnd
15+
- ireturn
16+
- lll
17+
- mnd
2318
- nestif
19+
- nlreturn
2420
- paralleltest
25-
- scopelint
21+
- tagliatelle
2622
- testpackage
2723
- tparallel
24+
- varnamelen
2825
- wrapcheck
2926
- wsl
30-
- godox
31-
- forcetypeassert
32-
- nlreturn
33-
- tagliatelle
34-
- varnamelen
35-
- ireturn
36-
- varcheck
37-
- deadcode
38-
- golint
39-
- ifshort
40-
- structcheck
41-
- nosnakecase
42-
- lll
27+
settings:
28+
cyclop:
29+
max-complexity: 20
30+
gocyclo:
31+
min-complexity: 20
32+
exclusions:
33+
generated: lax
34+
presets:
35+
- comments
36+
- common-false-positives
37+
- legacy
38+
- std-error-handling
39+
paths:
40+
- builtin$
41+
- examples$
42+
- third_party$
43+
formatters:
44+
enable:
45+
- gci
46+
- gofmt
47+
- gofumpt
48+
- goimports
49+
exclusions:
50+
generated: lax
51+
paths:
52+
- builtin$
53+
- examples$
54+
- third_party$

simple-game-server-go/go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
github.com/FZambia/eagle v0.0.2 h1:35qHDuXSQevZ4w9A51k4wU7OE/tPHTEWXoywA93hvkY=
22
github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8=
3-
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.3.1 h1:piV2hAtoc5ke3ywkIvUs82J+CphjWXoN3219isDmY00=
4-
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.3.1/go.mod h1:WgDwSafd4alCs+HdK0z+7htBVZIe+LUrLQgM738WDd0=
5-
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.5.0 h1:nH2XUCCx1BAV6hXhjSaIA+rOrzX2CRPz4/Yx/sZ26Do=
6-
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.5.0/go.mod h1:WgDwSafd4alCs+HdK0z+7htBVZIe+LUrLQgM738WDd0=
73
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.5.4 h1:2KQYKCx44tEurLU5TlhmPhvKyvXo5QyBiTEfFqiC25g=
84
github.com/Unity-Technologies/unity-gaming-services-go-sdk v0.5.4/go.mod h1:WgDwSafd4alCs+HdK0z+7htBVZIe+LUrLQgM738WDd0=
95
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

simple-game-server-go/internal/game/allocated.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ func (g *Game) allocated(allocationID string) {
3232
maxPlayers = defaultMaxPlayers
3333
}
3434

35-
g.Server.SetMaxPlayers(int32(maxPlayers))
36-
g.Server.SetServerName(fmt.Sprintf("simple-game-server-go - %s", c.AllocatedUUID))
37-
g.Server.SetGameType(c.Extra["gameType"])
38-
g.Server.SetGameMap(c.Extra["map"])
35+
g.SetMaxPlayers(int32(maxPlayers))
36+
g.SetServerName("simple-game-server-go - " + c.AllocatedUUID)
37+
g.SetGameType(c.Extra["gameType"])
38+
g.SetGameMap(c.Extra["map"])
3939

4040
// Set a random metric, if using SQP.
4141
if c.QueryType == server.QueryProtocolSQP {
@@ -95,7 +95,7 @@ func (g *Game) acceptClient(server *net.TCPListener) (*net.TCPConn, error) {
9595
}
9696

9797
g.clients.Store(client.RemoteAddr(), client)
98-
currentPlayers := g.Server.PlayerJoined()
98+
currentPlayers := g.PlayerJoined()
9999
g.logger.WithFields(logrus.Fields{
100100
"client_ip": client.RemoteAddr().String(),
101101
"current_players": currentPlayers,
@@ -108,7 +108,7 @@ func (g *Game) acceptClient(server *net.TCPListener) (*net.TCPConn, error) {
108108
func (g *Game) handleClient(client *net.TCPConn) {
109109
defer func() {
110110
g.clients.Delete(client.RemoteAddr())
111-
currentPlayers := g.Server.PlayerLeft()
111+
currentPlayers := g.PlayerLeft()
112112
g.logger.WithFields(logrus.Fields{
113113
"client_ip": client.RemoteAddr().String(),
114114
"current_players": currentPlayers,

simple-game-server-go/internal/game/deallocated.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func (g *Game) deallocated() {
1919

2020
// disconnectAllClients disconnects all remaining clients connected to the game server.
2121
func (g *Game) disconnectAllClients() {
22-
g.clients.Range(func(key interface{}, value interface{}) bool {
22+
g.clients.Range(func(_, value any) bool {
2323
client, ok := value.(*net.TCPConn)
2424
if !ok {
2525
return true

simple-game-server-go/internal/game/game.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (g *Game) Start() error {
7777
g.logger.Info("stopped")
7878
}()
7979

80-
return g.Server.WaitUntilTerminated()
80+
return g.WaitUntilTerminated()
8181
}
8282

8383
// processEvents handles processing events for the operation of the

simple-game-server-go/main.go

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"flag"
5+
"io"
56
"os"
67
"path/filepath"
78
"runtime/debug"
9+
"strings"
810

911
"github.com/Unity-Technologies/multiplay-examples/simple-game-server-go/internal/game"
1012
"github.com/sirupsen/logrus"
@@ -15,29 +17,89 @@ func parseFlags(args []string) (string, string, string, error) {
1517
dir, _ := os.UserHomeDir()
1618
f := flag.NewFlagSet("simple-game-server-go", flag.ContinueOnError)
1719

18-
var log, logFile, tracebackLevel string
19-
f.StringVar(&log, "log", filepath.Join(dir, "logs"), "path to the log directory to write to")
20+
var logTargets, logFile, tracebackLevel string
21+
f.StringVar(&logTargets, "log", "stdout,"+filepath.Join(dir, "logs"), "comma-separated log targets: 'stdout', file path, or directory")
2022
f.StringVar(&logFile, "logFile", "", "path to the log file to write to")
21-
f.StringVar(
22-
&tracebackLevel,
23-
"tracebackLevel",
24-
"",
25-
"the amount of detail printed by the runtime prints before exiting due to an unrecovered panic",
26-
)
23+
f.StringVar(&tracebackLevel, "tracebackLevel", "none", "the amount of detail printed by the runtime prints before exiting due to an unrecovered panic")
2724

2825
// Flags which are not used, but must be present to satisfy the default parameters in the Unity Dashboard.
2926
var port, queryPort uint
3027
f.UintVar(&port, "port", 8000, "port for the game server to bind to")
3128
f.UintVar(&queryPort, "queryport", 8001, "port for the query endpoint to bind to")
3229

33-
return log, logFile, tracebackLevel, f.Parse(args)
30+
return logTargets, logFile, tracebackLevel, f.Parse(args)
31+
}
32+
33+
// logWritersFromTargets creates a multi-writer from the specified log targets and log file.
34+
// If no valid targets are provided, it defaults to writing to stdout.
35+
func logWritersFromTargets(logTargets string, logFile string, logger *logrus.Logger) io.Writer {
36+
targets := make([]io.Writer, 0)
37+
for _, t := range splitAndTrim(logTargets) {
38+
switch t {
39+
case "stdout":
40+
targets = append(targets, os.Stdout)
41+
case "stderr":
42+
targets = append(targets, os.Stderr)
43+
default:
44+
// If it's a directory, use server.log inside it
45+
info, err := os.Stat(t)
46+
if err == nil && info.IsDir() {
47+
t = filepath.Join(t, "server.log")
48+
}
49+
f, err := os.OpenFile(t, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
50+
if err != nil {
51+
logger.WithError(err).Warningf("could not open log target %s for writing", t)
52+
continue
53+
}
54+
targets = append(targets, f)
55+
}
56+
}
57+
// logFile takes precedence
58+
if logFile != "" {
59+
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
60+
if err == nil {
61+
targets = append(targets, f)
62+
} else {
63+
logger.WithError(err).Warning("could not open log file for writing")
64+
}
65+
}
66+
if len(targets) == 0 {
67+
return os.Stdout
68+
}
69+
return io.MultiWriter(targets...)
70+
}
71+
72+
// splitAndTrim splits a string by the OS-specific path list separator and trims each part.
73+
func splitAndTrim(s string) []string {
74+
parts := make([]string, 0)
75+
for _, p := range filepath.SplitList(s) {
76+
for _, t := range splitComma(p) {
77+
trimmed := filepath.Clean(t)
78+
if trimmed != "" && trimmed != "." {
79+
parts = append(parts, trimmed)
80+
}
81+
}
82+
}
83+
return parts
84+
}
85+
86+
// splitComma splits a string by commas and trims each part, returning a slice of non-empty strings.
87+
func splitComma(s string) []string {
88+
res := make([]string, 0)
89+
for _, t := range strings.Split(s, ",") {
90+
trimmed := strings.TrimSpace(t)
91+
if trimmed != "" && trimmed != "." {
92+
res = append(res, trimmed)
93+
}
94+
}
95+
return res
3496
}
3597

3698
func main() {
3799
logger := logrus.New()
38100
logger.SetFormatter(&logrus.JSONFormatter{})
39101

40-
log, logFile, tracebackLevel, err := parseFlags(os.Args[1:])
102+
logTargets, logFile, tracebackLevel, err := parseFlags(os.Args[1:])
41103
if err != nil {
42104
logger.WithError(err).Fatal("error parsing flags")
43105
}
@@ -47,20 +109,7 @@ func main() {
47109
debug.SetTraceback(tracebackLevel)
48110
}
49111

50-
// Let -logFile take precedence over -log
51-
if logFile == "" && log != "" {
52-
logFile = filepath.Join(log, "server.log")
53-
}
54-
55-
if logFile != "" {
56-
f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
57-
if err == nil {
58-
defer f.Close()
59-
logger.Out = f
60-
} else {
61-
logger.WithError(err).Warning("could not open log file for writing")
62-
}
63-
}
112+
logger.Out = logWritersFromTargets(logTargets, logFile, logger)
64113

65114
g, err := game.New(logger)
66115
if err != nil {

0 commit comments

Comments
 (0)