From bee8b4cbbe7ba51e1bea128aadc7c71277e8fee8 Mon Sep 17 00:00:00 2001
From: q191201771 <191201771@qq.com>
Date: Tue, 27 Aug 2019 14:24:50 +0800
Subject: [PATCH] =?UTF-8?q?=E5=B0=86=20lal/pkg/util=20=E7=9A=84=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81=E6=8B=B7=E8=B4=9D=E8=87=B3=E6=9C=AC=E4=BB=93=E5=BA=93?=
=?UTF-8?q?=E4=B8=AD.=20=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81=E4=B8=ADimpor?=
=?UTF-8?q?t=20lal=E7=9A=84=E8=B7=AF=E5=BE=84=E4=B8=BA=E6=9C=AC=E4=BB=93?=
=?UTF-8?q?=E5=BA=93=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 3 +
.idea/misc.xml | 6 +
.idea/modules.xml | 8 ++
.idea/nezha.iml | 8 ++
.idea/vcs.xml | 6 +
.idea/workspace.xml | 229 ++++++++++++++++++++++++++++++
assert/assert.go | 53 +++++++
assert/assert_test.go | 26 ++++
bele/bele.go | 66 +++++++++
bele/bele_test.go | 203 ++++++++++++++++++++++++++
bininfo/bininfo.go | 41 ++++++
connstat/connstat.go | 82 +++++++++++
connstat2/connstat2.go | 1 +
connstat2/connstat2_test.go | 37 +++++
errors/error.go | 7 +
log/log.go | 274 ++++++++++++++++++++++++++++++++++++
log/log_test.go | 93 ++++++++++++
test.sh | 15 ++
unique/unique.go | 16 +++
unique/unique_test.go | 25 ++++
20 files changed, 1199 insertions(+)
create mode 100644 .gitignore
create mode 100644 .idea/misc.xml
create mode 100644 .idea/modules.xml
create mode 100644 .idea/nezha.iml
create mode 100644 .idea/vcs.xml
create mode 100644 .idea/workspace.xml
create mode 100644 assert/assert.go
create mode 100644 assert/assert_test.go
create mode 100644 bele/bele.go
create mode 100644 bele/bele_test.go
create mode 100644 bininfo/bininfo.go
create mode 100644 connstat/connstat.go
create mode 100644 connstat2/connstat2.go
create mode 100644 connstat2/connstat2_test.go
create mode 100644 errors/error.go
create mode 100644 log/log.go
create mode 100644 log/log_test.go
create mode 100755 test.sh
create mode 100644 unique/unique.go
create mode 100644 unique/unique_test.go
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..19b30a0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+coverage.txt
+coverage.html
+profile.out
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..28a804d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..eabf418
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/nezha.iml b/.idea/nezha.iml
new file mode 100644
index 0000000..c956989
--- /dev/null
+++ b/.idea/nezha.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..741089f
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assert/assert.go b/assert/assert.go
new file mode 100644
index 0000000..663e51f
--- /dev/null
+++ b/assert/assert.go
@@ -0,0 +1,53 @@
+// Package assert 提供了单元测试时的断言功能
+//
+// 代码参考了 https://github.com/stretchr/testify
+//
+package assert
+
+import (
+ "bytes"
+ "reflect"
+)
+
+type TestingT interface {
+ Errorf(format string, args ...interface{})
+}
+
+func Equal(t TestingT, expected interface{}, actual interface{}, msg ...string) {
+ if !equal(expected, actual) {
+ t.Errorf("%s expected=%+v, actual=%+v", msg, expected, actual)
+ }
+ return
+}
+
+func isNil(actual interface{}) bool {
+ if actual == nil {
+ return true
+ }
+ v := reflect.ValueOf(actual)
+ k := v.Kind()
+ if k == reflect.Chan || k == reflect.Map || k == reflect.Ptr || k == reflect.Interface || k == reflect.Slice {
+ return v.IsNil()
+ }
+ return false
+}
+
+func equal(expected, actual interface{}) bool {
+ if expected == nil {
+ return isNil(actual)
+ }
+
+ exp, ok := expected.([]byte)
+ if !ok {
+ return reflect.DeepEqual(expected, actual)
+ }
+
+ act, ok := actual.([]byte)
+ if !ok {
+ return false
+ }
+ //if exp == nil || act == nil {
+ // return exp == nil && act == nil
+ //}
+ return bytes.Equal(exp, act)
+}
\ No newline at end of file
diff --git a/assert/assert_test.go b/assert/assert_test.go
new file mode 100644
index 0000000..2f96f33
--- /dev/null
+++ b/assert/assert_test.go
@@ -0,0 +1,26 @@
+package assert
+
+import "testing"
+
+func TestEqual(t *testing.T) {
+ Equal(t, nil, nil)
+ Equal(t, nil, nil, "fxxk.")
+ Equal(t, 1, 1)
+ Equal(t, "aaa", "aaa")
+ var ch chan struct{}
+ Equal(t, nil, ch)
+ var m map[string]string
+ Equal(t, nil, m)
+ var p *int
+ Equal(t, nil, p)
+ var i interface{}
+ Equal(t, nil, i)
+ var b []byte
+ Equal(t, nil, b)
+
+ Equal(t, true, isNil(nil))
+ Equal(t, false, isNil("aaa"))
+ Equal(t, false, equal([]byte{}, "aaa"))
+ Equal(t, true, equal([]byte{}, []byte{}))
+ Equal(t, true, equal([]byte{0, 1, 2}, []byte{0, 1, 2}))
+}
diff --git a/bele/bele.go b/bele/bele.go
new file mode 100644
index 0000000..17220ee
--- /dev/null
+++ b/bele/bele.go
@@ -0,0 +1,66 @@
+// Package bele 提供了大小端的转换操作
+//
+// be是big endian的缩写,即大端
+// le是little endian的缩写,即小端
+//
+// assume local is `le`
+//
+package bele
+
+import (
+ "encoding/binary"
+ "io"
+ "math"
+)
+
+func BEUint16(p []byte) uint16 {
+ return binary.BigEndian.Uint16(p)
+}
+
+func BEUint24(p []byte) (ret uint32) {
+ ret = 0
+ ret |= uint32(p[0]) << 16
+ ret |= uint32(p[1]) << 8
+ ret |= uint32(p[2])
+ return
+}
+
+func BEUint32(p []byte) (ret uint32) {
+ return binary.BigEndian.Uint32(p)
+}
+
+func BEFloat64(p []byte) (ret float64) {
+ a := binary.BigEndian.Uint64(p)
+ return math.Float64frombits(a)
+}
+
+func LEUint32(p []byte) (ret uint32) {
+ return binary.LittleEndian.Uint32(p)
+}
+
+func BEPutUint24(out []byte, in uint32) {
+ out[0] = byte(in >> 16)
+ out[1] = byte(in >> 8)
+ out[2] = byte(in)
+}
+
+func BEPutUint32(out []byte, in uint32) {
+ binary.BigEndian.PutUint32(out, in)
+}
+
+func LEPutUint32(out []byte, in uint32) {
+ binary.LittleEndian.PutUint32(out, in)
+}
+
+func WriteBEUint24(writer io.Writer, in uint32) error {
+ _, err := writer.Write([]byte{uint8(in >> 16), uint8(in >> 8), uint8(in & 0xFF)})
+ return err
+}
+
+func WriteBE(writer io.Writer, in interface{}) error {
+ return binary.Write(writer, binary.BigEndian, in)
+}
+
+func WriteLE(writer io.Writer, in interface{}) error {
+ return binary.Write(writer, binary.LittleEndian, in)
+}
diff --git a/bele/bele_test.go b/bele/bele_test.go
new file mode 100644
index 0000000..b21063e
--- /dev/null
+++ b/bele/bele_test.go
@@ -0,0 +1,203 @@
+package bele
+
+import (
+ "bytes"
+ "encoding/binary"
+ "github.com/q191201771/nezha/assert"
+ "testing"
+)
+
+func TestBEUint16(t *testing.T) {
+ vector := []struct {
+ input []byte
+ output uint16
+ }{
+ {input: []byte{0, 0}, output: 0},
+ {input: []byte{0, 1}, output: 1},
+ {input: []byte{0, 255}, output: 255},
+ {input: []byte{1, 0}, output: 256},
+ {input: []byte{255, 0}, output: 255 * 256},
+ {input: []byte{12, 34}, output: 12*256 + 34},
+ }
+
+ for i := 0; i < len(vector); i++ {
+ assert.Equal(t, vector[i].output, BEUint16(vector[i].input))
+ }
+}
+
+func TestBEUint24(t *testing.T) {
+ vector := []struct {
+ input []byte
+ output uint32
+ }{
+ {input: []byte{0, 0, 0}, output: 0},
+ {input: []byte{0, 0, 1}, output: 1},
+ {input: []byte{0, 1, 0}, output: 256},
+ {input: []byte{1, 0, 0}, output: 1 * 256 * 256},
+ {input: []byte{12, 34, 56}, output: 12*256*256 + 34*256 + 56},
+ }
+
+ for i := 0; i < len(vector); i++ {
+ assert.Equal(t, vector[i].output, BEUint24(vector[i].input))
+ }
+}
+
+func TestBEUint32(t *testing.T) {
+ vector := []struct {
+ input []byte
+ output uint32
+ }{
+ {input: []byte{0, 0, 0, 0}, output: 0},
+ {input: []byte{0, 0, 1, 0}, output: 1 * 256},
+ {input: []byte{0, 1, 0, 0}, output: 1 * 256 * 256},
+ {input: []byte{1, 0, 0, 0}, output: 1 * 256 * 256 * 256},
+ {input: []byte{12, 34, 56, 78}, output: 12*256*256*256 + 34*256*256 + 56*256 + 78},
+ }
+
+ for i := 0; i < len(vector); i++ {
+ assert.Equal(t, vector[i].output, BEUint32(vector[i].input))
+ }
+}
+
+func TestBEFloat64(t *testing.T) {
+ vector := []int{
+ 1,
+ 0xFF,
+ 0xFFFF,
+ 0xFFFFFF,
+ }
+ for i := 0; i < len(vector); i++ {
+ b := &bytes.Buffer{}
+ err := binary.Write(b, binary.BigEndian, float64(vector[i]))
+ assert.Equal(t, nil, err)
+ assert.Equal(t, vector[i], int(BEFloat64(b.Bytes())))
+ }
+}
+
+func TestLEUint32(t *testing.T) {
+ vector := []struct {
+ input []byte
+ output uint32
+ }{
+ {input: []byte{0, 0, 0, 0}, output: 0},
+ {input: []byte{0, 0, 1, 0}, output: 1 * 256 * 256},
+ {input: []byte{0, 1, 0, 0}, output: 1 * 256},
+ {input: []byte{1, 0, 0, 0}, output: 1},
+ {input: []byte{12, 34, 56, 78}, output: 12 + 34*256 + 56*256*256 + 78*256*256*256},
+ }
+
+ for i := 0; i < len(vector); i++ {
+ assert.Equal(t, vector[i].output, LEUint32(vector[i].input))
+ }
+}
+
+func TestBEPutUint24(t *testing.T) {
+ vector := []struct {
+ input uint32
+ output []byte
+ }{
+ {input: 0, output: []byte{0, 0, 0}},
+ {input: 1, output: []byte{0, 0, 1}},
+ {input: 256, output: []byte{0, 1, 0}},
+ {input: 1 * 256 * 256, output: []byte{1, 0, 0}},
+ {input: 12*256*256 + 34*256 + 56, output: []byte{12, 34, 56}},
+ }
+
+ out := make([]byte, 3)
+ for i := 0; i < len(vector); i++ {
+ BEPutUint24(out, vector[i].input)
+ assert.Equal(t, vector[i].output, out)
+ }
+}
+
+func TestBEPutUint32(t *testing.T) {
+ vector := []struct {
+ input uint32
+ output []byte
+ }{
+ {input: 0, output: []byte{0, 0, 0, 0}},
+ {input: 1 * 256, output: []byte{0, 0, 1, 0}},
+ {input: 1 * 256 * 256, output: []byte{0, 1, 0, 0}},
+ {input: 1 * 256 * 256 * 256, output: []byte{1, 0, 0, 0}},
+ {input: 12*256*256*256 + 34*256*256 + 56*256 + 78, output: []byte{12, 34, 56, 78}},
+ }
+
+ out := make([]byte, 4)
+ for i := 0; i < len(vector); i++ {
+ BEPutUint32(out, vector[i].input)
+ assert.Equal(t, vector[i].output, out)
+ }
+}
+
+func TestLEPutUint32(t *testing.T) {
+ vector := []struct {
+ input uint32
+ output []byte
+ }{
+ {input: 0, output: []byte{0, 0, 0, 0}},
+ {input: 1 * 256 * 256, output: []byte{0, 0, 1, 0}},
+ {input: 1 * 256, output: []byte{0, 1, 0, 0}},
+ {input: 1, output: []byte{1, 0, 0, 0}},
+ {input: 78*256*256*256 + 56*256*256 + 34*256 + 12, output: []byte{12, 34, 56, 78}},
+ }
+
+ out := make([]byte, 4)
+ for i := 0; i < len(vector); i++ {
+ LEPutUint32(out, vector[i].input)
+ assert.Equal(t, vector[i].output, out)
+ }
+}
+
+func TestWriteBEUint24(t *testing.T) {
+ vector := []struct {
+ input uint32
+ output []byte
+ }{
+ {input: 0, output: []byte{0, 0, 0}},
+ {input: 1, output: []byte{0, 0, 1}},
+ {input: 256, output: []byte{0, 1, 0}},
+ {input: 1 * 256 * 256, output: []byte{1, 0, 0}},
+ {input: 12*256*256 + 34*256 + 56, output: []byte{12, 34, 56}},
+ }
+
+ for i := 0; i < len(vector); i++ {
+ out := &bytes.Buffer{}
+ err := WriteBEUint24(out, vector[i].input)
+ assert.Equal(t, nil, err)
+ assert.Equal(t, vector[i].output, out.Bytes())
+ }
+}
+
+func TestWriteBE(t *testing.T) {
+ vector := []struct {
+ input interface{}
+ output []byte
+ }{
+ {input: uint32(1), output: []byte{0, 0, 0, 1}},
+ {input: uint64(1), output: []byte{0, 0, 0, 0, 0, 0, 0, 1}},
+ }
+ for i := 0; i < len(vector); i++ {
+ out := &bytes.Buffer{}
+ err := WriteBE(out, vector[i].input)
+ assert.Equal(t, nil, err)
+ assert.Equal(t, vector[i].output, out.Bytes())
+ }
+}
+
+func TestWriteLE(t *testing.T) {
+ vector := []struct {
+ input interface{}
+ output []byte
+ }{
+ {input: uint32(1), output: []byte{1, 0, 0, 0}},
+ {input: uint64(1), output: []byte{1, 0, 0, 0, 0, 0, 0, 0}},
+ }
+ for i := 0; i < len(vector); i++ {
+ out := &bytes.Buffer{}
+ err := WriteLE(out, vector[i].input)
+ assert.Equal(t, nil, err)
+ assert.Equal(t, vector[i].output, out.Bytes())
+ }
+}
+
+// TODO chef: benchmark
diff --git a/bininfo/bininfo.go b/bininfo/bininfo.go
new file mode 100644
index 0000000..7cb696c
--- /dev/null
+++ b/bininfo/bininfo.go
@@ -0,0 +1,41 @@
+package bininfo
+
+import (
+ "fmt"
+ "runtime"
+)
+
+// 编译时通过如下方式传入编译时信息
+// go build -ldflags " \
+// -X 'github.com/q191201771/lal/pkg/util/bininfo.GitCommitID=`git log --pretty=format:'%h' -n 1`' \
+// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildTime=`date +'%Y.%m.%d.%H%M%S'`' \
+// -X 'github.com/q191201771/lal/pkg/util/bininfo.BuildGoVersion=`go version`' \
+// "
+
+var (
+ GitCommitID string
+ BuildTime string
+ BuildGoVersion string
+)
+
+func StringifySingleLine() string {
+ return fmt.Sprintf("GitCommitID=%s. BuildTime=%s. GoVersion=%s. runtime=%s/%s.",
+ GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
+}
+
+func StringifyMultiLine() string {
+ return fmt.Sprintf("GitCommitID=%s\nBuildTime=%s\nGoVersion=%s\nruntime=%s/%s.",
+ GitCommitID, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
+}
+
+func init() {
+ if GitCommitID == "" {
+ GitCommitID = "unknown"
+ }
+ if BuildTime == "" {
+ BuildTime = "unknown"
+ }
+ if BuildGoVersion == "" {
+ BuildGoVersion = "unknown"
+ }
+}
diff --git a/connstat/connstat.go b/connstat/connstat.go
new file mode 100644
index 0000000..1121be7
--- /dev/null
+++ b/connstat/connstat.go
@@ -0,0 +1,82 @@
+package connstat
+
+import (
+ "sync/atomic"
+ "time"
+)
+
+// 高性能场景下实现长连接流数据读写超时功能
+// 不使用Go的库函数SetDeadline
+// 也不在每次读写数据时使用time now获取读写时间
+// 允许出现两秒左右的误差
+
+type ConnStat struct {
+ readTimeout int64
+ writeTimeout int64
+
+ totalReadByte uint64
+ totalWriteByte uint64
+
+ prevTotalReadByte uint64
+ prevTotalWriteByte uint64
+
+ lastReadActiveTick int64
+ lastWriteActiveTick int64
+}
+
+// 单位秒,设置为0则用于不超时
+func (cs *ConnStat) Start(readTimeout int64, writeTimeout int64) {
+ cs.readTimeout = readTimeout
+ cs.writeTimeout = writeTimeout
+
+ now := time.Now().Unix()
+ cs.lastReadActiveTick = now
+ cs.lastWriteActiveTick = now
+}
+
+func (cs *ConnStat) Read(n int) {
+ atomic.AddUint64(&cs.totalReadByte, uint64(n))
+}
+
+func (cs *ConnStat) Write(n int) {
+ atomic.AddUint64(&cs.totalWriteByte, uint64(n))
+}
+
+// 检查时传入当前时间戳。检查频率应该小于超时阈值。频率越低,则越精确
+func (cs *ConnStat) Check(now int64) (isReadTimeout bool, isWriteTimeout bool) {
+ if cs.readTimeout == 0 { // 没有设置,则不用检查
+ isReadTimeout = false
+ } else {
+ trb := atomic.LoadUint64(&cs.totalReadByte)
+ if trb == 0 { // 历史从来没有收到过数据
+ isReadTimeout = (now - cs.lastReadActiveTick) > cs.readTimeout
+ } else {
+ if trb-cs.prevTotalReadByte > 0 { // 距离上次检查有收到过数据
+ isReadTimeout = false
+ cs.lastReadActiveTick = now
+ } else {
+ isReadTimeout = (now - cs.lastReadActiveTick) > cs.readTimeout
+ }
+ }
+ cs.prevTotalReadByte = trb
+ }
+
+ if cs.writeTimeout == 0 {
+ isWriteTimeout = false
+ } else {
+ twb := atomic.LoadUint64(&cs.totalWriteByte)
+ if twb == 0 {
+ isWriteTimeout = (now - cs.lastWriteActiveTick) > cs.writeTimeout
+ } else {
+ if twb-cs.prevTotalWriteByte > 0 {
+ isWriteTimeout = false
+ cs.lastWriteActiveTick = now
+ } else {
+ isWriteTimeout = (now - cs.lastWriteActiveTick) > cs.writeTimeout
+ }
+ }
+ cs.prevTotalWriteByte = twb
+ }
+
+ return
+}
diff --git a/connstat2/connstat2.go b/connstat2/connstat2.go
new file mode 100644
index 0000000..cee269a
--- /dev/null
+++ b/connstat2/connstat2.go
@@ -0,0 +1 @@
+package connstat2
diff --git a/connstat2/connstat2_test.go b/connstat2/connstat2_test.go
new file mode 100644
index 0000000..49a70d5
--- /dev/null
+++ b/connstat2/connstat2_test.go
@@ -0,0 +1,37 @@
+package connstat2
+
+import (
+ "github.com/q191201771/nezha/assert"
+ "net"
+ "testing"
+)
+
+func startMockServer(t assert.TestingT) {
+ l, err := net.Listen("tcp", ":10027")
+ assert.Equal(t, nil , err)
+ go func() {
+ //for {
+ conn, err := l.Accept()
+ assert.Equal(t, nil , err)
+ go func() {
+ buf := make([]byte, 8)
+ for {
+ _, err = conn.Read(buf)
+ assert.Equal(t, nil , err)
+ }
+ }()
+ //}
+ }()
+}
+
+func Benchmark(b *testing.B) {
+ startMockServer(b)
+ //time.Sleep(time.Duration(1) * time.Second)
+
+ conn, err := net.Dial("tcp", ":10027")
+ assert.Equal(b, nil , err)
+ buf := make([]byte, 8)
+ for i := 0; i < b.N; i++ {
+ conn.Write(buf)
+ }
+}
\ No newline at end of file
diff --git a/errors/error.go b/errors/error.go
new file mode 100644
index 0000000..8f166af
--- /dev/null
+++ b/errors/error.go
@@ -0,0 +1,7 @@
+package errors
+
+func PanicIfErrorOccur(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/log/log.go b/log/log.go
new file mode 100644
index 0000000..db7a6d3
--- /dev/null
+++ b/log/log.go
@@ -0,0 +1,274 @@
+package log
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+// TODO chef:
+// - 性能优化,目前是基于系统库log实现的
+// - 和系统库中的log跑个benchmark对比
+
+var logErr = errors.New("log:fxxk")
+
+type Logger interface {
+ Debugf(format string, v ...interface{})
+ Infof(format string, v ...interface{})
+ Warnf(format string, v ...interface{})
+ Errorf(format string, v ...interface{})
+
+ Debug(v ...interface{})
+ Info(v ...interface{})
+ Warn(v ...interface{})
+ Error(v ...interface{})
+
+ Outputf(level Level, calldepth int, format string, v ...interface{})
+ Output(level Level, calldepth int, v ...interface{})
+}
+
+type Level uint8
+
+const (
+ LevelDebug = iota
+ LevelInfo
+ LevelWarn
+ LevelError
+)
+
+type Config struct {
+ Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出
+
+ Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建
+ IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台
+ // 文件输出和控制台输出可同时打开,控制台输出主要用做开发时调试,支持level彩色输出,以及源码文件、行号
+
+ RotateMByte int `json:"rotate_mbyte"` // 日志大小达到多少兆后翻滚,如果为0,则不翻滚
+}
+
+func New(c Config) (Logger, error) {
+ var (
+ fl *log.Logger
+ sl *log.Logger
+ dir string
+ fp *os.File
+ err error
+ )
+ if c.Level < LevelDebug || c.Level > LevelError {
+ return nil, logErr
+ }
+ if c.Filename != "" {
+ dir = filepath.Dir(c.Filename)
+ if err := os.MkdirAll(dir, 0777); err != nil {
+ return nil, err
+ }
+ fp, err = os.Create(c.Filename)
+ if err != nil {
+ return nil, err
+ }
+ fl = log.New(fp, "", log.Ldate|log.Lmicroseconds)
+ }
+ if c.IsToStdout {
+ sl = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
+ }
+
+ l := &logger{
+ fileLogger: fl,
+ stdoutLogger: sl,
+ c: c,
+ dir: dir,
+ fp: fp,
+ }
+ return l, nil
+}
+
+const (
+ levelDebugString = "DEBUG "
+ levelInfoString = "INFO "
+ levelWarnString = "WARN "
+ levelErrorString = "ERROR "
+
+ levelDebugColorString = "\033[22;37mDEBUG\033[0m "
+ levelInfoColorString = "\033[22;36mINFO\033[0m "
+ levelWarnColorString = "\033[22;33mWARN\033[0m "
+ levelErrorColorString = "\033[22;31mERROR\033[0m "
+)
+
+var (
+ levelToString = map[Level]string{
+ LevelDebug: levelDebugString,
+ LevelInfo: levelInfoString,
+ LevelWarn: levelWarnString,
+ LevelError: levelErrorString,
+ }
+ levelToColorString = map[Level]string{
+ LevelDebug: levelDebugColorString,
+ LevelInfo: levelInfoColorString,
+ LevelWarn: levelWarnColorString,
+ LevelError: levelErrorColorString,
+ }
+)
+
+type logger struct {
+ fileLogger *log.Logger
+ stdoutLogger *log.Logger
+ c Config
+
+ dir string
+
+ m sync.Mutex
+ fp *os.File
+}
+
+func (l *logger) Debugf(format string, v ...interface{}) {
+ l.Outputf(LevelDebug, 3, format, v...)
+}
+
+func (l *logger) Infof(format string, v ...interface{}) {
+ l.Outputf(LevelInfo, 3, format, v...)
+}
+
+func (l *logger) Warnf(format string, v ...interface{}) {
+ l.Outputf(LevelWarn, 3, format, v...)
+}
+
+func (l *logger) Errorf(format string, v ...interface{}) {
+ l.Outputf(LevelError, 3, format, v...)
+}
+
+func (l *logger) Debug(v ...interface{}) {
+ l.Output(LevelDebug, 3, v...)
+}
+
+func (l *logger) Info(v ...interface{}) {
+ l.Output(LevelInfo, 3, v...)
+}
+
+func (l *logger) Warn(v ...interface{}) {
+ l.Output(LevelWarn, 3, v...)
+}
+
+func (l *logger) Error(v ...interface{}) {
+ l.Output(LevelError, 3, v...)
+}
+
+// TODO chef: Outputf 和 Output 代码重复
+func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) {
+ if l.c.Level > level {
+ return
+ }
+
+ msg := fmt.Sprintf(format, v...)
+ if l.stdoutLogger != nil {
+ l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
+ }
+ if l.fileLogger != nil {
+ if l.c.RotateMByte > 0 {
+ l.m.Lock()
+ // 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
+ // TODO chef: 性能比较差,系统库内部也有锁
+ defer l.m.Unlock()
+ if fi, err := os.Stat(l.c.Filename); err == nil {
+ if fi.Size() > int64(l.c.RotateMByte)*1024*1024 {
+ newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
+ if err := os.Rename(l.c.Filename, newFileName); err == nil {
+ l.fp.Close()
+ l.fp, _ = os.Create(l.c.Filename)
+ l.fileLogger.SetOutput(l.fp)
+ }
+ }
+ }
+ }
+ l.fileLogger.Output(calldepth, levelToString[level]+msg)
+ }
+}
+
+func (l *logger) Output(level Level, calldepth int, v ...interface{}) {
+ if l.c.Level > level {
+ return
+ }
+
+ msg := fmt.Sprint(v...)
+ if l.stdoutLogger != nil {
+ l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg)
+ }
+ if l.fileLogger != nil {
+ if l.c.RotateMByte > 0 {
+ l.m.Lock()
+ // 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件
+ // TODO chef: 性能比较差,系统库内部也有锁
+ defer l.m.Unlock()
+ if fi, err := os.Stat(l.c.Filename); err == nil {
+ if fi.Size() > int64(l.c.RotateMByte)*1024*1024 {
+ newFileName := l.c.Filename + "." + time.Now().Format("20060102150405")
+ if err := os.Rename(l.c.Filename, newFileName); err == nil {
+ l.fp.Close()
+ l.fp, _ = os.Create(l.c.Filename)
+ l.fileLogger.SetOutput(l.fp)
+ }
+ }
+ }
+ }
+ l.fileLogger.Output(calldepth, levelToString[level]+msg)
+ }
+}
+
+var global Logger
+
+func Debugf(format string, v ...interface{}) {
+ global.Outputf(LevelDebug, 3, format, v...)
+}
+
+func Infof(format string, v ...interface{}) {
+ global.Outputf(LevelInfo, 3, format, v...)
+}
+
+func Warnf(format string, v ...interface{}) {
+ global.Outputf(LevelWarn, 3, format, v...)
+}
+
+func Errorf(format string, v ...interface{}) {
+ global.Outputf(LevelError, 3, format, v...)
+}
+
+func Debug(v ...interface{}) {
+ global.Output(LevelDebug, 3, v...)
+}
+
+func Info(v ...interface{}) {
+ global.Output(LevelInfo, 3, v...)
+}
+
+func Warn(v ...interface{}) {
+ global.Output(LevelWarn, 3, v...)
+}
+
+func Error(v ...interface{}) {
+ global.Output(LevelError, 3, v...)
+}
+
+func Outputf(level Level, calldepth int, format string, v ...interface{}) {
+ global.Outputf(level, calldepth, format, v...)
+}
+
+func Output(level Level, calldepth int, v ...interface{}) {
+ global.Output(level, calldepth, v...)
+}
+
+// 这里不加锁保护,如果要调用Init函数初始化全局的Logger,那么由调用方保证调用Init函数时不会并发调用全局Logger的其他方法
+func Init(c Config) error {
+ var err error
+ global, err = New(c)
+ return err
+}
+
+func init() {
+ global, _ = New(Config{
+ Level: LevelDebug,
+ IsToStdout: true,
+ })
+}
diff --git a/log/log_test.go b/log/log_test.go
new file mode 100644
index 0000000..497e878
--- /dev/null
+++ b/log/log_test.go
@@ -0,0 +1,93 @@
+package log
+
+import (
+ "testing"
+ "github.com/q191201771/nezha/assert"
+)
+
+func TestLogger(t *testing.T) {
+ c := Config{
+ Level: LevelInfo,
+ Filename: "/tmp/lallogtest/aaa.log",
+ IsToStdout: true,
+ RotateMByte: 10,
+ }
+ l, err := New(c)
+ assert.Equal(t, nil, err)
+ l.Debugf("test msg by Debug%s", "f")
+ l.Infof("test msg by Info%s", "f")
+ l.Warnf("test msg by Warn%s", "f")
+ l.Errorf("test msg by Error%s", "f")
+ l.Debug("test msg by Debug")
+ l.Info("test msg by Info")
+ l.Warn("test msg by Warn")
+ l.Error("test msg by Error")
+}
+
+func TestGlobal(t *testing.T) {
+ Debugf("test msg by Debug%s", "f")
+ Infof("test msg by Info%s", "f")
+ Warnf("test msg by Warn%s", "f")
+ Errorf("test msg by Error%s", "f")
+ Debug("test msg by Debug")
+ Info("test msg by Info")
+ Warn("test msg by Warn")
+ Error("test msg by Error")
+
+ c := Config{
+ Level: LevelInfo,
+ Filename: "/tmp/lallogtest/bbb.log",
+ IsToStdout: true,
+ RotateMByte: 10,
+ }
+ err := Init(c)
+ assert.Equal(t, nil, err)
+ Debugf("test msg by Debug%s", "f")
+ Infof("test msg by Info%s", "f")
+ Warnf("test msg by Warn%s", "f")
+ Errorf("test msg by Error%s", "f")
+ Debug("test msg by Debug")
+ Info("test msg by Info")
+ Warn("test msg by Warn")
+ Error("test msg by Error")
+ Output(LevelInfo, 3, "test msg by Output")
+ Outputf(LevelInfo, 3, "test msg by Output%s", "f")
+}
+
+func TestNew(t *testing.T) {
+ l, err := New(Config{Level:LevelError+1})
+ assert.Equal(t, nil, l)
+ assert.Equal(t, logErr, err)
+}
+
+func TestRotate(t *testing.T) {
+ c := Config{
+ Level: LevelInfo,
+ Filename: "/tmp/lallogtest/ccc.log",
+ IsToStdout: false,
+ RotateMByte: 1,
+ }
+ err := Init(c)
+ assert.Equal(t, nil, err)
+ b := make([]byte, 1024)
+ for i := 0; i < 2 * 1024; i++ {
+ Info(b)
+ }
+ for i := 0; i < 2 * 1024; i++ {
+ Infof("%+v", b)
+ }
+}
+
+func BenchmarkStdout(b *testing.B) {
+ c := Config{
+ Level: LevelInfo,
+ Filename: "/tmp/lallogtest/ccc.log",
+ IsToStdout: true,
+ RotateMByte: 10,
+ }
+ err := Init(c)
+ assert.Equal(b, nil, err)
+ for i := 0; i < b.N; i++ {
+ Infof("hello %s %d", "world", i)
+ }
+}
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..4f3d016
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -e
+echo "" > coverage.txt
+
+for d in $(go list ./... | grep -v vendor); do
+ go test -race -coverprofile=profile.out -covermode=atomic $d
+ if [ -f profile.out ]; then
+ cat profile.out >> coverage.txt
+ rm profile.out
+ fi
+done
+
+# go test -race -coverprofile=profile.out -covermode=atomic && go tool cover -html=profile.out -o coverage.html && open coverage.html
+# go test -test.bench=".*"
diff --git a/unique/unique.go b/unique/unique.go
new file mode 100644
index 0000000..e0ea00b
--- /dev/null
+++ b/unique/unique.go
@@ -0,0 +1,16 @@
+package unique
+
+import (
+ "fmt"
+ "sync/atomic"
+)
+
+var globalID = uint64(0)
+
+func GenUniqueKey(prefix string) string {
+ return fmt.Sprintf("%s%d", prefix, genUniqueID())
+}
+
+func genUniqueID() uint64 {
+ return atomic.AddUint64(&globalID, 1)
+}
diff --git a/unique/unique_test.go b/unique/unique_test.go
new file mode 100644
index 0000000..80862ec
--- /dev/null
+++ b/unique/unique_test.go
@@ -0,0 +1,25 @@
+package unique
+
+import (
+ "github.com/q191201771/nezha/assert"
+ "sync"
+ "testing"
+)
+
+func TestGenUniqueKey(t *testing.T) {
+ m := make(map[string]struct{})
+ var mutex sync.Mutex
+ var wg sync.WaitGroup
+ wg.Add(1000)
+ for i := 0; i < 1000; i++ {
+ go func() {
+ uk := GenUniqueKey("test")
+ mutex.Lock()
+ m[uk] = struct{}{}
+ mutex.Unlock()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ assert.Equal(t, 1000, len(m))
+}