Merge pull request #37 from divebomb/master

Add: listen on random local port
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 0000000..3e01441
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,19 @@
+---
+name: Bug Report
+about: Report a bug
+labels: kind/bug
+
+---
+
+<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
+
+-->
+
+
+**What happened**:
+
+**What you expected to happen**:
+
+**How to reproduce it (as minimally and precisely as possible)**:
+
+**Anything else we need to know?**:
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md
new file mode 100644
index 0000000..6d1a6d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement.md
@@ -0,0 +1,11 @@
+---
+name: Enhancement Request
+about: Suggest an enhancement
+labels: kind/feature
+
+---
+<!-- Please only use this template for submitting enhancement requests -->
+
+**What would you like to be added**:
+
+**Why is this needed**:
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..3eb1ec0
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,24 @@
+<!--  Thanks for sending a pull request! 
+-->
+
+**What this PR does**:
+
+**Which issue(s) this PR fixes**:
+<!--
+*Automatically closes linked issue when PR is merged.
+Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
+_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
+-->
+Fixes #
+
+**Special notes for your reviewer**:
+
+**Does this PR introduce a user-facing change?**:
+<!--
+If no, just write "NONE" in the release-note block below.
+If yes, a release note is required:
+Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
+-->
+```release-note
+
+```
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index aff461c..d542282 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,11 +1,16 @@
 language: go
 
+os:
+  - linux
+
 go:
   - "1.13"
 
 env:
   - GO111MODULE=on
 
+install: true
+
 script:
   - go fmt ./... && [[ -z `git status -s` ]]
   - go mod vendor && go test $(go list ./... | grep -v vendor | grep -v demo) -coverprofile=coverage.txt -covermode=atomic
diff --git a/client.go b/client.go
index a3d5fe8..5e7a45d 100644
--- a/client.go
+++ b/client.go
@@ -153,7 +153,7 @@
 			return newTCPSession(conn, c)
 		}
 
-		log.Infof("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, connectTimeout, err)
+		log.Infof("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, connectTimeout, perrors.WithStack(err))
 		<-wheel.After(connectInterval)
 	}
 }
@@ -185,7 +185,7 @@
 			err = errSelfConnect
 		}
 		if err != nil {
-			log.Warnf("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, err)
+			log.Warnf("net.DialTimeout(addr:%s, timeout:%v) = error:%+v", c.addr, perrors.WithStack(err))
 			<-wheel.After(connectInterval)
 			continue
 		}
@@ -194,7 +194,7 @@
 		conn.SetWriteDeadline(time.Now().Add(1e9))
 		if length, err = conn.Write(connectPingPackage[:]); err != nil {
 			conn.Close()
-			log.Warnf("conn.Write(%s) = {length:%d, err:%+v}", string(connectPingPackage), length, err)
+			log.Warnf("conn.Write(%s) = {length:%d, err:%+v}", string(connectPingPackage), length, perrors.WithStack(err))
 			<-wheel.After(connectInterval)
 			continue
 		}
@@ -204,7 +204,7 @@
 			err = nil
 		}
 		if err != nil {
-			log.Infof("conn{%#v}.Read() = {length:%d, err:%+v}", conn, length, err)
+			log.Infof("conn{%#v}.Read() = {length:%d, err:%+v}", conn, length, perrors.WithStack(err))
 			conn.Close()
 			<-wheel.After(connectInterval)
 			continue
@@ -229,7 +229,7 @@
 			return nil
 		}
 		conn, _, err = dialer.Dial(c.addr, nil)
-		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, err)
+		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, perrors.WithStack(err))
 		if err == nil && gxnet.IsSameAddr(conn.RemoteAddr(), conn.LocalAddr()) {
 			conn.Close()
 			err = errSelfConnect
@@ -243,7 +243,7 @@
 			return ss
 		}
 
-		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, err)
+		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, perrors.WithStack(err))
 		<-wheel.After(connectInterval)
 	}
 }
@@ -269,7 +269,7 @@
 	if c.cert != "" {
 		certPEMBlock, err := ioutil.ReadFile(c.cert)
 		if err != nil {
-			panic(fmt.Sprintf("ioutil.ReadFile(cert:%s) = error:%+v", c.cert, err))
+			panic(fmt.Sprintf("ioutil.ReadFile(cert:%s) = error:%+v", c.cert, perrors.WithStack(err)))
 		}
 
 		var cert tls.Certificate
@@ -291,7 +291,7 @@
 	for _, c := range config.Certificates {
 		roots, err = x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
 		if err != nil {
-			panic(fmt.Sprintf("error parsing server's root cert: %+v\n", err))
+			panic(fmt.Sprintf("error parsing server's root cert: %+v\n", perrors.WithStack(err)))
 		}
 		for _, root = range roots {
 			certPool.AddCert(root)
@@ -321,7 +321,7 @@
 			return ss
 		}
 
-		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, err)
+		log.Infof("websocket.dialer.Dial(addr:%s) = error:%+v", c.addr, perrors.WithStack(err))
 		<-wheel.After(connectInterval)
 	}
 }
@@ -387,7 +387,7 @@
 	}
 }
 
-// there are two methods to keep connection pool. the first approch is like
+// there are two methods to keep connection pool. the first approach is like
 // redigo's lazy connection pool(https://github.com/gomodule/redigo/blob/master/redis/pool.go:),
 // in which you should apply testOnBorrow to check alive of the connection.
 // the second way is as follows. @RunEventLoop detects the aliveness of the connection
@@ -405,13 +405,11 @@
 func (c *client) reConnect() {
 	var num, max, times, interval int
 
-	// c.Lock()
 	max = c.number
 	interval = c.reconnectInterval
 	if interval == 0 {
 		interval = reconnectInterval
 	}
-	// c.Unlock()
 	for {
 		if c.IsClosed() {
 			log.Warnf("client{peer:%s} goroutine exit now.", c.addr)
diff --git a/client_test.go b/client_test.go
index 5ca93f2..f2407a8 100644
--- a/client_test.go
+++ b/client_test.go
@@ -276,6 +276,8 @@
 	beforeWritePkgNum := atomic.LoadUint32(&conn.writePkgNum)
 	err = ss.WriteBytes([]byte("hello"))
 	assert.Equal(t, beforeWritePkgNum+1, atomic.LoadUint32(&conn.writePkgNum))
+	err = ss.WriteBytesArray([]byte("hello"), []byte("hello"))
+	assert.Equal(t, beforeWritePkgNum+3, atomic.LoadUint32(&conn.writePkgNum))
 	err = conn.writePing()
 	assert.Nil(t, err)
 
diff --git a/go.mod b/go.mod
index ffeecdb..be1c736 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,7 @@
 module github.com/dubbogo/getty
 
 require (
-	github.com/dubbogo/gost v1.5.2
+	github.com/dubbogo/gost v1.9.0
 	github.com/golang/snappy v0.0.1
 	github.com/gorilla/websocket v1.4.0
 	github.com/juju/errors v0.0.0-20190930114154-d42613fe1ab9
diff --git a/go.sum b/go.sum
index d0e532e..c6fe58c 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,8 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dubbogo/gost v1.5.2 h1:ri/03971hdpnn3QeCU+4UZgnRNGDXLDGDucR/iozZm8=
 github.com/dubbogo/gost v1.5.2/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
+github.com/dubbogo/gost v1.9.0 h1:UT+dWwvLyJiDotxJERO75jB3Yxgsdy10KztR5ycxRAk=
+github.com/dubbogo/gost v1.9.0/go.mod h1:pPTjVyoJan3aPxBPNUX0ADkXjPibLo+/Ib0/fADXSG8=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
diff --git a/server.go b/server.go
index cb0238a..98d3472 100644
--- a/server.go
+++ b/server.go
@@ -17,6 +17,7 @@
 	"io/ioutil"
 	"net"
 	"net/http"
+	"strings"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -67,10 +68,6 @@
 
 	s.init(opts...)
 
-	if s.addr == "" {
-		panic(fmt.Sprintf("@addr:%s", s.addr))
-	}
-
 	return s
 }
 
@@ -163,9 +160,16 @@
 		streamListener net.Listener
 	)
 
-	streamListener, err = net.Listen("tcp", s.addr)
-	if err != nil {
-		return perrors.Wrapf(err, "net.Listen(tcp, addr:%s))", s.addr)
+	if len(s.addr) == 0 || !strings.Contains(s.addr, ":") {
+		streamListener, err = gxnet.ListenOnTCPRandomPort(s.addr)
+		if err != nil {
+			return perrors.Wrapf(err, "gxnet.ListenOnTCPRandomPort(addr:%s)", s.addr)
+		}
+	} else {
+		streamListener, err = net.Listen("tcp", s.addr)
+		if err != nil {
+			return perrors.Wrapf(err, "net.Listen(tcp, addr:%s)", s.addr)
+		}
 	}
 
 	s.streamListener = streamListener
@@ -180,13 +184,20 @@
 		pktListener *net.UDPConn
 	)
 
-	localAddr, err = net.ResolveUDPAddr("udp", s.addr)
-	if err != nil {
-		return perrors.Wrapf(err, "net.ResolveUDPAddr(udp, addr:%s)", s.addr)
-	}
-	pktListener, err = net.ListenUDP("udp", localAddr)
-	if err != nil {
-		return perrors.Wrapf(err, "net.ListenUDP((udp, localAddr:%#v)", localAddr)
+	if len(s.addr) == 0 || !strings.Contains(s.addr, ":") {
+		pktListener, err = gxnet.ListenOnUDPRandomPort(s.addr)
+		if err != nil {
+			return perrors.Wrapf(err, "gxnet.ListenOnUDPRandomPort(addr:%s)", s.addr)
+		}
+	} else {
+		localAddr, err = net.ResolveUDPAddr("udp", s.addr)
+		if err != nil {
+			return perrors.Wrapf(err, "net.ResolveUDPAddr(udp, addr:%s)", s.addr)
+		}
+		pktListener, err = net.ListenUDP("udp", localAddr)
+		if err != nil {
+			return perrors.Wrapf(err, "net.ListenUDP((udp, localAddr:%#v)", localAddr)
+		}
 	}
 
 	s.pktListener = pktListener
@@ -213,7 +224,7 @@
 	}
 	if gxnet.IsSameAddr(conn.RemoteAddr(), conn.LocalAddr()) {
 		log.Warnf("conn.localAddr{%s} == conn.RemoteAddr", conn.LocalAddr().String(), conn.RemoteAddr().String())
-		return nil, errSelfConnect
+		return nil, perrors.WithStack(errSelfConnect)
 	}
 
 	ss := newTCPSession(conn, s)
@@ -237,7 +248,7 @@
 		)
 		for {
 			if s.IsClosed() {
-				log.Warnf("server{%s} stop acceptting client connect request.", s.addr)
+				log.Warnf("server{%s} stop accepting client connect request.", s.addr)
 				return
 			}
 			if delay != 0 {
@@ -256,7 +267,7 @@
 					}
 					continue
 				}
-				log.Warnf("server{%s}.Accept() = err {%+v}", s.addr, err)
+				log.Warnf("server{%s}.Accept() = err {%+v}", s.addr, perrors.WithStack(err))
 				continue
 			}
 			delay = 0
@@ -357,8 +368,7 @@
 		s.lock.Unlock()
 		err = server.Serve(s.streamListener)
 		if err != nil {
-			log.Errorf("http.server.Serve(addr{%s}) = err{%+v}", s.addr, err)
-			// panic(err)
+			log.Errorf("http.server.Serve(addr{%s}) = err:%+v", s.addr, perrors.WithStack(err))
 		}
 	}()
 }
@@ -380,8 +390,8 @@
 		defer s.wg.Done()
 
 		if certificate, err = tls.LoadX509KeyPair(s.cert, s.privateKey); err != nil {
-			panic(fmt.Sprintf("tls.LoadX509KeyPair(cert{%s}, privateKey{%s}) = err{%+v}",
-				s.cert, s.privateKey, err))
+			panic(fmt.Sprintf("tls.LoadX509KeyPair(cert{%s}, privateKey{%s}) = err:%+v",
+				s.cert, s.privateKey, perrors.WithStack(err)))
 			return
 		}
 		config = &tls.Config{
@@ -394,7 +404,7 @@
 		if s.caCert != "" {
 			certPem, err = ioutil.ReadFile(s.caCert)
 			if err != nil {
-				panic(fmt.Errorf("ioutil.ReadFile(certFile{%s}) = err{%+v}", s.caCert, err))
+				panic(fmt.Errorf("ioutil.ReadFile(certFile{%s}) = err:%+v", s.caCert, perrors.WithStack(err)))
 			}
 			certPool = x509.NewCertPool()
 			if ok := certPool.AppendCertsFromPEM(certPem); !ok {
@@ -419,7 +429,7 @@
 		s.lock.Unlock()
 		err = server.Serve(tls.NewListener(s.streamListener, config))
 		if err != nil {
-			log.Errorf("http.server.Serve(addr{%s}) = err{%+v}", s.addr, err)
+			log.Errorf("http.server.Serve(addr{%s}) = err:%+v", s.addr, perrors.WithStack(err))
 			panic(err)
 		}
 	}()
@@ -429,7 +439,7 @@
 // @newSession: new connection callback
 func (s *server) RunEventLoop(newSession NewSessionCallback) {
 	if err := s.listen(); err != nil {
-		panic(fmt.Errorf("server.listen() = error:%+v", err))
+		panic(fmt.Errorf("server.listen() = error:%+v", perrors.WithStack(err)))
 	}
 
 	switch s.endPointType {
diff --git a/server_test.go b/server_test.go
index 8d9a3bb..5643380 100644
--- a/server_test.go
+++ b/server_test.go
@@ -9,16 +9,16 @@
 	"github.com/stretchr/testify/assert"
 )
 
-func TestTCPServer(t *testing.T) {
+func testTCPServer(t *testing.T, address string) {
 	var (
 		server           *server
 		serverMsgHandler MessageHandler
 	)
-	addr := "127.0.0.1:0"
+
 	func() {
 		server = newServer(
 			TCP_SERVER,
-			WithLocalAddress(addr),
+			WithLocalAddress(address),
 		)
 		newServerSession := func(session Session) error {
 			return newSessionCallback(session, &serverMsgHandler)
@@ -26,11 +26,12 @@
 		server.RunEventLoop(newServerSession)
 		assert.True(t, server.ID() > 0)
 		assert.True(t, server.EndPointType() == TCP_SERVER)
+		assert.NotNil(t, server.streamListener)
 	}()
 	time.Sleep(500e6)
 
-	addr = server.streamListener.Addr().String()
-	t.Logf("server addr: %v", addr)
+	addr := server.streamListener.Addr().String()
+	t.Logf("@address:%s, tcp server addr: %v", address, addr)
 	clt := newClient(TCP_CLIENT,
 		WithServerAddress(addr),
 		WithReconnectInterval(5e8),
@@ -58,16 +59,15 @@
 	assert.True(t, server.IsClosed())
 }
 
-func TestUDPServer(t *testing.T) {
+func testUDPServer(t *testing.T, address string) {
 	var (
 		server           *server
 		serverMsgHandler MessageHandler
 	)
-	addr := "127.0.0.1:0"
 	func() {
 		server = newServer(
 			UDP_ENDPOINT,
-			WithLocalAddress(addr),
+			WithLocalAddress(address),
 		)
 		newServerSession := func(session Session) error {
 			return newSessionCallback(session, &serverMsgHandler)
@@ -75,34 +75,25 @@
 		server.RunEventLoop(newServerSession)
 		assert.True(t, server.ID() > 0)
 		assert.True(t, server.EndPointType() == UDP_ENDPOINT)
+		assert.NotNil(t, server.pktListener)
 	}()
 	time.Sleep(500e6)
 
-	//addr = server.streamListener.Addr().String()
-	//t.Logf("server addr: %v", addr)
-	//clt := newClient(TCP_CLIENT,
-	//	WithServerAddress(addr),
-	//	WithReconnectInterval(5e8),
-	//	WithConnectionNumber(1),
-	//)
-	//assert.NotNil(t, clt)
-	//assert.True(t, clt.ID() > 0)
-	//assert.Equal(t, clt.endPointType, TCP_CLIENT)
-	//
-	//var (
-	//	msgHandler MessageHandler
-	//)
-	//cb := func(session Session) error {
-	//	return newSessionCallback(session, &msgHandler)
-	//}
-	//
-	//clt.RunEventLoop(cb)
-	//time.Sleep(1e9)
-	//
-	//assert.Equal(t, 1, msgHandler.SessionNumber())
-	//clt.Close()
-	//assert.True(t, clt.IsClosed())
-	//
-	//server.Close()
-	//assert.True(t, server.IsClosed())
+	addr := server.pktListener.LocalAddr().String()
+	t.Logf("@address:%s, udp server addr: %v", address, addr)
+}
+
+func TestServer(t *testing.T) {
+	var addr string
+
+	testTCPServer(t, addr)
+	testUDPServer(t, addr)
+
+	addr = "127.0.0.1:0"
+	testTCPServer(t, addr)
+	testUDPServer(t, addr)
+
+	addr = "127.0.0.1"
+	testTCPServer(t, addr)
+	testUDPServer(t, addr)
 }
diff --git a/session.go b/session.go
index 5e9dc3f..c3190e7 100644
--- a/session.go
+++ b/session.go
@@ -12,6 +12,7 @@
 import (
 	"bytes"
 	"fmt"
+	jerrors "github.com/juju/errors"
 	"io"
 	"net"
 	"runtime"
@@ -387,7 +388,7 @@
 	if timeout <= 0 {
 		pkgBytes, err := s.writer.Write(s, pkg)
 		if err != nil {
-			log.Warnf("%s, [session.WritePkg] session.writer.Write(@pkg:%#v) = error:%v", s.Stat(), pkg, err)
+			log.Warnf("%s, [session.WritePkg] session.writer.Write(@pkg:%#v) = error:%+v", s.Stat(), pkg, err)
 			return perrors.WithStack(err)
 		}
 		var udpCtxPtr *UDPContext
@@ -404,7 +405,7 @@
 		}
 		_, err = s.Connection.send(pkg)
 		if err != nil {
-			log.Warnf("%s, [session.WritePkg] @s.Connection.Write(pkg:%#v) = err:%v", s.Stat(), pkg, err)
+			log.Warnf("%s, [session.WritePkg] @s.Connection.Write(pkg:%#v) = err:%+v", s.Stat(), pkg, err)
 			return perrors.WithStack(err)
 		}
 		return nil
@@ -445,10 +446,47 @@
 		return s.WriteBytes(pkgs[0])
 	}
 
-	// TODO Currently, only TCP is supported.
-	if _, err := s.Connection.send(pkgs); err != nil {
-		return perrors.Wrapf(err, "s.Connection.Write(pkgs num:%d)", len(pkgs))
+	// reduce syscall and memcopy for multiple packages
+	if _, ok := s.Connection.(*gettyTCPConn); ok {
+		if _, err := s.Connection.send(pkgs); err != nil {
+			return perrors.Wrapf(err, "s.Connection.Write(pkgs num:%d)", len(pkgs))
+		}
 	}
+
+	// get len
+	var (
+		l      int
+		err    error
+		length int
+		arrp   *[]byte
+		arr    []byte
+	)
+	length = 0
+	for i := 0; i < len(pkgs); i++ {
+		length += len(pkgs[i])
+	}
+
+	// merge the pkgs
+	//arr = make([]byte, length)
+	arrp = gxbytes.AcquireBytes(length)
+	defer gxbytes.ReleaseBytes(arrp)
+	arr = *arrp
+
+	l = 0
+	for i := 0; i < len(pkgs); i++ {
+		copy(arr[l:], pkgs[i])
+		l += len(pkgs[i])
+	}
+
+	if err = s.WriteBytes(arr); err != nil {
+		return jerrors.Trace(err)
+	}
+
+	num := len(pkgs) - 1
+	for i := 0; i < num; i++ {
+		s.incWritePkgNum()
+	}
+
 	return nil
 }
 
@@ -554,7 +592,7 @@
 			for idx := 0; idx < maxIovecNum; idx++ {
 				pkgBytes, err = s.writer.Write(s, outPkg)
 				if err != nil {
-					log.Errorf("%s, [session.handleLoop] = error:%+v", s.sessionToken(), err)
+					log.Errorf("%s, [session.handleLoop] = error:%+v", s.sessionToken(), jerrors.ErrorStack(err))
 					s.stop()
 					// break LOOP
 					flag = false
@@ -582,7 +620,7 @@
 			err = s.WriteBytesArray(iovec[:]...)
 			if err != nil {
 				log.Errorf("%s, [session.handleLoop]s.WriteBytesArray(iovec len:%d) = error:%+v",
-					s.sessionToken(), len(iovec), err)
+					s.sessionToken(), len(iovec), jerrors.ErrorStack(err))
 				s.stop()
 				// break LOOP
 				flag = false
@@ -593,7 +631,7 @@
 				if wsFlag {
 					err := wsConn.writePing()
 					if err != nil {
-						log.Warnf("wsConn.writePing() = error{%s}", err)
+						log.Warnf("wsConn.writePing() = error:%+v", perrors.WithStack(err))
 					}
 				}
 				s.listener.OnCron(s)
@@ -632,7 +670,7 @@
 		log.Infof("%s, [session.handlePackage] gr will exit now, left gr num %d", s.sessionToken(), grNum)
 		s.stop()
 		if err != nil {
-			log.Errorf("%s, [session.handlePackage] error:%+v", s.sessionToken(), err)
+			log.Errorf("%s, [session.handlePackage] error:%+v", s.sessionToken(), perrors.WithStack(err))
 			if s != nil || s.listener != nil {
 				s.listener.OnError(s, err)
 			}
@@ -703,12 +741,12 @@
 					break
 				}
 				if perrors.Cause(err) == io.EOF {
-					log.Infof("%s, [session.conn.read] = error:%+v", s.sessionToken(), err)
+					log.Infof("%s, [session.conn.read] = error:%+v", s.sessionToken(), perrors.WithStack(err))
 					err = nil
 					exit = true
 					break
 				}
-				log.Errorf("%s, [session.conn.read] = error:%+v", s.sessionToken(), err)
+				log.Errorf("%s, [session.conn.read] = error:%+v", s.sessionToken(), perrors.WithStack(err))
 				exit = true
 			}
 			break
@@ -784,7 +822,7 @@
 		}
 
 		bufLen, addr, err = conn.recv(buf)
-		log.Debugf("conn.read() = bufLen:%d, addr:%#v, err:%+v", bufLen, addr, err)
+		log.Debugf("conn.read() = bufLen:%d, addr:%#v, err:%+v", bufLen, addr, perrors.WithStack(err))
 		if netError, ok = perrors.Cause(err).(net.Error); ok && netError.Timeout() {
 			continue
 		}
@@ -796,7 +834,7 @@
 		}
 
 		if bufLen == 0 {
-			log.Errorf("conn.read() = bufLen:%d, addr:%s, err:%+v", bufLen, addr, err)
+			log.Errorf("conn.read() = bufLen:%d, addr:%s, err:%+v", bufLen, addr, perrors.WithStack(err))
 			continue
 		}
 
@@ -806,17 +844,17 @@
 		}
 
 		pkg, pkgLen, err = s.reader.Read(s, buf[:bufLen])
-		log.Debugf("s.reader.Read() = pkg:%#v, pkgLen:%d, err:%+v", pkg, pkgLen, err)
+		log.Debugf("s.reader.Read() = pkg:%#v, pkgLen:%d, err:%+v", pkg, pkgLen, perrors.WithStack(err))
 		if err == nil && s.maxMsgLen > 0 && bufLen > int(s.maxMsgLen) {
 			err = perrors.Errorf("Message Too Long, bufLen %d, session max message len %d", bufLen, s.maxMsgLen)
 		}
 		if err != nil {
-			log.Warnf("%s, [session.handleUDPPackage] = len{%d}, error:%+v",
-				s.sessionToken(), pkgLen, err)
+			log.Warnf("%s, [session.handleUDPPackage] = len:%d, error:%+v",
+				s.sessionToken(), pkgLen, perrors.WithStack(err))
 			continue
 		}
 		if pkgLen == 0 {
-			log.Errorf("s.reader.Read() = pkg:%#v, pkgLen:%d, err:%+v", pkg, pkgLen, err)
+			log.Errorf("s.reader.Read() = pkg:%#v, pkgLen:%d, err:%+v", pkg, pkgLen, perrors.WithStack(err))
 			continue
 		}
 
@@ -849,7 +887,7 @@
 			continue
 		}
 		if err != nil {
-			log.Warnf("%s, [session.handleWSPackage] = error{%+s}",
+			log.Warnf("%s, [session.handleWSPackage] = error:%+v",
 				s.sessionToken(), perrors.WithStack(err))
 			return perrors.WithStack(err)
 		}
@@ -860,8 +898,8 @@
 				err = perrors.Errorf("Message Too Long, length %d, session max message len %d", length, s.maxMsgLen)
 			}
 			if err != nil {
-				log.Warnf("%s, [session.handleWSPackage] = len{%d}, error:%+v",
-					s.sessionToken(), length, err)
+				log.Warnf("%s, [session.handleWSPackage] = len:%d, error:%+v",
+					s.sessionToken(), length, perrors.WithStack(err))
 				continue
 			}