Refactor(log): use go.uber.org/zap (#389)

This commit is contained in:
Jason Lyu
2024-08-29 06:56:35 +08:00
committed by GitHub
parent c8c08cfeea
commit 601601a1dc
18 changed files with 136 additions and 489 deletions

View File

@@ -1,3 +0,0 @@
package observable
type Iterable <-chan any

View File

@@ -1,65 +0,0 @@
package observable
import (
"errors"
"sync"
)
type Observable struct {
iterable Iterable
listener map[Subscription]*Subscriber
mux sync.Mutex
done bool
}
func (o *Observable) process() {
for item := range o.iterable {
o.mux.Lock()
for _, sub := range o.listener {
sub.Emit(item)
}
o.mux.Unlock()
}
o.close()
}
func (o *Observable) close() {
o.mux.Lock()
defer o.mux.Unlock()
o.done = true
for _, sub := range o.listener {
sub.Close()
}
}
func (o *Observable) Subscribe() (Subscription, error) {
o.mux.Lock()
defer o.mux.Unlock()
if o.done {
return nil, errors.New("observable is closed")
}
subscriber := newSubscriber()
o.listener[subscriber.Out()] = subscriber
return subscriber.Out(), nil
}
func (o *Observable) UnSubscribe(sub Subscription) {
o.mux.Lock()
defer o.mux.Unlock()
subscriber, exist := o.listener[sub]
if !exist {
return
}
delete(o.listener, sub)
subscriber.Close()
}
func NewObservable(any Iterable) *Observable {
observable := &Observable{
iterable: any,
listener: map[Subscription]*Subscriber{},
}
go observable.process()
return observable
}

View File

@@ -1,148 +0,0 @@
package observable
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func iterator(item []any) chan any {
ch := make(chan any)
go func() {
time.Sleep(100 * time.Millisecond)
for _, elm := range item {
ch <- elm
}
close(ch)
}()
return ch
}
func TestObservable(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter)
data, err := src.Subscribe()
assert.Nil(t, err)
count := 0
for range data {
count++
}
assert.Equal(t, count, 5)
}
func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter)
ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe()
count := atomic.NewInt32(0)
var wg sync.WaitGroup
wg.Add(2)
waitCh := func(ch <-chan any) {
for range ch {
count.Inc()
}
wg.Done()
}
go waitCh(ch1)
go waitCh(ch2)
wg.Wait()
assert.Equal(t, int32(10), count.Load())
}
func TestObservable_UnSubscribe(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter)
data, err := src.Subscribe()
assert.Nil(t, err)
src.UnSubscribe(data)
_, open := <-data
assert.False(t, open)
}
func TestObservable_SubscribeClosedSource(t *testing.T) {
iter := iterator([]any{1})
src := NewObservable(iter)
data, _ := src.Subscribe()
<-data
_, closed := src.Subscribe()
assert.NotNil(t, closed)
}
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
sub := Subscription(make(chan any))
iter := iterator([]any{1})
src := NewObservable(iter)
src.UnSubscribe(sub)
}
func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator([]any{1, 2, 3, 4, 5})
src := NewObservable(iter)
max := 100
var list []Subscription
for i := 0; i < max; i++ {
ch, _ := src.Subscribe()
list = append(list, ch)
}
var wg sync.WaitGroup
wg.Add(max)
waitCh := func(ch <-chan any) {
for range ch {
}
wg.Done()
}
for _, ch := range list {
go waitCh(ch)
}
wg.Wait()
for _, sub := range list {
_, more := <-sub
assert.False(t, more)
}
if len(list) > 0 {
_, more := <-list[0]
assert.False(t, more)
}
}
func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan any)
o := NewObservable(ch)
num := 1000
var subs []Subscription
for i := 0; i < num; i++ {
sub, _ := o.Subscribe()
subs = append(subs, sub)
}
wg := sync.WaitGroup{}
wg.Add(num)
b.ResetTimer()
for _, sub := range subs {
go func(s Subscription) {
for range s {
}
wg.Done()
}(sub)
}
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
wg.Wait()
}

View File

@@ -1,33 +0,0 @@
package observable
import (
"sync"
)
type Subscription <-chan any
type Subscriber struct {
buffer chan any
once sync.Once
}
func (s *Subscriber) Emit(item any) {
s.buffer <- item
}
func (s *Subscriber) Out() Subscription {
return s.buffer
}
func (s *Subscriber) Close() {
s.once.Do(func() {
close(s.buffer)
})
}
func newSubscriber() *Subscriber {
sub := &Subscriber{
buffer: make(chan any, 200),
}
return sub
}

View File

@@ -109,7 +109,7 @@ func general(k *Key) error {
if err != nil { if err != nil {
return err return err
} }
log.SetLevel(level) log.SetLogger(log.Must(log.NewLeveled(level)))
if k.Interface != "" { if k.Interface != "" {
iface, err := net.InterfaceByName(k.Interface) iface, err := net.InterfaceByName(k.Interface)
@@ -156,7 +156,7 @@ func restAPI(k *Key) error {
go func() { go func() {
if err := restapi.Start(host, token); err != nil { if err := restapi.Start(host, token); err != nil {
log.Warnf("[RESTAPI] failed to start: %v", err) log.Errorf("[RESTAPI] failed to start: %v", err)
} }
}() }()
log.Infof("[RESTAPI] serve at: %s", u) log.Infof("[RESTAPI] serve at: %s", u)
@@ -175,7 +175,7 @@ func netstack(k *Key) (err error) {
if k.TUNPreUp != "" { if k.TUNPreUp != "" {
log.Infof("[TUN] pre-execute command: `%s`", k.TUNPreUp) log.Infof("[TUN] pre-execute command: `%s`", k.TUNPreUp)
if preUpErr := execCommand(k.TUNPreUp); preUpErr != nil { if preUpErr := execCommand(k.TUNPreUp); preUpErr != nil {
log.Warnf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr) log.Errorf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr)
} }
} }
@@ -185,7 +185,7 @@ func netstack(k *Key) (err error) {
} }
log.Infof("[TUN] post-execute command: `%s`", k.TUNPostUp) log.Infof("[TUN] post-execute command: `%s`", k.TUNPostUp)
if postUpErr := execCommand(k.TUNPostUp); postUpErr != nil { if postUpErr := execCommand(k.TUNPostUp); postUpErr != nil {
log.Warnf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr) log.Errorf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr)
} }
}() }()

4
go.mod
View File

@@ -14,10 +14,10 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.4.1 github.com/gorilla/schema v1.4.1
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
go.uber.org/automaxprocs v1.5.3 go.uber.org/automaxprocs v1.5.3
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.25.0 golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0 golang.org/x/sys v0.22.0
golang.org/x/time v0.5.0 golang.org/x/time v0.5.0
@@ -30,8 +30,8 @@ require (
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
) )

14
go.sum
View File

@@ -1,7 +1,5 @@
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -32,21 +30,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -58,7 +57,6 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQX
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d h1:dFTIljP/5ReqgM7nMR4DauApFatUaSP8r9btX0sd8a8= gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d h1:dFTIljP/5ReqgM7nMR4DauApFatUaSP8r9btX0sd8a8=

2
log/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package log is a thin wrapper based on "go.uber.org/zap".
package log

View File

@@ -1,7 +1,6 @@
package log package log
import ( import (
"io"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@@ -9,26 +8,30 @@ import (
glog "gvisor.dev/gvisor/pkg/log" glog "gvisor.dev/gvisor/pkg/log"
) )
var _globalE = &emitter{}
func init() { func init() {
EnableStackLog(true) glog.SetTarget(_globalE)
} }
func EnableStackLog(v bool) { type emitter struct {
if v { logger *SugaredLogger
glog.SetTarget(&emitter{}) // built-in logger
} else {
glog.SetTarget(&glog.Writer{Next: io.Discard})
}
} }
type emitter struct{} func (e *emitter) setLogger(logger *SugaredLogger) {
e.logger = logger.WithOptions(pkgCallerSkip)
}
func (emitter) Emit(depth int, level glog.Level, _ time.Time, format string, args ...any) { func (e *emitter) logf(level glog.Level, format string, args ...any) {
e.logger.Logf(1-Level(level), "[STACK] "+format, args...)
}
func (e *emitter) Emit(depth int, level glog.Level, _ time.Time, format string, args ...any) {
if _, file, line, ok := runtime.Caller(depth + 1); ok { if _, file, line, ok := runtime.Caller(depth + 1); ok {
// Ignore (*gonet.TCPConn).RemoteAddr() warning: `ep.GetRemoteAddress() failed`. // Ignore: gvisor.dev/gvisor/pkg/tcpip/adapters/gonet/gonet.go:457
if line == 457 && strings.HasSuffix(file, "/pkg/tcpip/adapters/gonet/gonet.go") { if line == 457 && strings.HasSuffix(file, "gonet/gonet.go") {
return return
} }
} }
logf(Level(level)+2, "[STACK] "+format, args...) e.logf(level, format, args...)
} }

View File

@@ -1,39 +0,0 @@
package log
import (
"fmt"
"time"
"github.com/xjasonlyu/tun2socks/v2/common/observable"
)
var (
_logCh = make(chan any)
_source = observable.NewObservable(_logCh)
)
type Event struct {
Level Level `json:"level"`
Message string `json:"msg"`
Time time.Time `json:"time"`
}
func newEvent(level Level, format string, args ...any) *Event {
event := &Event{
Level: level,
Time: time.Now(),
Message: fmt.Sprintf(format, args...),
}
_logCh <- event /* send all events to logCh */
return event
}
func Subscribe() observable.Subscription {
sub, _ := _source.Subscribe()
return sub
}
func UnSubscribe(sub observable.Subscription) {
_source.UnSubscribe(sub)
}

View File

@@ -1,72 +1,31 @@
package log package log
import ( import (
"encoding/json" "go.uber.org/zap/zapcore"
"fmt"
"strings"
) )
type Level uint32 // Level is an alias for zapcore.Level.
type Level = zapcore.Level
// Levels are aliases for Level.
const ( const (
SilentLevel Level = iota DebugLevel = zapcore.DebugLevel
ErrorLevel InfoLevel = zapcore.InfoLevel
WarnLevel WarnLevel = zapcore.WarnLevel
InfoLevel ErrorLevel = zapcore.ErrorLevel
DebugLevel DPanicLevel = zapcore.DPanicLevel
PanicLevel = zapcore.PanicLevel
FatalLevel = zapcore.FatalLevel
InvalidLevel = zapcore.InvalidLevel
SilentLevel = InvalidLevel + 1
) )
// UnmarshalJSON deserialize Level with json // ParseLevel is a thin wrapper for zapcore.ParseLevel.
func (level *Level) UnmarshalJSON(data []byte) error { func ParseLevel(text string) (Level, error) {
var lvl string switch text {
if err := json.Unmarshal(data, &lvl); err != nil { case "silent", "SILENT":
return err
}
l, err := ParseLevel(lvl)
if err != nil {
return err
}
*level = l
return nil
}
// MarshalJSON serialize Level with json
func (level Level) MarshalJSON() ([]byte, error) {
return json.Marshal(level.String())
}
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case SilentLevel:
return "silent"
default:
return fmt.Sprintf("not a valid level %d", level)
}
}
func ParseLevel(lvl string) (Level, error) {
switch strings.ToLower(lvl) {
case "silent":
return SilentLevel, nil return SilentLevel, nil
case "error":
return ErrorLevel, nil
case "warning":
return WarnLevel, nil
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
default: default:
return Level(0), fmt.Errorf("not a valid logrus Level: %q", lvl) return zapcore.ParseLevel(text)
} }
} }

View File

@@ -1,63 +1,71 @@
package log package log
import ( import (
"io" "fmt"
"os" "sync"
"github.com/sirupsen/logrus" "go.uber.org/zap"
"go.uber.org/atomic"
) )
// _defaultLevel is package default logging level. // global Logger and SugaredLogger.
var _defaultLevel = atomic.NewUint32(uint32(InfoLevel)) var (
_globalMu sync.RWMutex
_globalL *Logger
_globalS *SugaredLogger
)
func init() { func init() {
logrus.SetOutput(os.Stdout) SetLogger(zap.Must(zap.NewProduction()))
logrus.SetLevel(logrus.DebugLevel)
} }
func SetOutput(out io.Writer) { func NewLeveled(l Level, options ...Option) (*Logger, error) {
logrus.SetOutput(out) switch l {
} case SilentLevel:
return zap.NewNop(), nil
func SetLevel(level Level) {
_defaultLevel.Store(uint32(level))
}
func Debugf(format string, args ...any) {
logf(DebugLevel, format, args...)
}
func Infof(format string, args ...any) {
logf(InfoLevel, format, args...)
}
func Warnf(format string, args ...any) {
logf(WarnLevel, format, args...)
}
func Errorf(format string, args ...any) {
logf(ErrorLevel, format, args...)
}
func Fatalf(format string, args ...any) {
logrus.Fatalf(format, args...)
}
func logf(level Level, format string, args ...any) {
event := newEvent(level, format, args...)
if uint32(event.Level) > _defaultLevel.Load() {
return
}
switch level {
case DebugLevel: case DebugLevel:
logrus.WithTime(event.Time).Debugln(event.Message) return zap.NewDevelopment(options...)
case InfoLevel: case InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel:
logrus.WithTime(event.Time).Infoln(event.Message) cfg := zap.NewProductionConfig()
case WarnLevel: cfg.Level.SetLevel(l)
logrus.WithTime(event.Time).Warnln(event.Message) return cfg.Build(options...)
case ErrorLevel: default:
logrus.WithTime(event.Time).Errorln(event.Message) return nil, fmt.Errorf("invalid level: %s", l)
} }
} }
// SetLogger sets the global Logger and SugaredLogger.
func SetLogger(logger *Logger) {
_globalMu.Lock()
defer _globalMu.Unlock()
// apply pkgCallerSkip to global loggers.
_globalL = logger.WithOptions(pkgCallerSkip)
_globalS = _globalL.Sugar()
_globalE.setLogger(_globalS)
}
func logf(lvl Level, template string, args ...any) {
_globalMu.RLock()
s := _globalS
_globalMu.RUnlock()
s.Logf(lvl, template, args...)
}
func Debugf(template string, args ...any) {
logf(DebugLevel, template, args...)
}
func Infof(template string, args ...any) {
logf(InfoLevel, template, args...)
}
func Warnf(template string, args ...any) {
logf(WarnLevel, template, args...)
}
func Errorf(template string, args ...any) {
logf(ErrorLevel, template, args...)
}
func Fatalf(template string, args ...any) {
logf(FatalLevel, template, args...)
}

22
log/zap.go Normal file
View File

@@ -0,0 +1,22 @@
package log
import (
"go.uber.org/zap"
)
// Must is an alias for zap.Must.
var Must = zap.Must
// logger aliases for zap.Logger and zap.SugaredLogger.
type (
Logger = zap.Logger
SugaredLogger = zap.SugaredLogger
)
type (
// Option is an alias for zap.Option.
Option = zap.Option
)
// pkgCallerSkip skips the pkg wrapper code as the caller.
var pkgCallerSkip = zap.AddCallerSkip(2)

View File

@@ -30,7 +30,7 @@ func init() {
flag.StringVar(&configFile, "config", "", "YAML format configuration file") flag.StringVar(&configFile, "config", "", "YAML format configuration file")
flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name") flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name")
flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)") flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)")
flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warning|error|silent]") flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warn|error|silent]")
flag.StringVar(&key.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]") flag.StringVar(&key.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]")
flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address") flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address")
flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack") flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack")

View File

@@ -17,7 +17,7 @@ import (
const defaultInterval = 1000 const defaultInterval = 1000
func init() { func init() {
registerMountPoint("/connections", connectionRouter()) registerEndpoint("/connections", connectionRouter())
} }
func connectionRouter() http.Handler { func connectionRouter() http.Handler {

View File

@@ -10,7 +10,7 @@ import (
) )
func init() { func init() {
registerMountPoint("/debug/pprof/", pprofRouter()) registerEndpoint("/debug/pprof/", pprofRouter())
} }
func pprofRouter() http.Handler { func pprofRouter() http.Handler {

View File

@@ -19,7 +19,7 @@ func SetStatsFunc(s func() tcpip.Stats) {
} }
func init() { func init() {
registerMountPoint("/netstats", http.HandlerFunc(getNetStats)) registerEndpoint("/netstats", http.HandlerFunc(getNetStats))
} }
func getNetStats(w http.ResponseWriter, r *http.Request) { func getNetStats(w http.ResponseWriter, r *http.Request) {

View File

@@ -14,7 +14,6 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
V "github.com/xjasonlyu/tun2socks/v2/internal/version" V "github.com/xjasonlyu/tun2socks/v2/internal/version"
"github.com/xjasonlyu/tun2socks/v2/log"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
) )
@@ -25,11 +24,11 @@ var (
}, },
} }
_mountPoints = make(map[string]http.Handler) _endpoints = make(map[string]http.Handler)
) )
func registerMountPoint(pattern string, handler http.Handler) { func registerEndpoint(pattern string, handler http.Handler) {
_mountPoints[pattern] = handler _endpoints[pattern] = handler
} }
func Start(addr, token string) error { func Start(addr, token string) error {
@@ -46,11 +45,10 @@ func Start(addr, token string) error {
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
r.Use(authenticator(token)) r.Use(authenticator(token))
r.Get("/", hello) r.Get("/", hello)
r.Get("/logs", getLogs)
r.Get("/traffic", traffic) r.Get("/traffic", traffic)
r.Get("/version", version) r.Get("/version", version)
// attach HTTP handlers // attach HTTP handlers
for pattern, handler := range _mountPoints { for pattern, handler := range _endpoints {
r.Mount(pattern, handler) r.Mount(pattern, handler)
} }
}) })
@@ -103,61 +101,6 @@ func authenticator(token string) func(http.Handler) http.Handler {
} }
} }
func getLogs(w http.ResponseWriter, r *http.Request) {
lvl := r.URL.Query().Get("level")
if lvl == "" {
lvl = "info" /* default */
}
level, err := log.ParseLevel(lvl)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
wsConn, err = _upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
}
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
sub := log.Subscribe()
defer log.UnSubscribe(sub)
buf := &bytes.Buffer{}
for elm := range sub {
buf.Reset()
e := elm.(*log.Event)
if e.Level > level {
continue
}
if err = json.NewEncoder(buf).Encode(e); err != nil {
break
}
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err != nil {
break
}
}
}
func traffic(w http.ResponseWriter, r *http.Request) { func traffic(w http.ResponseWriter, r *http.Request) {
var ( var (
err error err error