diff --git a/common/common.go b/common/common.go index 9822cfe42..5a4bebe24 100644 --- a/common/common.go +++ b/common/common.go @@ -9,7 +9,7 @@ import ( "strings" _ "github.com/go-sql-driver/mysql" - _ "github.com/mattn/go-sqlite3" + //_ "github.com/mattn/go-sqlite3" ) type Runnable interface { @@ -57,5 +57,6 @@ func ConnectDatabase(driverName, username, password, ip string, port int, dbName } func ConnectSQLite(dbName string) (*sql.DB, error) { + //for debug only return sql.Open("sqlite3", dbName) } diff --git a/conf/conf.go b/conf/conf.go index e55ead8c5..761fe26f6 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -78,8 +78,8 @@ type GlobalConfig struct { RemotePort uint16 `json:"remote_port"` Hash map[string]string - Passwords []string `json:"password"` - LogLevel int + Passwords []string `json:"password"` + LogLevel int `json: "log_level"` TLS TLSConfig `json:"ssl"` TCP TCPConfig `json:"tcp"` MySQL MySQLConfig `json:"mysql"` diff --git a/go.mod b/go.mod index 71b448f8b..a1b31537f 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.13 require ( github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed github.com/go-sql-driver/mysql v1.5.0 - github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/pkg/errors v0.9.1 // indirect - github.com/smartystreets/goconvey v1.6.4 // indirect + github.com/smartystreets/goconvey v1.6.4 github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a github.com/withmandala/go-log v0.1.0 github.com/xtaci/smux v2.0.1+incompatible + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20200320220750-118fecf932d8 golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae ) diff --git a/go.sum b/go.sum index 449bb08f3..efc5bc750 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= diff --git a/log/buffer/buffer.go b/log/buffer/buffer.go new file mode 100644 index 000000000..c5eac8f11 --- /dev/null +++ b/log/buffer/buffer.go @@ -0,0 +1,42 @@ +// Buffer-like byte slice +// Copyright (c) 2017 Fadhli Dzil Ikram + +package buffer + +// Buffer type wrap up byte slice built-in type +type Buffer []byte + +// Reset buffer position to start +func (b *Buffer) Reset() { + *b = Buffer([]byte(*b)[:0]) +} + +// Append byte slice to buffer +func (b *Buffer) Append(data []byte) { + *b = append(*b, data...) +} + +// AppendByte to buffer +func (b *Buffer) AppendByte(data byte) { + *b = append(*b, data) +} + +// AppendInt to buffer +func (b *Buffer) AppendInt(val int, width int) { + var repr [8]byte + reprCount := len(repr) - 1 + for val >= 10 || width > 1 { + reminder := val / 10 + repr[reprCount] = byte('0' + val - reminder*10) + val = reminder + reprCount-- + width-- + } + repr[reprCount] = byte('0' + val) + b.Append(repr[reprCount:]) +} + +// Bytes return underlying slice data +func (b Buffer) Bytes() []byte { + return []byte(b) +} diff --git a/log/buffer/buffer_test.go b/log/buffer/buffer_test.go new file mode 100644 index 000000000..facad5380 --- /dev/null +++ b/log/buffer/buffer_test.go @@ -0,0 +1,76 @@ +// Buffer-like byte slice +// Copyright (c) 2017 Fadhli Dzil Ikram +// +// Test file for buffer + +package buffer + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestBufferAllocation(t *testing.T) { + Convey("Given new unallocated buffer", t, func() { + var buf Buffer + + Convey("When appended with data", func() { + data := []byte("Hello") + buf.Append(data) + + Convey("It should have same content as the original data", func() { + So(buf.Bytes(), ShouldResemble, data) + }) + }) + + Convey("When appended with single byte", func() { + data := byte('H') + buf.AppendByte(data) + + Convey("It should have 1 byte length", func() { + So(len(buf), ShouldEqual, 1) + }) + + Convey("It should have same content", func() { + So(buf.Bytes()[0], ShouldEqual, data) + }) + }) + + Convey("When appended with integer", func() { + data := 12345 + repr := []byte("012345") + buf.AppendInt(data, len(repr)) + + Convey("Should have same content with the integer representation", func() { + So(buf.Bytes(), ShouldResemble, repr) + }) + }) + }) +} + +func TestBufferReset(t *testing.T) { + Convey("Given allocated buffer", t, func() { + var buf Buffer + data := []byte("Hello") + replace := []byte("World") + buf.Append(data) + + Convey("When buffer reset", func() { + buf.Reset() + + Convey("It should have zero length", func() { + So(len(buf), ShouldEqual, 0) + }) + }) + + Convey("When buffer reset and replaced with another append", func() { + buf.Reset() + buf.Append(replace) + + Convey("It should have same content with the replaced data", func() { + So(buf.Bytes(), ShouldResemble, replace) + }) + }) + }) +} diff --git a/log/colorful/colorful.go b/log/colorful/colorful.go new file mode 100644 index 000000000..48a173167 --- /dev/null +++ b/log/colorful/colorful.go @@ -0,0 +1,104 @@ +// The color engine for the go-log library +// Copyright (c) 2017 Fadhli Dzil Ikram + +package colorful + +import "github.com/withmandala/go-log/buffer" + +// ColorBuffer add color option to buffer append +type ColorBuffer struct { + buffer.Buffer +} + +// color pallete map +var ( + colorOff = []byte("\033[0m") + colorRed = []byte("\033[0;31m") + colorGreen = []byte("\033[0;32m") + colorOrange = []byte("\033[0;33m") + colorBlue = []byte("\033[0;34m") + colorPurple = []byte("\033[0;35m") + colorCyan = []byte("\033[0;36m") + colorGray = []byte("\033[0;37m") +) + +// Off apply no color to the data +func (cb *ColorBuffer) Off() { + cb.Append(colorOff) +} + +// Red apply red color to the data +func (cb *ColorBuffer) Red() { + cb.Append(colorRed) +} + +// Green apply green color to the data +func (cb *ColorBuffer) Green() { + cb.Append(colorGreen) +} + +// Orange apply orange color to the data +func (cb *ColorBuffer) Orange() { + cb.Append(colorOrange) +} + +// Blue apply blue color to the data +func (cb *ColorBuffer) Blue() { + cb.Append(colorBlue) +} + +// Purple apply purple color to the data +func (cb *ColorBuffer) Purple() { + cb.Append(colorPurple) +} + +// Cyan apply cyan color to the data +func (cb *ColorBuffer) Cyan() { + cb.Append(colorCyan) +} + +// Gray apply gray color to the data +func (cb *ColorBuffer) Gray() { + cb.Append(colorGray) +} + +// mixer mix the color on and off byte with the actual data +func mixer(data []byte, color []byte) []byte { + var result []byte + return append(append(append(result, color...), data...), colorOff...) +} + +// Red apply red color to the data +func Red(data []byte) []byte { + return mixer(data, colorRed) +} + +// Green apply green color to the data +func Green(data []byte) []byte { + return mixer(data, colorGreen) +} + +// Orange apply orange color to the data +func Orange(data []byte) []byte { + return mixer(data, colorOrange) +} + +// Blue apply blue color to the data +func Blue(data []byte) []byte { + return mixer(data, colorBlue) +} + +// Purple apply purple color to the data +func Purple(data []byte) []byte { + return mixer(data, colorPurple) +} + +// Cyan apply cyan color to the data +func Cyan(data []byte) []byte { + return mixer(data, colorCyan) +} + +// Gray apply gray color to the data +func Gray(data []byte) []byte { + return mixer(data, colorGray) +} diff --git a/log/colorful/colorful_test.go b/log/colorful/colorful_test.go new file mode 100644 index 000000000..2601e7c9c --- /dev/null +++ b/log/colorful/colorful_test.go @@ -0,0 +1,117 @@ +// The color engine for the go-log library +// Copyright (c) 2017 Fadhli Dzil Ikram +// +// Test file + +package colorful + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + "github.com/withmandala/go-log/buffer" +) + +func TestColorBuffer(t *testing.T) { + Convey("Given empty color buffer and test data", t, func() { + var cb ColorBuffer + var result buffer.Buffer + + // Add color to the result buffer + result.Append(colorRed) + result.Append(colorGreen) + result.Append(colorOrange) + result.Append(colorBlue) + result.Append(colorPurple) + result.Append(colorCyan) + result.Append(colorGray) + result.Append(colorOff) + + Convey("When appended with color", func() { + cb.Red() + cb.Green() + cb.Orange() + cb.Blue() + cb.Purple() + cb.Cyan() + cb.Gray() + cb.Off() + + Convey("It should have same content with the test data", func() { + So(result.Bytes(), ShouldResemble, cb.Bytes()) + }) + }) + }) +} + +func TestColorMixer(t *testing.T) { + Convey("Given mixer test result data", t, func() { + var ( + data = []byte("Hello") + resultRed buffer.Buffer + resultGreen buffer.Buffer + resultOrange buffer.Buffer + resultBlue buffer.Buffer + resultPurple buffer.Buffer + resultCyan buffer.Buffer + resultGray buffer.Buffer + ) + + // Add result to buffer + resultRed.Append(colorRed) + resultRed.Append(data) + resultRed.Append(colorOff) + + resultGreen.Append(colorGreen) + resultGreen.Append(data) + resultGreen.Append(colorOff) + + resultOrange.Append(colorOrange) + resultOrange.Append(data) + resultOrange.Append(colorOff) + + resultBlue.Append(colorBlue) + resultBlue.Append(data) + resultBlue.Append(colorOff) + + resultPurple.Append(colorPurple) + resultPurple.Append(data) + resultPurple.Append(colorOff) + + resultCyan.Append(colorCyan) + resultCyan.Append(data) + resultCyan.Append(colorOff) + + resultGray.Append(colorGray) + resultGray.Append(data) + resultGray.Append(colorOff) + + Convey("It should have same result when data appended with Red color", func() { + So(Red(data), ShouldResemble, resultRed.Bytes()) + }) + + Convey("It should have same result when data appended with Green color", func() { + So(Green(data), ShouldResemble, resultGreen.Bytes()) + }) + + Convey("It should have same result when data appended with Orange color", func() { + So(Orange(data), ShouldResemble, resultOrange.Bytes()) + }) + + Convey("It should have same result when data appended with Blue color", func() { + So(Blue(data), ShouldResemble, resultBlue.Bytes()) + }) + + Convey("It should have same result when data appended with Purple color", func() { + So(Purple(data), ShouldResemble, resultPurple.Bytes()) + }) + + Convey("It should have same result when data appended with Cyan color", func() { + So(Cyan(data), ShouldResemble, resultCyan.Bytes()) + }) + + Convey("It should have same result when data appended with Gray color", func() { + So(Gray(data), ShouldResemble, resultGray.Bytes()) + }) + }) +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 000000000..ad35daab2 --- /dev/null +++ b/log/log.go @@ -0,0 +1,362 @@ +// The colorful and simple logging library +// Copyright (c) 2017 Fadhli Dzil Ikram + +package log + +import ( + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + "github.com/withmandala/go-log/colorful" + "golang.org/x/crypto/ssh/terminal" +) + +//LogLevel how much log to dump +//0: ALL; 1: INFO; 2: WARN; 3: ERROR; 4: FATAL; 5: OFF +var LogLevel int + +// FdWriter interface extends existing io.Writer with file descriptor function +// support +type FdWriter interface { + io.Writer + Fd() uintptr +} + +// Logger struct define the underlying storage for single logger +type Logger struct { + mu sync.RWMutex + color bool + out FdWriter + debug bool + timestamp bool + quiet bool + buf colorful.ColorBuffer +} + +// Prefix struct define plain and color byte +type Prefix struct { + Plain []byte + Color []byte + File bool +} + +var ( + // Plain prefix template + plainFatal = []byte("[FATAL] ") + plainError = []byte("[ERROR] ") + plainWarn = []byte("[WARN] ") + plainInfo = []byte("[INFO] ") + plainDebug = []byte("[DEBUG] ") + plainTrace = []byte("[TRACE] ") + + // FatalPrefix show fatal prefix + FatalPrefix = Prefix{ + Plain: plainFatal, + Color: colorful.Red(plainFatal), + File: true, + } + + // ErrorPrefix show error prefix + ErrorPrefix = Prefix{ + Plain: plainError, + Color: colorful.Red(plainError), + File: true, + } + + // WarnPrefix show warn prefix + WarnPrefix = Prefix{ + Plain: plainWarn, + Color: colorful.Orange(plainWarn), + } + + // InfoPrefix show info prefix + InfoPrefix = Prefix{ + Plain: plainInfo, + Color: colorful.Green(plainInfo), + } + + // DebugPrefix show info prefix + DebugPrefix = Prefix{ + Plain: plainDebug, + Color: colorful.Purple(plainDebug), + File: true, + } + + // TracePrefix show info prefix + TracePrefix = Prefix{ + Plain: plainTrace, + Color: colorful.Cyan(plainTrace), + } +) + +// New returns new Logger instance with predefined writer output and +// automatically detect terminal coloring support +func New(out FdWriter) *Logger { + return &Logger{ + color: terminal.IsTerminal(int(out.Fd())), + out: out, + timestamp: true, + } +} + +// WithColor explicitly turn on colorful features on the log +func (l *Logger) WithColor() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.color = true + return l +} + +// WithoutColor explicitly turn off colorful features on the log +func (l *Logger) WithoutColor() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.color = false + return l +} + +// WithDebug turn on debugging output on the log to reveal debug and trace level +func (l *Logger) WithDebug() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.debug = true + return l +} + +// WithoutDebug turn off debugging output on the log +func (l *Logger) WithoutDebug() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.debug = false + return l +} + +// IsDebug check the state of debugging output +func (l *Logger) IsDebug() bool { + l.mu.RLock() + defer l.mu.RUnlock() + return l.debug +} + +// WithTimestamp turn on timestamp output on the log +func (l *Logger) WithTimestamp() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.timestamp = true + return l +} + +// WithoutTimestamp turn off timestamp output on the log +func (l *Logger) WithoutTimestamp() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.timestamp = false + return l +} + +// Quiet turn off all log output +func (l *Logger) Quiet() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.quiet = true + return l +} + +// NoQuiet turn on all log output +func (l *Logger) NoQuiet() *Logger { + l.mu.Lock() + defer l.mu.Unlock() + l.quiet = false + return l +} + +// IsQuiet check for quiet state +func (l *Logger) IsQuiet() bool { + l.mu.RLock() + defer l.mu.RUnlock() + return l.quiet +} + +// Output print the actual value +func (l *Logger) Output(depth int, prefix Prefix, data string) error { + // Check if quiet is requested, and try to return no error and be quiet + if l.IsQuiet() { + return nil + } + // Get current time + now := time.Now() + // Temporary storage for file and line tracing + var file string + var line int + var fn string + // Check if the specified prefix needs to be included with file logging + if prefix.File { + var ok bool + var pc uintptr + + // Get the caller filename and line + if pc, file, line, ok = runtime.Caller(depth + 1); !ok { + file = "" + fn = "" + line = 0 + } else { + file = filepath.Base(file) + fn = runtime.FuncForPC(pc).Name() + } + } + // Acquire exclusive access to the shared buffer + l.mu.Lock() + defer l.mu.Unlock() + // Reset buffer so it start from the begining + l.buf.Reset() + // Write prefix to the buffer + if l.color { + l.buf.Append(prefix.Color) + } else { + l.buf.Append(prefix.Plain) + } + // Check if the log require timestamping + if l.timestamp { + // Print timestamp color if color enabled + if l.color { + l.buf.Blue() + } + // Print date and time + year, month, day := now.Date() + l.buf.AppendInt(year, 4) + l.buf.AppendByte('/') + l.buf.AppendInt(int(month), 2) + l.buf.AppendByte('/') + l.buf.AppendInt(day, 2) + l.buf.AppendByte(' ') + hour, min, sec := now.Clock() + l.buf.AppendInt(hour, 2) + l.buf.AppendByte(':') + l.buf.AppendInt(min, 2) + l.buf.AppendByte(':') + l.buf.AppendInt(sec, 2) + l.buf.AppendByte(' ') + // Print reset color if color enabled + if l.color { + l.buf.Off() + } + } + // Add caller filename and line if enabled + if prefix.File { + // Print color start if enabled + if l.color { + l.buf.Orange() + } + // Print filename and line + l.buf.Append([]byte(fn)) + l.buf.AppendByte(':') + l.buf.Append([]byte(file)) + l.buf.AppendByte(':') + l.buf.AppendInt(line, 0) + l.buf.AppendByte(' ') + // Print color stop + if l.color { + l.buf.Off() + } + } + // Print the actual string data from caller + l.buf.Append([]byte(data)) + if len(data) == 0 || data[len(data)-1] != '\n' { + l.buf.AppendByte('\n') + } + // Flush buffer to output + _, err := l.out.Write(l.buf.Buffer) + return err +} + +// Fatal print fatal message to output and quit the application with status 1 +func (l *Logger) Fatal(v ...interface{}) { + if LogLevel <= 4 { + l.Output(1, FatalPrefix, fmt.Sprintln(v...)) + } + os.Exit(1) +} + +// Fatalf print formatted fatal message to output and quit the application +// with status 1 +func (l *Logger) Fatalf(format string, v ...interface{}) { + if LogLevel <= 4 { + l.Output(1, FatalPrefix, fmt.Sprintf(format, v...)) + } + os.Exit(1) +} + +// Error print error message to output +func (l *Logger) Error(v ...interface{}) { + if LogLevel <= 3 { + l.Output(1, ErrorPrefix, fmt.Sprintln(v...)) + } +} + +// Errorf print formatted error message to output +func (l *Logger) Errorf(format string, v ...interface{}) { + if LogLevel <= 3 { + l.Output(1, ErrorPrefix, fmt.Sprintf(format, v...)) + } +} + +// Warn print warning message to output +func (l *Logger) Warn(v ...interface{}) { + if LogLevel <= 2 { + l.Output(1, WarnPrefix, fmt.Sprintln(v...)) + } +} + +// Warnf print formatted warning message to output +func (l *Logger) Warnf(format string, v ...interface{}) { + if LogLevel <= 2 { + l.Output(1, WarnPrefix, fmt.Sprintf(format, v...)) + } +} + +// Info print informational message to output +func (l *Logger) Info(v ...interface{}) { + if LogLevel <= 1 { + l.Output(1, InfoPrefix, fmt.Sprintln(v...)) + } +} + +// Infof print formatted informational message to output +func (l *Logger) Infof(format string, v ...interface{}) { + if LogLevel <= 1 { + l.Output(1, InfoPrefix, fmt.Sprintf(format, v...)) + } +} + +// Debug print debug message to output if debug output enabled +func (l *Logger) Debug(v ...interface{}) { + if LogLevel == 0 { + l.Output(1, DebugPrefix, fmt.Sprintln(v...)) + } +} + +// Debugf print formatted debug message to output if debug output enabled +func (l *Logger) Debugf(format string, v ...interface{}) { + if LogLevel == 0 { + l.Output(1, DebugPrefix, fmt.Sprintf(format, v...)) + } +} + +// Trace print trace message to output if debug output enabled +func (l *Logger) Trace(v ...interface{}) { + if LogLevel == 0 { + l.Output(1, TracePrefix, fmt.Sprintln(v...)) + } +} + +// Tracef print formatted trace message to output if debug output enabled +func (l *Logger) Tracef(format string, v ...interface{}) { + if LogLevel == 0 { + l.Output(1, TracePrefix, fmt.Sprintf(format, v...)) + } +} diff --git a/stat/db.go b/stat/db.go index 4a4c998c1..cb78bc897 100644 --- a/stat/db.go +++ b/stat/db.go @@ -91,8 +91,9 @@ func (c *DBTrafficMeter) dbDaemon() { err = tx.Commit() if err != nil { logger.Error(common.NewError("failed to commit tx").Base(err)) + } else { + logger.Info("buffered data has been written into the database") } - logger.Info("buffered data has been written into the database") } }