From db7d48e48f42dde4636d48ebc294453b0a61a9ba Mon Sep 17 00:00:00 2001 From: v2ray Date: Sat, 5 Dec 2015 21:10:14 +0100 Subject: [PATCH] Refine log settings --- common/log/access.go | 83 +++++----------------- common/log/access_test.go | 15 ++-- common/log/log.go | 69 ++++++++++-------- common/log/log_test.go | 11 +-- common/log/log_writer.go | 77 ++++++++++++++++++++ shell/point/config/config.go | 3 + shell/point/config/json/log.go | 30 +++++++- shell/point/config/testing/mocks/config.go | 19 +++-- shell/point/point.go | 19 +++++ testing/assert/stringsubject.go | 14 ++++ 10 files changed, 229 insertions(+), 111 deletions(-) create mode 100644 common/log/log_writer.go diff --git a/common/log/access.go b/common/log/access.go index a92c70de..2046bf8a 100644 --- a/common/log/access.go +++ b/common/log/access.go @@ -1,12 +1,5 @@ package log -import ( - "log" - "os" - - "github.com/v2ray/v2ray-core/common/platform" -) - // AccessStatus is the status of an access request from clients. type AccessStatus string @@ -15,16 +8,9 @@ const ( AccessRejected = AccessStatus("rejected") ) -type accessLogger interface { - Log(from, to string, status AccessStatus, reason string) -} - -type noOpAccessLogger struct { -} - -func (this *noOpAccessLogger) Log(from, to string, status AccessStatus, reason string) { - // Swallow -} +var ( + accessLoggerInstance logWriter = &noOpLogWriter{} +) type accessLog struct { From string @@ -33,60 +19,27 @@ type accessLog struct { Reason string } -type fileAccessLogger struct { - queue chan *accessLog - logger *log.Logger - file *os.File +func (this *accessLog) String() string { + return this.From + " " + string(this.Status) + " " + this.To + " " + this.Reason } -func (this *fileAccessLogger) close() { - this.file.Close() -} - -func (logger *fileAccessLogger) Log(from, to string, status AccessStatus, reason string) { - select { - case logger.queue <- &accessLog{ - From: from, - To: to, - Status: status, - Reason: reason, - }: - default: - // We don't expect this to happen, but don't want to block main thread as well. - } -} - -func (this *fileAccessLogger) Run() { - for entry := range this.queue { - this.logger.Println(entry.From + " " + string(entry.Status) + " " + entry.To + " " + entry.Reason) - } -} - -func newFileAccessLogger(path string) accessLogger { - file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) - if err != nil { - log.Printf("Unable to create or open file (%s): %v%s", path, err, platform.LineSeparator()) - return nil - } - return &fileAccessLogger{ - queue: make(chan *accessLog, 16), - logger: log.New(file, "", log.Ldate|log.Ltime), - file: file, - } -} - -var accessLoggerInstance accessLogger = &noOpAccessLogger{} - // InitAccessLogger initializes the access logger to write into the give file. -func InitAccessLogger(file string) { - logger := newFileAccessLogger(file) - if logger != nil { - go logger.(*fileAccessLogger).Run() - accessLoggerInstance = logger +func InitAccessLogger(file string) error { + logger, err := newFileLogWriter(file) + if err != nil { + Error("Failed to create access logger on file (%s): %v", file, err) + return err } + accessLoggerInstance = logger + return nil } // Access writes an access log. func Access(from, to string, status AccessStatus, reason string) { - accessLoggerInstance.Log(from, to, status, reason) + accessLoggerInstance.Log(&accessLog{ + From: from, + To: to, + Status: status, + Reason: reason, + }) } diff --git a/common/log/access_test.go b/common/log/access_test.go index 361168ef..7c254fb8 100644 --- a/common/log/access_test.go +++ b/common/log/access_test.go @@ -3,10 +3,10 @@ package log import ( "io/ioutil" "os" - "strings" "testing" "time" + "github.com/v2ray/v2ray-core/common/serial" v2testing "github.com/v2ray/v2ray-core/testing" "github.com/v2ray/v2ray-core/testing/assert" ) @@ -22,14 +22,15 @@ func TestAccessLog(t *testing.T) { Access("test_from", "test_to", AccessAccepted, "test_reason") <-time.After(2 * time.Second) - accessLoggerInstance.(*fileAccessLogger).close() - accessLoggerInstance = &noOpAccessLogger{} + accessLoggerInstance.(*fileLogWriter).close() + accessLoggerInstance = &noOpLogWriter{} content, err := ioutil.ReadFile(filename) assert.Error(err).IsNil() - assert.Bool(strings.Contains(string(content), "test_from")).IsTrue() - assert.Bool(strings.Contains(string(content), "test_to")).IsTrue() - assert.Bool(strings.Contains(string(content), "test_reason")).IsTrue() - assert.Bool(strings.Contains(string(content), "accepted")).IsTrue() + contentStr := serial.StringLiteral(content) + assert.String(contentStr).Contains(serial.StringLiteral("test_from")) + assert.String(contentStr).Contains(serial.StringLiteral("test_to")) + assert.String(contentStr).Contains(serial.StringLiteral("test_reason")) + assert.String(contentStr).Contains(serial.StringLiteral("accepted")) } diff --git a/common/log/log.go b/common/log/log.go index e771cc26..11754102 100644 --- a/common/log/log.go +++ b/common/log/log.go @@ -2,8 +2,6 @@ package log import ( "fmt" - "log" - "os" ) const ( @@ -13,36 +11,25 @@ const ( ErrorLevel = LogLevel(3) ) -type logger interface { - WriteLog(prefix, format string, v ...interface{}) +type errorLog struct { + prefix string + format string + values []interface{} } -type noOpLogger struct { -} - -func (this *noOpLogger) WriteLog(prefix, format string, v ...interface{}) { - // Swallow -} - -type streamLogger struct { - logger *log.Logger -} - -func (this *streamLogger) WriteLog(prefix, format string, v ...interface{}) { +func (this *errorLog) String() string { var data string - if v == nil || len(v) == 0 { - data = format + if len(this.values) == 0 { + data = this.format } else { - data = fmt.Sprintf(format, v...) + data = fmt.Sprintf(this.format, this.values...) } - this.logger.Println(prefix + data) + return this.prefix + data } var ( - noOpLoggerInstance logger = &noOpLogger{} - streamLoggerInstance logger = &streamLogger{ - logger: log.New(os.Stdout, "", log.Ldate|log.Ltime), - } + noOpLoggerInstance logWriter = &noOpLogWriter{} + streamLoggerInstance logWriter = newStdOutLogWriter() debugLogger = noOpLoggerInstance infoLogger = noOpLoggerInstance @@ -74,22 +61,48 @@ func SetLogLevel(level LogLevel) { } } +func InitErrorLogger(file string) error { + logger, err := newFileLogWriter(file) + if err != nil { + Error("Failed to create error logger on file (%s): %v", file, err) + return err + } + streamLoggerInstance = logger + return nil +} + // Debug outputs a debug log with given format and optional arguments. func Debug(format string, v ...interface{}) { - debugLogger.WriteLog("[Debug]", format, v...) + debugLogger.Log(&errorLog{ + prefix: "[Debug]", + format: format, + values: v, + }) } // Info outputs an info log with given format and optional arguments. func Info(format string, v ...interface{}) { - infoLogger.WriteLog("[Info]", format, v...) + infoLogger.Log(&errorLog{ + prefix: "[Info]", + format: format, + values: v, + }) } // Warning outputs a warning log with given format and optional arguments. func Warning(format string, v ...interface{}) { - warningLogger.WriteLog("[Warning]", format, v...) + warningLogger.Log(&errorLog{ + prefix: "[Warning]", + format: format, + values: v, + }) } // Error outputs an error log with given format and optional arguments. func Error(format string, v ...interface{}) { - errorLogger.WriteLog("[Error]", format, v...) + errorLogger.Log(&errorLog{ + prefix: "[Error]", + format: format, + values: v, + }) } diff --git a/common/log/log_test.go b/common/log/log_test.go index 31498d76..a7886a89 100644 --- a/common/log/log_test.go +++ b/common/log/log_test.go @@ -25,13 +25,14 @@ func TestStreamLogger(t *testing.T) { v2testing.Current(t) buffer := bytes.NewBuffer(make([]byte, 0, 1024)) - logger := &streamLogger{ + infoLogger = &stdOutLogWriter{ logger: log.New(buffer, "", 0), } - logger.WriteLog("TestPrefix: ", "Test %s Format", "Stream Logger") - assert.Bytes(buffer.Bytes()).Equals([]byte("TestPrefix: Test Stream Logger Format\n")) + Info("Test %s Format", "Stream Logger") + assert.Bytes(buffer.Bytes()).Equals([]byte("[Info]Test Stream Logger Format\n")) buffer.Reset() - logger.WriteLog("TestPrefix: ", "Test No Format") - assert.Bytes(buffer.Bytes()).Equals([]byte("TestPrefix: Test No Format\n")) + errorLogger = infoLogger + Error("Test No Format") + assert.Bytes(buffer.Bytes()).Equals([]byte("[Error]Test No Format\n")) } diff --git a/common/log/log_writer.go b/common/log/log_writer.go new file mode 100644 index 00000000..4f291c37 --- /dev/null +++ b/common/log/log_writer.go @@ -0,0 +1,77 @@ +package log + +import ( + "io" + "log" + "os" + + "github.com/v2ray/v2ray-core/common/platform" + "github.com/v2ray/v2ray-core/common/serial" +) + +func createLogger(writer io.Writer) *log.Logger { + return log.New(writer, "", log.Ldate|log.Ltime) +} + +type logWriter interface { + Log(serial.String) +} + +type noOpLogWriter struct { +} + +func (this *noOpLogWriter) Log(serial.String) { + // Swallow +} + +type stdOutLogWriter struct { + logger *log.Logger +} + +func newStdOutLogWriter() logWriter { + return &stdOutLogWriter{ + logger: createLogger(os.Stdout), + } +} + +func (this *stdOutLogWriter) Log(log serial.String) { + this.logger.Print(log.String() + platform.LineSeparator()) +} + +type fileLogWriter struct { + queue chan serial.String + logger *log.Logger + file *os.File +} + +func (this *fileLogWriter) Log(log serial.String) { + select { + case this.queue <- log: + default: + // We don't expect this to happen, but don't want to block main thread as well. + } +} + +func (this *fileLogWriter) run() { + for entry := range this.queue { + this.logger.Print(entry.String() + platform.LineSeparator()) + } +} + +func (this *fileLogWriter) close() { + this.file.Close() +} + +func newFileLogWriter(path string) (*fileLogWriter, error) { + file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + logger := &fileLogWriter{ + queue: make(chan serial.String, 16), + logger: log.New(file, "", log.Ldate|log.Ltime), + file: file, + } + go logger.run() + return logger, nil +} diff --git a/shell/point/config/config.go b/shell/point/config/config.go index 0490ed4f..9cdcb4ab 100644 --- a/shell/point/config/config.go +++ b/shell/point/config/config.go @@ -2,6 +2,7 @@ package config import ( routerconfig "github.com/v2ray/v2ray-core/app/router/config" + "github.com/v2ray/v2ray-core/common/log" v2net "github.com/v2ray/v2ray-core/common/net" ) @@ -12,6 +13,8 @@ type ConnectionConfig interface { type LogConfig interface { AccessLog() string + ErrorLog() string + LogLevel() log.LogLevel } type InboundDetourConfig interface { diff --git a/shell/point/config/json/log.go b/shell/point/config/json/log.go index 05a83263..80817a67 100644 --- a/shell/point/config/json/log.go +++ b/shell/point/config/json/log.go @@ -1,9 +1,35 @@ package json +import ( + "strings" + + "github.com/v2ray/v2ray-core/common/log" +) + type LogConfig struct { AccessLogValue string `json:"access"` + ErrorLogValue string `json:"error"` + LogLevelValue string `json:"loglevel"` } -func (config *LogConfig) AccessLog() string { - return config.AccessLogValue +func (this *LogConfig) AccessLog() string { + return this.AccessLogValue +} + +func (this *LogConfig) ErrorLog() string { + return this.ErrorLogValue +} + +func (this *LogConfig) LogLevel() log.LogLevel { + level := strings.ToLower(this.LogLevelValue) + switch level { + case "debug": + return log.DebugLevel + case "info": + return log.InfoLevel + case "error": + return log.ErrorLevel + default: + return log.WarningLevel + } } diff --git a/shell/point/config/testing/mocks/config.go b/shell/point/config/testing/mocks/config.go index 5fadfbb4..5218fe8e 100644 --- a/shell/point/config/testing/mocks/config.go +++ b/shell/point/config/testing/mocks/config.go @@ -3,6 +3,7 @@ package mocks import ( routerconfig "github.com/v2ray/v2ray-core/app/router/config" routertestingconfig "github.com/v2ray/v2ray-core/app/router/config/testing" + "github.com/v2ray/v2ray-core/common/log" v2net "github.com/v2ray/v2ray-core/common/net" "github.com/v2ray/v2ray-core/shell/point/config" ) @@ -22,6 +23,20 @@ func (config *ConnectionConfig) Settings() interface{} { type LogConfig struct { AccessLogValue string + ErrorLogValue string + LogLevelValue log.LogLevel +} + +func (config *LogConfig) AccessLog() string { + return config.AccessLogValue +} + +func (this *LogConfig) ErrorLog() string { + return this.ErrorLogValue +} + +func (this *LogConfig) LogLevel() log.LogLevel { + return this.LogLevelValue } type PortRange struct { @@ -55,10 +70,6 @@ func (this *OutboundDetourConfig) Tag() string { return this.TagValue } -func (config *LogConfig) AccessLog() string { - return config.AccessLogValue -} - type Config struct { PortValue v2net.Port LogConfigValue *LogConfig diff --git a/shell/point/point.go b/shell/point/point.go index c8643e6d..8900b736 100644 --- a/shell/point/point.go +++ b/shell/point/point.go @@ -30,6 +30,25 @@ func NewPoint(pConfig config.PointConfig) (*Point, error) { var vpoint = new(Point) vpoint.port = pConfig.Port() + if pConfig.LogConfig() != nil { + logConfig := pConfig.LogConfig() + if len(logConfig.AccessLog()) > 0 { + err := log.InitAccessLogger(logConfig.AccessLog()) + if err != nil { + return nil, err + } + } + + if len(logConfig.ErrorLog()) > 0 { + err := log.InitErrorLogger(logConfig.ErrorLog()) + if err != nil { + return nil, err + } + } + + log.SetLogLevel(logConfig.LogLevel()) + } + ichFactory := connhandler.GetInboundConnectionHandlerFactory(pConfig.InboundConfig().Protocol()) if ichFactory == nil { log.Error("Unknown inbound connection handler factory %s", pConfig.InboundConfig().Protocol()) diff --git a/testing/assert/stringsubject.go b/testing/assert/stringsubject.go index aa904a82..1a8f3e48 100644 --- a/testing/assert/stringsubject.go +++ b/testing/assert/stringsubject.go @@ -1,6 +1,8 @@ package assert import ( + "strings" + "github.com/v2ray/v2ray-core/common/serial" ) @@ -31,3 +33,15 @@ func (subject *StringSubject) Equals(expectation string) { subject.Fail(subject.DisplayString(), "is equal to", serial.StringLiteral(expectation)) } } + +func (subject *StringSubject) Contains(substring serial.String) { + if !strings.Contains(subject.value.String(), substring.String()) { + subject.Fail(subject.DisplayString(), "contains", substring) + } +} + +func (subject *StringSubject) NotContains(substring serial.String) { + if strings.Contains(subject.value.String(), substring.String()) { + subject.Fail(subject.DisplayString(), "doesn't contain", substring) + } +}