diff --git a/go.mod b/go.mod index fae1cbd..ade828b 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/jetkvm/kvm -go 1.21.0 +go 1.23.0 -toolchain go1.21.1 +toolchain go1.24.1 require ( github.com/Masterminds/semver/v3 v3.3.0 @@ -30,6 +30,8 @@ require ( replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b +replace github.com/pion/logging v0.2.2 => ./internal/pion/logging + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect @@ -49,6 +51,7 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -70,13 +73,14 @@ require ( github.com/pion/turn/v4 v4.0.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/wlynxg/anet v0.0.5 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1563130..f577f01 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,7 @@ github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9F github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= @@ -47,6 +48,7 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -78,6 +80,11 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= @@ -125,6 +132,7 @@ github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/webrtc/v4 v4.0.0 h1:x8ec7uJQPP3D1iI8ojPAiTOylPI7Fa7QgqZrhpLyqZ8= github.com/pion/webrtc/v4 v4.0.0/go.mod h1:SfNn8CcFxR6OUVjLXVslAQ3a3994JhyE3Hw1jAuqEto= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= @@ -139,6 +147,9 @@ github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIw github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= 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= @@ -175,12 +186,16 @@ golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= diff --git a/internal/pion/logging/go.mod b/internal/pion/logging/go.mod new file mode 100644 index 0000000..71a61cf --- /dev/null +++ b/internal/pion/logging/go.mod @@ -0,0 +1,15 @@ +module github.com/pion/logging + +go 1.20 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/zerolog v1.34.0 // indirect + golang.org/x/sys v0.12.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/internal/pion/logging/go.sum b/internal/pion/logging/go.sum new file mode 100644 index 0000000..2a3cfd0 --- /dev/null +++ b/internal/pion/logging/go.sum @@ -0,0 +1,25 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/pion/logging/logger.go b/internal/pion/logging/logger.go new file mode 100644 index 0000000..f38ed1e --- /dev/null +++ b/internal/pion/logging/logger.go @@ -0,0 +1,262 @@ +// This is an internal package to patch the Pion logging library to use the +// zerolog instead of the log package. +package logging + +import ( + "fmt" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/rs/zerolog" +) + +var defaultOutput io.Writer = zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: time.RFC3339, + PartsOrder: []string{"time", "level", "scope", "message"}, + FieldsExclude: []string{"scope"}, + FormatPartValueByName: func(value interface{}, name string) string { + val := fmt.Sprintf("%s", value) + if name == "scope" { + if strings.HasPrefix(val, "jetkvm/") { + return val[7:] + } else { + // scope without prefix, we assume it's from the pion library + return fmt.Sprintf("pion/%s", val) + } + } + return val + }, +} + +func SetDefaultOutput(output io.Writer) { + defaultOutput = output +} + +// Use this abstraction to ensure thread-safe access to the logger's io.Writer. +// (which could change at runtime). +type loggerWriter struct { + sync.RWMutex + output io.Writer +} + +func (lw *loggerWriter) SetOutput(output io.Writer) { + lw.Lock() + defer lw.Unlock() + lw.output = output +} + +func (lw *loggerWriter) Write(data []byte) (int, error) { + lw.RLock() + defer lw.RUnlock() + + return lw.output.Write(data) +} + +type zerologEventLogger func() *zerolog.Event + +// DefaultLeveledLogger encapsulates functionality for providing logging at. +// user-defined levels. +type DefaultLeveledLogger struct { + level LogLevel + writer *zerolog.Logger + trace zerologEventLogger + debug zerologEventLogger + info zerologEventLogger + warn zerologEventLogger + err zerologEventLogger +} + +func (ll *DefaultLeveledLogger) GetLogger() *zerolog.Logger { + return ll.writer +} + +// WithTraceLogger is a chainable configuration function which sets the +// Trace-level logger. +func (ll *DefaultLeveledLogger) WithTraceLogger(log zerologEventLogger) *DefaultLeveledLogger { + ll.trace = log + + return ll +} + +// WithDebugLogger is a chainable configuration function which sets the +// Debug-level logger. +func (ll *DefaultLeveledLogger) WithDebugLogger(log zerologEventLogger) *DefaultLeveledLogger { + ll.debug = log + + return ll +} + +// WithInfoLogger is a chainable configuration function which sets the +// Info-level logger. +func (ll *DefaultLeveledLogger) WithInfoLogger(log zerologEventLogger) *DefaultLeveledLogger { + ll.info = log + + return ll +} + +// WithWarnLogger is a chainable configuration function which sets the +// Warn-level logger. +func (ll *DefaultLeveledLogger) WithWarnLogger(log zerologEventLogger) *DefaultLeveledLogger { + ll.warn = log + + return ll +} + +// WithErrorLogger is a chainable configuration function which sets the +// Error-level logger. +func (ll *DefaultLeveledLogger) WithErrorLogger(log zerologEventLogger) *DefaultLeveledLogger { + ll.err = log + + return ll +} + +// WithOutput is a chainable configuration function which sets the logger's +// logging output to the supplied io.Writer. +func (ll *DefaultLeveledLogger) WithOutput(output io.Writer) *DefaultLeveledLogger { + ll.writer.Output(output) + + return ll +} + +// SetLevel sets the logger's logging level. +func (ll *DefaultLeveledLogger) SetLevel(newLevel LogLevel) { + ll.level.Set(newLevel) +} + +// Trace emits the preformatted message if the logger is at or below LogLevelTrace. +func (ll *DefaultLeveledLogger) Trace(msg string) { + ll.trace().Msgf(msg) +} + +// Tracef formats and emits a message if the logger is at or below LogLevelTrace. +func (ll *DefaultLeveledLogger) Tracef(format string, args ...interface{}) { + ll.trace().Msgf(format, args...) +} + +// Debug emits the preformatted message if the logger is at or below LogLevelDebug. +func (ll *DefaultLeveledLogger) Debug(msg string) { + ll.debug().Msgf(msg) +} + +// Debugf formats and emits a message if the logger is at or below LogLevelDebug. +func (ll *DefaultLeveledLogger) Debugf(format string, args ...interface{}) { + ll.debug().Msgf(format, args...) +} + +// Info emits the preformatted message if the logger is at or below LogLevelInfo. +func (ll *DefaultLeveledLogger) Info(msg string) { + ll.info().Msgf(msg) +} + +// Infof formats and emits a message if the logger is at or below LogLevelInfo. +func (ll *DefaultLeveledLogger) Infof(format string, args ...interface{}) { + ll.info().Msgf(format, args...) +} + +// Warn emits the preformatted message if the logger is at or below LogLevelWarn. +func (ll *DefaultLeveledLogger) Warn(msg string) { + ll.warn().Msgf(msg) +} + +// Warnf formats and emits a message if the logger is at or below LogLevelWarn. +func (ll *DefaultLeveledLogger) Warnf(format string, args ...interface{}) { + ll.warn().Msgf(format, args...) +} + +// Error emits the preformatted message if the logger is at or below LogLevelError. +func (ll *DefaultLeveledLogger) Error(msg string) { + ll.err().Msgf(msg) +} + +// Errorf formats and emits a message if the logger is at or below LogLevelError. +func (ll *DefaultLeveledLogger) Errorf(format string, args ...interface{}) { + ll.err().Msgf(format, args...) +} + +// NewDefaultLeveledLoggerForScope returns a configured LeveledLogger. +func NewDefaultLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *DefaultLeveledLogger { + if writer == nil { + writer = defaultOutput + } + zerologWriter := zerolog.New(writer).With().Timestamp().Str("scope", scope).Logger() + logger := &DefaultLeveledLogger{ + writer: &zerologWriter, + level: level, + } + + return logger. + WithTraceLogger(zerologWriter.Trace). + WithDebugLogger(zerologWriter.Debug). + WithInfoLogger(zerologWriter.Info). + WithWarnLogger(zerologWriter.Warn). + WithErrorLogger(zerologWriter.Error) +} + +// DefaultLoggerFactory define levels by scopes and creates new DefaultLeveledLogger. +type DefaultLoggerFactory struct { + Writer io.Writer + DefaultLogLevel LogLevel + ScopeLevels map[string]LogLevel +} + +// NewDefaultLoggerFactory creates a new DefaultLoggerFactory. +func NewDefaultLoggerFactory() *DefaultLoggerFactory { + factory := DefaultLoggerFactory{} + factory.DefaultLogLevel = LogLevelError + factory.ScopeLevels = make(map[string]LogLevel) + factory.Writer = defaultOutput + + logLevels := map[string]LogLevel{ + "DISABLE": LogLevelDisabled, + "ERROR": LogLevelError, + "WARN": LogLevelWarn, + "INFO": LogLevelInfo, + "DEBUG": LogLevelDebug, + "TRACE": LogLevelTrace, + } + + for name, level := range logLevels { + env := os.Getenv(fmt.Sprintf("PION_LOG_%s", name)) + + if env == "" { + env = os.Getenv(fmt.Sprintf("PIONS_LOG_%s", name)) + } + + if env == "" { + continue + } + + if strings.ToLower(env) == "all" { + if factory.DefaultLogLevel < level { + factory.DefaultLogLevel = level + } + + continue + } + + scopes := strings.Split(strings.ToLower(env), ",") + for _, scope := range scopes { + factory.ScopeLevels[scope] = level + } + } + + return &factory +} + +// NewLogger returns a configured LeveledLogger for the given, argsscope. +func (f *DefaultLoggerFactory) NewLogger(scope string) LeveledLogger { + logLevel := f.DefaultLogLevel + if f.ScopeLevels != nil { + scopeLevel, found := f.ScopeLevels[scope] + + if found { + logLevel = scopeLevel + } + } + + return NewDefaultLeveledLoggerForScope(scope, logLevel, f.Writer) +} diff --git a/internal/pion/logging/logging_test.go b/internal/pion/logging/logging_test.go new file mode 100644 index 0000000..72faea0 --- /dev/null +++ b/internal/pion/logging/logging_test.go @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package logging_test + +import ( + "bytes" + "os" + "strings" + "testing" + + "github.com/pion/logging" + "github.com/stretchr/testify/assert" +) + +func testNoDebugLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + logger.Debug("this shouldn't be logged") + assert.GreaterOrEqual(t, 0, outBuf.Len(), "Debug was logged when it shouldn't have been") + + logger.Debugf("this shouldn't be logged") + assert.GreaterOrEqual(t, 0, outBuf.Len(), "Debug was logged when it shouldn't have been") +} + +func testDebugLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + dbgMsg := "this is a debug message" + logger.Debug(dbgMsg) + assert.Truef(t, strings.Contains(outBuf.String(), dbgMsg), + "Expected to find %q in %q, but didn't", dbgMsg, outBuf.String()) + assert.Truef(t, strings.Contains(outBuf.String(), dbgMsg), + "Expected to find %q in %q, but didn't", dbgMsg, outBuf.String()) + + logger.Debugf(dbgMsg) // nolint: govet + assert.Truef(t, strings.Contains(outBuf.String(), dbgMsg), + "Expected to find %q in %q, but didn't", dbgMsg, outBuf.String()) +} + +func testWarnLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + warnMsg := "this is a warning message" + logger.Warn(warnMsg) + assert.Truef(t, strings.Contains(outBuf.String(), warnMsg), + "Expected to find %q in %q, but didn't", warnMsg, outBuf.String()) + + logger.Warnf(warnMsg) // nolint: govet + assert.Truef(t, strings.Contains(outBuf.String(), warnMsg), + "Expected to find %q in %q, but didn't", warnMsg, outBuf.String()) +} + +func testErrorLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + errMsg := "this is an error message" + logger.Error(errMsg) + assert.Truef(t, strings.Contains(outBuf.String(), errMsg), + "Expected to find %q in %q but didn't", errMsg, outBuf.String()) + + logger.Errorf(errMsg) // nolint: govet + assert.Truef(t, strings.Contains(outBuf.String(), errMsg), + "Expected to find %q in %q but didn't", errMsg, outBuf.String()) +} + +func testTraceLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + traceMsg := "trace message" + logger.Trace(traceMsg) + assert.Truef(t, strings.Contains(outBuf.String(), traceMsg), + "Expected to find %q in %q but didn't", traceMsg, outBuf.String()) + + logger.Tracef(traceMsg) // nolint: govet + assert.Truef(t, strings.Contains(outBuf.String(), traceMsg), + "Expected to find %q in %q but didn't", traceMsg, outBuf.String()) +} + +func testInfoLevel(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + var outBuf bytes.Buffer + logger.WithOutput(&outBuf) + + infoMsg := "info message" + logger.Info(infoMsg) + assert.Truef(t, strings.Contains(outBuf.String(), infoMsg), + "Expected to find %q in %q but didn't", infoMsg, outBuf.String()) + + logger.Infof(infoMsg) // nolint: govet + assert.Truef(t, strings.Contains(outBuf.String(), infoMsg), + "Expected to find %q in %q but didn't", infoMsg, outBuf.String()) +} + +func testAllLevels(t *testing.T, logger *logging.DefaultLeveledLogger) { + t.Helper() + + testDebugLevel(t, logger) + testWarnLevel(t, logger) + testErrorLevel(t, logger) + testTraceLevel(t, logger) + testInfoLevel(t, logger) +} + +func TestDefaultLoggerFactory(t *testing.T) { + factory := logging.DefaultLoggerFactory{ + Writer: os.Stderr, + DefaultLogLevel: logging.LogLevelWarn, + ScopeLevels: map[string]logging.LogLevel{ + "foo": logging.LogLevelDebug, + }, + } + + logger := factory.NewLogger("baz") + bazLogger, ok := logger.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger type") + + testNoDebugLevel(t, bazLogger) + testWarnLevel(t, bazLogger) + + logger = factory.NewLogger("foo") + fooLogger, ok := logger.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger type") + + testDebugLevel(t, fooLogger) +} + +func TestDefaultLogger(t *testing.T) { + logger := logging. + NewDefaultLeveledLoggerForScope("test1", logging.LogLevelWarn, os.Stderr) + + testNoDebugLevel(t, logger) + testWarnLevel(t, logger) + testErrorLevel(t, logger) +} + +func TestNewDefaultLoggerFactory(t *testing.T) { + factory := logging.NewDefaultLoggerFactory() + + disabled := factory.NewLogger("DISABLE") + errorLevel := factory.NewLogger("ERROR") + warnLevel := factory.NewLogger("WARN") + infoLevel := factory.NewLogger("INFO") + debugLevel := factory.NewLogger("DEBUG") + traceLevel := factory.NewLogger("TRACE") + + disabledLogger, ok := disabled.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing disabled logger") + + errorLogger, ok := errorLevel.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing error logger") + + warnLogger, ok := warnLevel.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing warn logger") + + infoLogger, ok := infoLevel.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing info logger") + + debugLogger, ok := debugLevel.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing debug logger") + + traceLogger, ok := traceLevel.(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Missing trace logger") + + testNoDebugLevel(t, disabledLogger) + testNoDebugLevel(t, errorLogger) + testNoDebugLevel(t, warnLogger) + testNoDebugLevel(t, infoLogger) + testNoDebugLevel(t, debugLogger) + testNoDebugLevel(t, traceLogger) +} + +func TestNewDefaultLoggerFactoryLogAll(t *testing.T) { + t.Setenv("PION_LOG_ERROR", "all") + t.Setenv("PION_LOG_WARN", "all") + t.Setenv("PION_LOG_INFO", "all") + t.Setenv("PION_LOG_DEBUG", "all") + t.Setenv("PION_LOG_TRACE", "all") + + factory := logging.NewDefaultLoggerFactory() + + testAPI, ok := factory.NewLogger("test").(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger factory type") + + testAllLevels(t, testAPI) +} + +func TestNewDefaultLoggerFactorySpecifcScopes(t *testing.T) { + t.Setenv("PION_LOG_DEBUG", "feature,rtp-logger") + + factory := logging.NewDefaultLoggerFactory() + + feature, ok := factory.NewLogger("feature").(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger factory type") + + rtp, ok := factory.NewLogger("rtp-logger").(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger factory type") + + noScope, ok := factory.NewLogger("no-scope").(*logging.DefaultLeveledLogger) + assert.True(t, ok, "Invalid logger factory type") + + testDebugLevel(t, feature) + testDebugLevel(t, rtp) + testNoDebugLevel(t, noScope) +} + +func TestSetLevel(t *testing.T) { + logger := logging. + NewDefaultLeveledLoggerForScope("testSetLevel", logging.LogLevelWarn, os.Stderr) + + testNoDebugLevel(t, logger) + logger.SetLevel(logging.LogLevelDebug) + testDebugLevel(t, logger) +} + +func TestLogLevel(t *testing.T) { + logLevel := logging.LogLevelDisabled + + logLevel.Set(logging.LogLevelError) + assert.Equal(t, logging.LogLevelError, logLevel.Get(), "LogLevel was not set to LogLevelError") +} + +func TestLogLevelString(t *testing.T) { + expected := map[logging.LogLevel]string{ + logging.LogLevelDisabled: "Disabled", + logging.LogLevelError: "Error", + logging.LogLevelWarn: "Warn", + logging.LogLevelInfo: "Info", + logging.LogLevelDebug: "Debug", + logging.LogLevelTrace: "Trace", + logging.LogLevel(999): "UNKNOWN", + } + + for level, expectedStr := range expected { + assert.Equal(t, expectedStr, level.String()) + } +} + +func TestNewDefaultLoggerStderr(t *testing.T) { + logger := logging.NewDefaultLeveledLoggerForScope("test", logging.LogLevelWarn, nil) + + testNoDebugLevel(t, logger) + testWarnLevel(t, logger) + testErrorLevel(t, logger) +} diff --git a/internal/pion/logging/scoped.go b/internal/pion/logging/scoped.go new file mode 100644 index 0000000..7f90192 --- /dev/null +++ b/internal/pion/logging/scoped.go @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package logging + +import ( + "sync/atomic" + + "github.com/rs/zerolog" +) + +// LogLevel represents the level at which the logger will emit log messages. +type LogLevel int32 + +// Set updates the LogLevel to the supplied value. +func (ll *LogLevel) Set(newLevel LogLevel) { + atomic.StoreInt32((*int32)(ll), int32(newLevel)) +} + +// Get retrieves the current LogLevel value. +func (ll *LogLevel) Get() LogLevel { + return LogLevel(atomic.LoadInt32((*int32)(ll))) +} + +func (ll LogLevel) String() string { + switch ll { + case LogLevelDisabled: + return "Disabled" + case LogLevelError: + return "Error" + case LogLevelWarn: + return "Warn" + case LogLevelInfo: + return "Info" + case LogLevelDebug: + return "Debug" + case LogLevelTrace: + return "Trace" + default: + return "UNKNOWN" + } +} + +const ( + // LogLevelDisabled completely disables logging of any events. + LogLevelDisabled LogLevel = iota + // LogLevelError is for fatal errors which should be handled by user code, + // but are logged to ensure that they are seen. + LogLevelError + // LogLevelWarn is for logging abnormal, but non-fatal library operation. + LogLevelWarn + // LogLevelInfo is for logging normal library operation (e.g. state transitions, etc.). + LogLevelInfo + // LogLevelDebug is for logging low-level library information (e.g. internal operations). + LogLevelDebug + // LogLevelTrace is for logging very low-level library information (e.g. network traces). + LogLevelTrace +) + +// LeveledLogger is the basic pion Logger interface. +type LeveledLogger interface { + Trace(msg string) + Tracef(format string, args ...interface{}) + Debug(msg string) + Debugf(format string, args ...interface{}) + Info(msg string) + Infof(format string, args ...interface{}) + Warn(msg string) + Warnf(format string, args ...interface{}) + Error(msg string) + Errorf(format string, args ...interface{}) + GetLogger() *zerolog.Logger +} + +// LoggerFactory is the basic pion LoggerFactory interface. +type LoggerFactory interface { + NewLogger(scope string) LeveledLogger +} diff --git a/log.go b/log.go index 0d36c0d..3441b03 100644 --- a/log.go +++ b/log.go @@ -4,6 +4,7 @@ import "github.com/pion/logging" // we use logging framework from pion // ref: https://github.com/pion/webrtc/wiki/Debugging-WebRTC -var logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm") -var cloudLogger = logging.NewDefaultLoggerFactory().NewLogger("cloud") -var websocketLogger = logging.NewDefaultLoggerFactory().NewLogger("websocket") +var logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm/jetkvm") +var cloudLogger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm/cloud") +var websocketLogger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm/websocket") +var nativeLogger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm/native") diff --git a/native.go b/native.go index 8960304..36589be 100644 --- a/native.go +++ b/native.go @@ -13,6 +13,7 @@ import ( "time" "github.com/jetkvm/kvm/resource" + "github.com/rs/zerolog" "github.com/pion/webrtc/v4/pkg/media" ) @@ -34,6 +35,19 @@ type CtrlResponse struct { Data json.RawMessage `json:"data,omitempty"` } +type nativeOutput struct { + mu *sync.Mutex + logger *zerolog.Event +} + +func (w *nativeOutput) Write(p []byte) (n int, err error) { + w.mu.Lock() + defer w.mu.Unlock() + + w.logger.Msg(string(p)) + return len(p), nil +} + type EventHandler func(event CtrlResponse) var seq int32 = 1 @@ -225,9 +239,19 @@ func ExtractAndRunNativeBin() error { // Run the binary in the background cmd := exec.Command(binaryPath) + nativeOutputLock := sync.Mutex{} + nativeStdout := &nativeOutput{ + mu: &nativeOutputLock, + logger: nativeLogger.GetLogger().Info().Str("pipe", "stdout"), + } + nativeStderr := &nativeOutput{ + mu: &nativeOutputLock, + logger: nativeLogger.GetLogger().Info().Str("pipe", "stderr"), + } + // Redirect stdout and stderr to the current process - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + cmd.Stdout = nativeStdout + cmd.Stderr = nativeStderr // Set the process group ID so we can kill the process and its children when this process exits cmd.SysProcAttr = &syscall.SysProcAttr{