mirror of
https://github.com/wneessen/logranger.git
synced 2024-12-22 18:10:39 +01:00
Add initial server implementation with config loading and listener setup
This commit introduces the creation of the core server and its components: config loading mechanism, listener setup, and error handling. The provided configuration allows the server to run different types of listeners. Additionally, it includes robust log-level settings to facilitate debugging and operational transparency.
This commit is contained in:
parent
4689c24617
commit
cf17cc3419
9 changed files with 346 additions and 0 deletions
11
.golangci.toml
Normal file
11
.golangci.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
## SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
||||
##
|
||||
## SPDX-License-Identifier: MIT
|
||||
|
||||
[run]
|
||||
go = "1.20"
|
||||
tests = true
|
||||
|
||||
[linters]
|
||||
enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder",
|
||||
"errname", "errorlint", "gofmt", "gofumpt"]
|
71
cmd/server/main.go
Normal file
71
cmd/server/main.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/wneessen/logranger"
|
||||
)
|
||||
|
||||
const (
|
||||
// LogErrKey is the keyword used in slog for error messages
|
||||
LogErrKey = "error"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := slog.New(slog.NewJSONHandler(os.Stdout, nil)).With(slog.String("context", "logranger"))
|
||||
cp := "logranger.toml"
|
||||
cpe := os.Getenv("LOGRANGER_CONFIG")
|
||||
if cpe != "" {
|
||||
cp = cpe
|
||||
}
|
||||
|
||||
p := filepath.Dir(cp)
|
||||
f := filepath.Base(cp)
|
||||
c, err := logranger.NewConfig(p, f)
|
||||
if err != nil {
|
||||
l.Error("failed to read/parse config", LogErrKey, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
s := logranger.New(c)
|
||||
go func() {
|
||||
if err = s.Run(); err != nil {
|
||||
l.Error("failed to start logranger: %s", LogErrKey, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
sc := make(chan os.Signal, 1)
|
||||
signal.Notify(sc)
|
||||
for {
|
||||
select {
|
||||
case rc := <-sc:
|
||||
if rc == syscall.SIGKILL || rc == syscall.SIGABRT || rc == syscall.SIGINT || rc == syscall.SIGTERM {
|
||||
l.Warn("received signal. shutting down server", slog.String("signal", rc.String()))
|
||||
//s.Stop()
|
||||
l.Info("server gracefully shut down")
|
||||
os.Exit(0)
|
||||
}
|
||||
if rc == syscall.SIGHUP {
|
||||
l.Info(`received "SIGHUP" signal - reloading rules...`)
|
||||
/*
|
||||
_, nr, err := config.New(config.WithConfFile(*cf), config.WithRulesFile(*rf))
|
||||
if err != nil {
|
||||
s.Log.Errorf("%s - skipping reload", err)
|
||||
continue
|
||||
}
|
||||
if err := nr.CheckRegEx(); err != nil {
|
||||
s.Log.Errorf("ruleset validation failed for new ruleset - skipping reload: %s", err)
|
||||
continue
|
||||
}
|
||||
s.SetRules(nr)
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
config.go
Normal file
50
config.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package logranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/kkyr/fig"
|
||||
)
|
||||
|
||||
// Config holds all the global configuration settings that are parsed by fig
|
||||
type Config struct {
|
||||
// Server holds server specific configuration values
|
||||
Server struct {
|
||||
PIDFile string `fig:"pid_file" default:"/var/run/logranger.pid"`
|
||||
}
|
||||
Listener struct {
|
||||
ListenerUnix struct {
|
||||
Path string `fig:"path" default:"/var/tmp/logranger.sock"`
|
||||
} `fig:"unix"`
|
||||
ListenerTCP struct {
|
||||
Addr string `fig:"addr" default:"0.0.0.0"`
|
||||
Port uint `fig:"port" default:"9099"`
|
||||
} `fig:"tcp"`
|
||||
ListenerTLS struct {
|
||||
Addr string `fig:"addr" default:"0.0.0.0"`
|
||||
Port uint `fig:"port" default:"9099"`
|
||||
CertPath string `fig:"cert_path"`
|
||||
KeyPath string `fig:"key_path"`
|
||||
} `fig:"tls"`
|
||||
Type ListenerType `fig:"type" default:"unix"`
|
||||
} `fig:"listener"`
|
||||
Log struct {
|
||||
Level string `fig:"level" default:"info"`
|
||||
} `fig:"log"`
|
||||
}
|
||||
|
||||
// NewConfig returns a new Config object
|
||||
func NewConfig(p, f string) (*Config, error) {
|
||||
co := Config{}
|
||||
_, err := os.Stat(fmt.Sprintf("%s/%s", p, f))
|
||||
if err != nil {
|
||||
return &co, fmt.Errorf("failed to read config: %w", err)
|
||||
}
|
||||
|
||||
if err := fig.Load(&co, fig.Dirs(p), fig.File(f), fig.UseEnv("logranger")); err != nil {
|
||||
return &co, fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
return &co, nil
|
||||
}
|
6
error.go
Normal file
6
error.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package logranger
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrCertConfigEmpty is returned if a TLS listener is configured but not certificate or key paths are set
|
||||
var ErrCertConfigEmpty = errors.New("certificate and key paths are required for listener type: TLS")
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
|||
module github.com/wneessen/logranger
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/kkyr/fig v0.4.0
|
||||
|
||||
require (
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
23
go.sum
Normal file
23
go.sum
Normal file
|
@ -0,0 +1,23 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/kkyr/fig v0.4.0 h1:4D/g72a8ij1fgRypuIbEoqIT7ukf2URVBtE777/gkbc=
|
||||
github.com/kkyr/fig v0.4.0/go.mod h1:U4Rq/5eUNJ8o5UvOEc9DiXtNf41srOLn2r/BfCyuc58=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
79
listener.go
Normal file
79
listener.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package logranger
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ListenerType is an enumeration wrapper for the different listener types
|
||||
type ListenerType uint
|
||||
|
||||
const (
|
||||
ListenerUnix ListenerType = iota
|
||||
ListenerTCP
|
||||
ListenerTLS
|
||||
)
|
||||
|
||||
func NewListener(c *Config) (net.Listener, error) {
|
||||
var l net.Listener
|
||||
var lerr error
|
||||
switch c.Listener.Type {
|
||||
case ListenerUnix:
|
||||
rua, err := net.ResolveUnixAddr("unix", c.Listener.ListenerUnix.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve UNIX listener socket: %w", err)
|
||||
}
|
||||
l, lerr = net.Listen("unix", rua.String())
|
||||
case ListenerTCP:
|
||||
la := net.JoinHostPort(c.Listener.ListenerTCP.Addr, fmt.Sprintf("%d", c.Listener.ListenerTCP.Port))
|
||||
l, lerr = net.Listen("tcp", la)
|
||||
case ListenerTLS:
|
||||
if c.Listener.ListenerTLS.CertPath == "" || c.Listener.ListenerTLS.KeyPath == "" {
|
||||
return nil, ErrCertConfigEmpty
|
||||
}
|
||||
ce, err := tls.LoadX509KeyPair(c.Listener.ListenerTLS.CertPath, c.Listener.ListenerTLS.KeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load X509 certificate: %w", err)
|
||||
}
|
||||
la := net.JoinHostPort(c.Listener.ListenerTCP.Addr, fmt.Sprintf("%d", c.Listener.ListenerTCP.Port))
|
||||
lc := &tls.Config{Certificates: []tls.Certificate{ce}}
|
||||
l, lerr = tls.Listen("tcp", la, lc)
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to initialize listener: unknown listener type in config")
|
||||
}
|
||||
if lerr != nil {
|
||||
return nil, fmt.Errorf("failed to initalize listener: %w", lerr)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// UnmarshalString satisfies the fig.StringUnmarshaler interface for the ListenerType type
|
||||
func (l *ListenerType) UnmarshalString(v string) error {
|
||||
switch strings.ToLower(v) {
|
||||
case "unix":
|
||||
*l = ListenerUnix
|
||||
case "tcp":
|
||||
*l = ListenerTCP
|
||||
case "tls":
|
||||
*l = ListenerTLS
|
||||
default:
|
||||
return fmt.Errorf("unknown listener type: %s", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String satisfies the fmt.Stringer interface for the ListenerType type
|
||||
func (l ListenerType) String() string {
|
||||
switch l {
|
||||
case ListenerUnix:
|
||||
return "UNIX listener"
|
||||
case ListenerTCP:
|
||||
return "TCP listener"
|
||||
case ListenerTLS:
|
||||
return "TLS listener"
|
||||
default:
|
||||
return "Unknown listener type"
|
||||
}
|
||||
}
|
5
logranger.toml
Normal file
5
logranger.toml
Normal file
|
@ -0,0 +1,5 @@
|
|||
[server]
|
||||
pid_file = "/var/tmp/logranger.pid"
|
||||
|
||||
[listener]
|
||||
type = "tcp"
|
90
server.go
Normal file
90
server.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package logranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// LogErrKey is the keyword used in slog for error messages
|
||||
LogErrKey = "error"
|
||||
)
|
||||
|
||||
// Server is the main server struct
|
||||
type Server struct {
|
||||
// conf is a pointer to the config.Config
|
||||
conf *Config
|
||||
// listener is a listener that satisfies the net.Listener interface
|
||||
listener net.Listener
|
||||
// log is a pointer to the slog.Logger
|
||||
log *slog.Logger
|
||||
|
||||
// wg is a sync.WaitGroup
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New returns a Server struct
|
||||
func New(c *Config) *Server {
|
||||
s := &Server{
|
||||
conf: c,
|
||||
}
|
||||
s.setLogLevel()
|
||||
return s
|
||||
}
|
||||
|
||||
// Run starts the logranger Server with a new Listener based on the config settings
|
||||
func (s *Server) Run() error {
|
||||
l, err := NewListener(s.conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.RunWithListener(l)
|
||||
}
|
||||
|
||||
// RunWithListener starts the logranger Server using a provided net.Listener
|
||||
func (s *Server) RunWithListener(l net.Listener) error {
|
||||
s.listener = l
|
||||
|
||||
// Create PID file
|
||||
pf, err := os.Create(s.conf.Server.PIDFile)
|
||||
if err != nil {
|
||||
s.log.Error("failed to create PID file", LogErrKey, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
_, err = pf.WriteString(fmt.Sprintf("%d", os.Getpid()))
|
||||
if err != nil {
|
||||
s.log.Error("failed to write PID to PID file", LogErrKey, err)
|
||||
_ = pf.Close()
|
||||
}
|
||||
if err = pf.Close(); err != nil {
|
||||
s.log.Error("failed to close PID file", LogErrKey, err)
|
||||
}
|
||||
|
||||
// Listen for connections
|
||||
s.wg.Add(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setLogLevel assigns a new slog.Logger instance to the Server based on the configured log level
|
||||
func (s *Server) setLogLevel() {
|
||||
lo := slog.HandlerOptions{}
|
||||
switch strings.ToLower(s.conf.Log.Level) {
|
||||
case "debug":
|
||||
lo.Level = slog.LevelDebug
|
||||
case "info":
|
||||
lo.Level = slog.LevelInfo
|
||||
case "warn":
|
||||
lo.Level = slog.LevelWarn
|
||||
case "error":
|
||||
lo.Level = slog.LevelError
|
||||
default:
|
||||
lo.Level = slog.LevelInfo
|
||||
}
|
||||
lh := slog.NewJSONHandler(os.Stdout, &lo)
|
||||
s.log = slog.New(lh).With(slog.String("context", "logranger"))
|
||||
}
|
Loading…
Reference in a new issue