From dc6a5a29c11e3572e9f9347b096df900a1aa7583 Mon Sep 17 00:00:00 2001
From: zhouwenfeng <wenfeng.zhou@liulishuo.com>
Date: Sat, 31 Aug 2019 21:24:20 +0800
Subject: [PATCH 1/7] fix bad encryption and compression when use xtcp

---
 client/visitor.go | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/client/visitor.go b/client/visitor.go
index 6eb3688c..e87310ce 100644
--- a/client/visitor.go
+++ b/client/visitor.go
@@ -293,18 +293,6 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 		return
 	}
 
-	if sv.cfg.UseEncryption {
-		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
-		if err != nil {
-			sv.Error("create encryption stream error: %v", err)
-			return
-		}
-	}
-
-	if sv.cfg.UseCompression {
-		remote = frpIo.WithCompression(remote)
-	}
-
 	fmuxCfg := fmux.DefaultConfig()
 	fmuxCfg.KeepAliveInterval = 5 * time.Second
 	fmuxCfg.LogOutput = ioutil.Discard
@@ -320,6 +308,18 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 		return
 	}
 
-	frpIo.Join(userConn, muxConn)
+	var muxConnRWCloser io.ReadWriteCloser = muxConn
+	if sv.cfg.UseEncryption {
+		muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
+		if err != nil {
+			sv.Error("create encryption stream error: %v", err)
+			return
+		}
+	}
+	if sv.cfg.UseCompression {
+		muxConnRWCloser = frpIo.WithCompression(muxConnRWCloser)
+	}
+
+	frpIo.Join(userConn, muxConnRWCloser)
 	sv.Debug("join connections closed")
 }

From bf0993d2a68e4f4e10465facdb9a58dc746c8abb Mon Sep 17 00:00:00 2001
From: Guy Lewin <guy.lewin@microsoft.com>
Date: Tue, 1 Oct 2019 15:58:35 -0400
Subject: [PATCH 2/7] Grammer fixes

---
 README.md | 90 +++++++++++++++++++++++++++----------------------------
 1 file changed, 45 insertions(+), 45 deletions(-)

diff --git a/README.md b/README.md
index 2e3dacd9..fe46206a 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
 
-Now it also try to support p2p connect.
+Now it also tries to support p2p connect.
 
 ## Table of Contents
 
@@ -64,7 +64,7 @@ Now it also try to support p2p connect.
 
 ## Status
 
-frp is under development and you can try it with latest release version. Master branch for releasing stable version when dev branch for developing.
+frp is under development and you can try it with the latest release version. 'master' branch is for releasing stable version, 'dev' branch is for development.
 
 **We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
 
@@ -172,7 +172,7 @@ However, we can expose a http or https service using frp.
 
   `./frps -c ./frps.ini`
 
-3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to google dns server `8.8.8.8:53`:
+3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to Google's dns server `8.8.8.8:53`:
 
   ```ini
   # frpc.ini
@@ -197,7 +197,7 @@ However, we can expose a http or https service using frp.
 
 ### Forward unix domain socket
 
-Using tcp port to connect unix domain socket like docker daemon.
+Use tcp port to connect to a unix domain socket (e.g. Docker daemon's socket).
 
 Configure frps same as above.
 
@@ -222,7 +222,7 @@ Configure frps same as above.
 
 ### Expose a simple http file server
 
-A simple way to visit files in the LAN.
+A simple way to browse files in the LAN.
 
 Configure frps same as above.
 
@@ -244,7 +244,7 @@ Configure frps same as above.
   plugin_http_passwd = abc
   ```
 
-2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
+2. Visit `http://x.x.x.x:6000/static/` by your browser, specify correct user and password, so you can see files in `/tmp/file`.
 
 ### Enable HTTPS for local HTTP service
 
@@ -272,13 +272,13 @@ Configure frps same as above.
 
 ### Expose your service in security
 
-For some services, if expose them to the public network directly will be a security risk.
+Some services will be at risk if exposed directly to the public network.
 
-**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
+**stcp(secret tcp)** helps you create a proxy while keeping the service secure.
 
 Configure frps same as above.
 
-1. Start frpc, forward ssh port and `remote_port` is useless:
+1. Start frpc, forward ssh port and `remote_port` are useless:
 
   ```ini
   # frpc.ini
@@ -310,7 +310,7 @@ Configure frps same as above.
   bind_port = 6000
   ```
 
-3. Connect to server in LAN by ssh assuming that username is test:
+3. Connect to server in LAN using ssh assuming that username is test:
 
   `ssh -oPort=6000 test@127.0.0.1`
 
@@ -318,7 +318,7 @@ Configure frps same as above.
 
 **xtcp** is designed for transmitting a large amount of data directly between two client.
 
-Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
+It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
 
 1. Configure a udp port for xtcp:
 
@@ -326,7 +326,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
   bind_udp_port = 7001
   ```
 
-2. Start frpc, forward ssh port and `remote_port` is useless:
+2. Start frpc, forward ssh port and `remote_port` are useless:
 
   ```ini
   # frpc.ini
@@ -358,7 +358,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
   bind_port = 6000
   ```
 
-4. Connect to server in LAN by ssh assuming that username is test:
+4. Connect to server in LAN using ssh assuming that username is test:
 
   `ssh -oPort=6000 test@127.0.0.1`
 
@@ -366,7 +366,7 @@ Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp*
 
 ### Configuration File
 
-You can find features which this document not metioned from full example configuration files.
+You can find features not mentioned in this document from the full example configuration files.
 
 [frps full configuration file](./conf/frps_full.ini)
 
@@ -374,7 +374,7 @@ You can find features which this document not metioned from full example configu
 
 ### Configuration file template
 
-Configuration file tempalte can be rendered using os environments. Template uses Go's standard format.
+Configuration file template can be rendered using os environments. Template uses Go's standard format.
 
 ```ini
 # frpc.ini
@@ -402,7 +402,7 @@ All environments has prefix `.Envs`.
 
 ### Dashboard
 
-Check frp's status and proxies's statistics information by Dashboard.
+Check frp's status and proxies' statistics information by Dashboard.
 
 Configure a port for dashboard to enable this feature:
 
@@ -414,15 +414,15 @@ dashboard_user = admin
 dashboard_pwd = admin
 ```
 
-Then visit `http://[server_addr]:7500` to see dashboard, default username and password are both `admin`.
+Then visit `http://[server_addr]:7500` to see the dashboard, default username and password are both `admin`.
 
 ![dashboard](/doc/pic/dashboard.png)
 
 ### Admin UI
 
-Admin UI help you check and manage frpc's configure.
+Admin UI help you check and manage frpc's configuration.
 
-Configure a address for admin UI to enable this feature:
+Configure an address for admin UI to enable this feature:
 
 ```ini
 [common]
@@ -436,11 +436,11 @@ Then visit `http://127.0.0.1:7400` to see admin UI, default username and passwor
 
 ### Authentication
 
-`token` in frps.ini and frpc.ini should be same.
+`token` in frps.ini and frpc.ini should be equal.
 
 ### Encryption and Compression
 
-Defalut value is false, you could decide if the proxy will use encryption or compression:
+Default value is false, you could decide if the proxy will use encryption or compression:
 
 ```ini
 # frpc.ini
@@ -454,11 +454,11 @@ use_compression = true
 
 #### TLS
 
-frp support TLS protocol between frpc and frps since v0.25.0.
+frp supports TLS protocol between frpc and frps since v0.25.0.
 
 Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
 
-For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
+For port multiplexing, frp sends a first byte 0x17 to dial a TLS connection.
 
 ### Hot-Reload frpc configuration
 
@@ -473,15 +473,15 @@ admin_port = 7400
 
 Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
 
-**Note that parameters in [common] section won't be modified except 'start' now.**
+**Note that parameters in [common] section won't be modified except 'start'.**
 
 ### Get proxy status from client
 
-Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configure file.
+Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configuration file.
 
 ### Port White List
 
-`allow_ports` in frps.ini is used for preventing abuse of ports:
+`allow_ports` in frps.ini is used to prevent abuse of ports:
 
 ```ini
 # frps.ini
@@ -493,7 +493,7 @@ allow_ports = 2000-3000,3001,3003,4000-50000
 
 ### Port Reuse
 
-Now `vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect connection's protocol and handle it correspondingly.
+`vhost_http_port` and `vhost_https_port` in frps can use same port with `bind_port`. frps will detect the connection's protocol and handle it correspondingly.
 
 We would like to try to allow multiple proxies bind a same remote port with different protocols in the future.
 
@@ -525,7 +525,7 @@ Using kcp in frp:
   kcp_bind_port = 7000
   ```
 
-2. Configure the protocol used in frpc to connect frps:
+2. Configure the protocol used in frpc to connect to frps:
 
   ```ini
   # frpc.ini
@@ -538,7 +538,7 @@ Using kcp in frp:
 
 ### Connection Pool
 
-By default, frps send message to frpc for create a new connection to backward service when getting an user request.If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
+By default, frps sends a message to frpc to create a new connection to the backward service when getting a user request. If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
 
 This feature is fit for a large number of short connections.
 
@@ -585,9 +585,9 @@ group_key = 123
 
 Proxies in same group will accept connections from port 80 randomly.
 
-For `tcp` type, `remote_port` in one group shoud be same.
+For `tcp` type, `remote_port` in the same group should be same.
 
-For `http` type, `custom_domains, subdomain, locations` shoud be same.
+For `http` type, `custom_domains, subdomain, locations` should be same.
 
 ### Health Check
 
@@ -597,7 +597,7 @@ Add `health_check_type = {type}` to enable health check.
 
 **type** can be tcp or http.
 
-Type tcp will dial the service port and type http will send a http rquest to service and require a 200 response.
+Type tcp will dial the service port and type http will send a http request to the service and require a HTTP 200 response.
 
 Type tcp configuration:
 
@@ -611,9 +611,9 @@ remote_port = 6000
 health_check_type = tcp
 # dial timeout seconds
 health_check_timeout_s = 3
-# if continuous failed in 3 times, the proxy will be removed from frps
+# if health check failed 3 times in a row, the proxy will be removed from frps
 health_check_max_failed = 3
-# every 10 seconds will do a health check
+# health check every 10 seconds
 health_check_interval_s = 10
 ```
 
@@ -664,20 +664,20 @@ host_header_rewrite = dev.yourdomain.com
 header_X-From-Where = frp
 ```
 
-Note that params which have prefix `header_` will be added to http request headers.
+Note that parameters that have `header_` prefix will be added to http request headers.
 In this example, it will set header `X-From-Where: frp` to http request.
 
 ### Get Real IP
 
 #### HTTP X-Forwarded-For
 
-Features for http proxy only.
+These features are for http proxy only.
 
-You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
+You can get the user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
 
 #### Proxy Protocol
 
-frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
+frp support Proxy Protocol to send user's real IP to local service. It support all types except UDP.
 
 Here is an example for https service:
 
@@ -777,7 +777,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
 
 ### Range ports mapping
 
-Proxy name has prefix `range:` will support mapping range ports.
+Proxy name that has starts with `range:` will support mapping range ports.
 
 ```ini
 # frpc.ini
@@ -792,9 +792,9 @@ frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
 
 ### Plugin
 
-frpc only forward request to local tcp or udp port by default.
+frpc only forwards request to local tcp or udp port by default.
 
-Plugin is used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
+Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
 
 Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
 
@@ -822,14 +822,14 @@ Interested in getting involved? We would like to help you!
 
 * Take a look at our [issues list](https://github.com/fatedier/frp/issues) and consider sending a Pull Request to **dev branch**.
 * If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request.
-* Sorry for my poor english and improvement for this document is welcome even some typo fix.
-* If you have some wonderful ideas, send email to fatedier@gmail.com.
+* Sorry for my poor English. Improvements for this document are welcome, even some typo fixes.
+* If you have great ideas, send an email to fatedier@gmail.com.
 
-**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.**
+**Note: We prefer you to give your advise in [issues](https://github.com/fatedier/frp/issues), so others with a same question can search it quickly and we don't need to answer them repeatedly.**
 
 ## Donation
 
-If frp help you a lot, you can support us by:
+If frp helps you a lot, you can support us by:
 
 frp QQ group: 606194980
 

From 5f8ed4fc609954366031886b8484daa7337f870f Mon Sep 17 00:00:00 2001
From: Guy Lewin <guy.lewin@microsoft.com>
Date: Wed, 9 Oct 2019 13:41:05 -0700
Subject: [PATCH 3/7] Fix CR

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index fe46206a..dbb977a6 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ Now it also tries to support p2p connect.
 
 ## Status
 
-frp is under development and you can try it with the latest release version. 'master' branch is for releasing stable version, 'dev' branch is for development.
+frp is under development, you can try by using the latest release version under the 'master' branch. You can use the 'dev' branch instead for the version in development.
 
 **We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
 

From 649f47c3455edb914c8a668813706e97e6770bb9 Mon Sep 17 00:00:00 2001
From: fatedier <fatedier@gmail.com>
Date: Sat, 12 Oct 2019 20:13:12 +0800
Subject: [PATCH 4/7] change log method

---
 Makefile                            |   2 +-
 client/control.go                   |  73 +++++++++++-------
 client/health/health.go             |  33 +++-----
 client/proxy/proxy.go               | 115 +++++++++++++++-------------
 client/proxy/proxy_manager.go       |  24 +++---
 client/proxy/proxy_wrapper.go       |  38 +++++----
 client/service.go                   |  49 ++++++++----
 client/visitor.go                   |  82 +++++++++++---------
 client/visitor_manager.go           |  24 +++---
 cmd/frps/root.go                    |   2 +-
 go.sum                              |  14 ----
 models/plugin/http_proxy.go         |   2 +-
 models/plugin/https2http.go         |   3 +-
 models/plugin/plugin.go             |   6 +-
 models/plugin/socks5.go             |   3 +-
 models/plugin/static_file.go        |   3 +-
 models/plugin/unix_domain_socket.go |   4 +-
 server/control.go                   |  80 +++++++++++--------
 server/controller/visitor.go        |   3 +-
 server/group/http.go                |   5 +-
 server/proxy/http.go                |  12 +--
 server/proxy/https.go               |  11 ++-
 server/proxy/proxy.go               |  65 +++++++++-------
 server/proxy/stcp.go                |   4 +-
 server/proxy/tcp.go                 |  14 ++--
 server/proxy/udp.go                 |  23 +++---
 server/proxy/xtcp.go                |  10 ++-
 server/service.go                   |  69 +++++++++--------
 tests/ci/auto_test_frpc.ini         |   3 +-
 tests/ci/auto_test_frps.ini         |   3 +-
 tests/mock/echo_server.go           |   4 +-
 tests/util/util.go                  |   3 +-
 utils/log/log.go                    |  68 ----------------
 utils/net/conn.go                   |  80 +++++++++++--------
 utils/net/kcp.go                    |  22 +++---
 utils/net/listener.go               |  49 +++---------
 utils/net/tcp.go                    | 111 ---------------------------
 utils/net/tls.go                    |  10 +--
 utils/net/udp.go                    |  30 ++++----
 utils/net/websocket.go              |  27 +++----
 utils/vhost/https.go                |   9 +--
 utils/vhost/vhost.go                |  51 ++++++------
 utils/xlog/ctx.go                   |  42 ++++++++++
 utils/xlog/xlog.go                  |  73 ++++++++++++++++++
 44 files changed, 670 insertions(+), 688 deletions(-)
 delete mode 100644 utils/net/tcp.go
 create mode 100644 utils/xlog/ctx.go
 create mode 100644 utils/xlog/xlog.go

diff --git a/Makefile b/Makefile
index f737d3f5..cc6ee6f0 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ file:
 
 fmt:
 	go fmt ./...
-	
+
 frps:
 	go build -o bin/frps ./cmd/frps
 
diff --git a/client/control.go b/client/control.go
index 30992f9a..5589817f 100644
--- a/client/control.go
+++ b/client/control.go
@@ -15,9 +15,11 @@
 package client
 
 import (
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io"
+	"net"
 	"runtime/debug"
 	"sync"
 	"time"
@@ -25,8 +27,8 @@ import (
 	"github.com/fatedier/frp/client/proxy"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
-	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/control/shutdown"
 	"github.com/fatedier/golib/crypto"
@@ -45,7 +47,7 @@ type Control struct {
 	vm *VisitorManager
 
 	// control connection
-	conn frpNet.Conn
+	conn net.Conn
 
 	// tcp stream multiplexing, if enabled
 	session *fmux.Session
@@ -76,12 +78,19 @@ type Control struct {
 
 	mu sync.RWMutex
 
-	log.Logger
+	xl *xlog.Logger
+
+	// service context
+	ctx context.Context
 }
 
-func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg config.ClientCommonConf,
-	pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, serverUDPPort int) *Control {
+func NewControl(ctx context.Context, runId string, conn net.Conn, session *fmux.Session,
+	clientCfg config.ClientCommonConf,
+	pxyCfgs map[string]config.ProxyConf,
+	visitorCfgs map[string]config.VisitorConf,
+	serverUDPPort int) *Control {
 
+	// new xlog instance
 	ctl := &Control{
 		runId:              runId,
 		conn:               conn,
@@ -96,11 +105,12 @@ func NewControl(runId string, conn frpNet.Conn, session *fmux.Session, clientCfg
 		writerShutdown:     shutdown.New(),
 		msgHandlerShutdown: shutdown.New(),
 		serverUDPPort:      serverUDPPort,
-		Logger:             log.NewPrefixLogger(""),
+		xl:                 xlog.FromContextSafe(ctx),
+		ctx:                ctx,
 	}
-	ctl.pm = proxy.NewProxyManager(ctl.sendCh, runId, clientCfg, serverUDPPort)
+	ctl.pm = proxy.NewProxyManager(ctl.ctx, ctl.sendCh, clientCfg, serverUDPPort)
 
-	ctl.vm = NewVisitorManager(ctl)
+	ctl.vm = NewVisitorManager(ctl.ctx, ctl)
 	ctl.vm.Reload(visitorCfgs)
 	return ctl
 }
@@ -117,6 +127,7 @@ func (ctl *Control) Run() {
 }
 
 func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
+	xl := ctl.xl
 	workConn, err := ctl.connectServer()
 	if err != nil {
 		return
@@ -126,31 +137,31 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
 		RunId: ctl.runId,
 	}
 	if err = msg.WriteMsg(workConn, m); err != nil {
-		ctl.Warn("work connection write to server error: %v", err)
+		xl.Warn("work connection write to server error: %v", err)
 		workConn.Close()
 		return
 	}
 
 	var startMsg msg.StartWorkConn
 	if err = msg.ReadMsgInto(workConn, &startMsg); err != nil {
-		ctl.Error("work connection closed, %v", err)
+		xl.Error("work connection closed before response StartWorkConn message: %v", err)
 		workConn.Close()
 		return
 	}
-	workConn.AddLogPrefix(startMsg.ProxyName)
 
 	// dispatch this work connection to related proxy
 	ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
 }
 
 func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
+	xl := ctl.xl
 	// Server will return NewProxyResp message to each NewProxy message.
 	// Start a new proxy handler if no error got
 	err := ctl.pm.StartProxy(inMsg.ProxyName, inMsg.RemoteAddr, inMsg.Error)
 	if err != nil {
-		ctl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
+		xl.Warn("[%s] start error: %v", inMsg.ProxyName, err)
 	} else {
-		ctl.Info("[%s] start proxy success", inMsg.ProxyName)
+		xl.Info("[%s] start proxy success", inMsg.ProxyName)
 	}
 }
 
@@ -169,15 +180,16 @@ func (ctl *Control) ClosedDoneCh() <-chan struct{} {
 }
 
 // connectServer return a new connection to frps
-func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
+func (ctl *Control) connectServer() (conn net.Conn, err error) {
+	xl := ctl.xl
 	if ctl.clientCfg.TcpMux {
 		stream, errRet := ctl.session.OpenStream()
 		if errRet != nil {
 			err = errRet
-			ctl.Warn("start new connection to server error: %v", err)
+			xl.Warn("start new connection to server error: %v", err)
 			return
 		}
-		conn = frpNet.WrapConn(stream)
+		conn = stream
 	} else {
 		var tlsConfig *tls.Config
 		if ctl.clientCfg.TLSEnable {
@@ -188,7 +200,7 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
 		conn, err = frpNet.ConnectServerByProxyWithTLS(ctl.clientCfg.HttpProxy, ctl.clientCfg.Protocol,
 			fmt.Sprintf("%s:%d", ctl.clientCfg.ServerAddr, ctl.clientCfg.ServerPort), tlsConfig)
 		if err != nil {
-			ctl.Warn("start new connection to server error: %v", err)
+			xl.Warn("start new connection to server error: %v", err)
 			return
 		}
 	}
@@ -197,10 +209,11 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
 
 // reader read all messages from frps and send to readCh
 func (ctl *Control) reader() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.Error("panic error: %v", err)
-			ctl.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 	defer ctl.readerShutdown.Done()
@@ -210,10 +223,10 @@ func (ctl *Control) reader() {
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
-				ctl.Debug("read from control connection EOF")
+				xl.Debug("read from control connection EOF")
 				return
 			} else {
-				ctl.Warn("read error: %v", err)
+				xl.Warn("read error: %v", err)
 				ctl.conn.Close()
 				return
 			}
@@ -225,20 +238,21 @@ func (ctl *Control) reader() {
 
 // writer writes messages got from sendCh to frps
 func (ctl *Control) writer() {
+	xl := ctl.xl
 	defer ctl.writerShutdown.Done()
 	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.clientCfg.Token))
 	if err != nil {
-		ctl.conn.Error("crypto new writer error: %v", err)
+		xl.Error("crypto new writer error: %v", err)
 		ctl.conn.Close()
 		return
 	}
 	for {
 		if m, ok := <-ctl.sendCh; !ok {
-			ctl.Info("control writer is closing")
+			xl.Info("control writer is closing")
 			return
 		} else {
 			if err := msg.WriteMsg(encWriter, m); err != nil {
-				ctl.Warn("write message to control connection error: %v", err)
+				xl.Warn("write message to control connection error: %v", err)
 				return
 			}
 		}
@@ -247,10 +261,11 @@ func (ctl *Control) writer() {
 
 // msgHandler handles all channel events and do corresponding operations.
 func (ctl *Control) msgHandler() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.Error("panic error: %v", err)
-			ctl.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 	defer ctl.msgHandlerShutdown.Done()
@@ -266,11 +281,11 @@ func (ctl *Control) msgHandler() {
 		select {
 		case <-hbSend.C:
 			// send heartbeat to server
-			ctl.Debug("send heartbeat to server")
+			xl.Debug("send heartbeat to server")
 			ctl.sendCh <- &msg.Ping{}
 		case <-hbCheck.C:
 			if time.Since(ctl.lastPong) > time.Duration(ctl.clientCfg.HeartBeatTimeout)*time.Second {
-				ctl.Warn("heartbeat timeout")
+				xl.Warn("heartbeat timeout")
 				// let reader() stop
 				ctl.conn.Close()
 				return
@@ -287,7 +302,7 @@ func (ctl *Control) msgHandler() {
 				ctl.HandleNewProxyResp(m)
 			case *msg.Pong:
 				ctl.lastPong = time.Now()
-				ctl.Debug("receive heartbeat from server")
+				xl.Debug("receive heartbeat from server")
 			}
 		}
 	}
diff --git a/client/health/health.go b/client/health/health.go
index 91ea707b..12771411 100644
--- a/client/health/health.go
+++ b/client/health/health.go
@@ -24,7 +24,7 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/xlog"
 )
 
 var (
@@ -50,11 +50,11 @@ type HealthCheckMonitor struct {
 
 	ctx    context.Context
 	cancel context.CancelFunc
-
-	l log.Logger
 }
 
-func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFailedTimes int, addr string, url string,
+func NewHealthCheckMonitor(ctx context.Context, checkType string,
+	intervalS int, timeoutS int, maxFailedTimes int,
+	addr string, url string,
 	statusNormalFn func(), statusFailedFn func()) *HealthCheckMonitor {
 
 	if intervalS <= 0 {
@@ -66,7 +66,7 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
 	if maxFailedTimes <= 0 {
 		maxFailedTimes = 1
 	}
-	ctx, cancel := context.WithCancel(context.Background())
+	newctx, cancel := context.WithCancel(ctx)
 	return &HealthCheckMonitor{
 		checkType:      checkType,
 		interval:       time.Duration(intervalS) * time.Second,
@@ -77,15 +77,11 @@ func NewHealthCheckMonitor(checkType string, intervalS int, timeoutS int, maxFai
 		statusOK:       false,
 		statusNormalFn: statusNormalFn,
 		statusFailedFn: statusFailedFn,
-		ctx:            ctx,
+		ctx:            newctx,
 		cancel:         cancel,
 	}
 }
 
-func (monitor *HealthCheckMonitor) SetLogger(l log.Logger) {
-	monitor.l = l
-}
-
 func (monitor *HealthCheckMonitor) Start() {
 	go monitor.checkWorker()
 }
@@ -95,6 +91,7 @@ func (monitor *HealthCheckMonitor) Stop() {
 }
 
 func (monitor *HealthCheckMonitor) checkWorker() {
+	xl := xlog.FromContextSafe(monitor.ctx)
 	for {
 		doCtx, cancel := context.WithDeadline(monitor.ctx, time.Now().Add(monitor.timeout))
 		err := monitor.doCheck(doCtx)
@@ -109,25 +106,17 @@ func (monitor *HealthCheckMonitor) checkWorker() {
 		}
 
 		if err == nil {
-			if monitor.l != nil {
-				monitor.l.Trace("do one health check success")
-			}
+			xl.Trace("do one health check success")
 			if !monitor.statusOK && monitor.statusNormalFn != nil {
-				if monitor.l != nil {
-					monitor.l.Info("health check status change to success")
-				}
+				xl.Info("health check status change to success")
 				monitor.statusOK = true
 				monitor.statusNormalFn()
 			}
 		} else {
-			if monitor.l != nil {
-				monitor.l.Warn("do one health check failed: %v", err)
-			}
+			xl.Warn("do one health check failed: %v", err)
 			monitor.failedTimes++
 			if monitor.statusOK && int(monitor.failedTimes) >= monitor.maxFailedTimes && monitor.statusFailedFn != nil {
-				if monitor.l != nil {
-					monitor.l.Warn("health check status change to failed")
-				}
+				xl.Warn("health check status change to failed")
 				monitor.statusOK = false
 				monitor.statusFailedFn()
 			}
diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go
index f5e1b603..2da68ce8 100644
--- a/client/proxy/proxy.go
+++ b/client/proxy/proxy.go
@@ -16,6 +16,7 @@ package proxy
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -29,8 +30,8 @@ import (
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/models/plugin"
 	"github.com/fatedier/frp/models/proto/udp"
-	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/errors"
 	frpIo "github.com/fatedier/golib/io"
@@ -44,17 +45,17 @@ type Proxy interface {
 	Run() error
 
 	// InWorkConn accept work connections registered to server.
-	InWorkConn(frpNet.Conn, *msg.StartWorkConn)
+	InWorkConn(net.Conn, *msg.StartWorkConn)
 
 	Close()
-	log.Logger
 }
 
-func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
+func NewProxy(ctx context.Context, pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serverUDPPort int) (pxy Proxy) {
 	baseProxy := BaseProxy{
-		Logger:        log.NewPrefixLogger(pxyConf.GetBaseInfo().ProxyName),
 		clientCfg:     clientCfg,
 		serverUDPPort: serverUDPPort,
+		xl:            xlog.FromContextSafe(ctx),
+		ctx:           ctx,
 	}
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
@@ -93,10 +94,12 @@ func NewProxy(pxyConf config.ProxyConf, clientCfg config.ClientCommonConf, serve
 
 type BaseProxy struct {
 	closed        bool
-	mu            sync.RWMutex
 	clientCfg     config.ClientCommonConf
 	serverUDPPort int
-	log.Logger
+
+	mu  sync.RWMutex
+	xl  *xlog.Logger
+	ctx context.Context
 }
 
 // TCP
@@ -123,8 +126,8 @@ func (pxy *TcpProxy) Close() {
 	}
 }
 
-func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+func (pxy *TcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 		[]byte(pxy.clientCfg.Token), m)
 }
 
@@ -152,8 +155,8 @@ func (pxy *HttpProxy) Close() {
 	}
 }
 
-func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+func (pxy *HttpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 		[]byte(pxy.clientCfg.Token), m)
 }
 
@@ -181,8 +184,8 @@ func (pxy *HttpsProxy) Close() {
 	}
 }
 
-func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+func (pxy *HttpsProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 		[]byte(pxy.clientCfg.Token), m)
 }
 
@@ -210,8 +213,8 @@ func (pxy *StcpProxy) Close() {
 	}
 }
 
-func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
+func (pxy *StcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
 		[]byte(pxy.clientCfg.Token), m)
 }
 
@@ -239,12 +242,13 @@ func (pxy *XtcpProxy) Close() {
 	}
 }
 
-func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
+func (pxy *XtcpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	xl := pxy.xl
 	defer conn.Close()
 	var natHoleSidMsg msg.NatHoleSid
 	err := msg.ReadMsgInto(conn, &natHoleSidMsg)
 	if err != nil {
-		pxy.Error("xtcp read from workConn error: %v", err)
+		xl.Error("xtcp read from workConn error: %v", err)
 		return
 	}
 
@@ -259,7 +263,7 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 
 	err = msg.WriteMsg(clientConn, natHoleClientMsg)
 	if err != nil {
-		pxy.Error("send natHoleClientMsg to server error: %v", err)
+		xl.Error("send natHoleClientMsg to server error: %v", err)
 		return
 	}
 
@@ -270,28 +274,28 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	buf := pool.GetBuf(1024)
 	n, err := clientConn.Read(buf)
 	if err != nil {
-		pxy.Error("get natHoleRespMsg error: %v", err)
+		xl.Error("get natHoleRespMsg error: %v", err)
 		return
 	}
 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
 	if err != nil {
-		pxy.Error("get natHoleRespMsg error: %v", err)
+		xl.Error("get natHoleRespMsg error: %v", err)
 		return
 	}
 	clientConn.SetReadDeadline(time.Time{})
 	clientConn.Close()
 
 	if natHoleRespMsg.Error != "" {
-		pxy.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
+		xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
 		return
 	}
 
-	pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
+	xl.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
 
 	// Send detect message
 	array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
 	if len(array) <= 1 {
-		pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
+		xl.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
 	}
 	laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
 	/*
@@ -301,18 +305,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	*/
 	port, err := strconv.ParseInt(array[1], 10, 64)
 	if err != nil {
-		pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
+		xl.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
 		return
 	}
 	pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
-	pxy.Trace("send all detect msg done")
+	xl.Trace("send all detect msg done")
 
 	msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
 
 	// Listen for clientConn's address and wait for visitor connection
 	lConn, err := net.ListenUDP("udp", laddr)
 	if err != nil {
-		pxy.Error("listen on visitorConn's local adress error: %v", err)
+		xl.Error("listen on visitorConn's local adress error: %v", err)
 		return
 	}
 	defer lConn.Close()
@@ -322,22 +326,22 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	var uAddr *net.UDPAddr
 	n, uAddr, err = lConn.ReadFromUDP(sidBuf)
 	if err != nil {
-		pxy.Warn("get sid from visitor error: %v", err)
+		xl.Warn("get sid from visitor error: %v", err)
 		return
 	}
 	lConn.SetReadDeadline(time.Time{})
 	if string(sidBuf[:n]) != natHoleRespMsg.Sid {
-		pxy.Warn("incorrect sid from visitor")
+		xl.Warn("incorrect sid from visitor")
 		return
 	}
 	pool.PutBuf(sidBuf)
-	pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
+	xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
 
 	lConn.WriteToUDP(sidBuf[:n], uAddr)
 
 	kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
 	if err != nil {
-		pxy.Error("create kcp connection from udp connection error: %v", err)
+		xl.Error("create kcp connection from udp connection error: %v", err)
 		return
 	}
 
@@ -346,18 +350,18 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 	fmuxCfg.LogOutput = ioutil.Discard
 	sess, err := fmux.Server(kcpConn, fmuxCfg)
 	if err != nil {
-		pxy.Error("create yamux server from kcp connection error: %v", err)
+		xl.Error("create yamux server from kcp connection error: %v", err)
 		return
 	}
 	defer sess.Close()
 	muxConn, err := sess.Accept()
 	if err != nil {
-		pxy.Error("accept for yamux connection error: %v", err)
+		xl.Error("accept for yamux connection error: %v", err)
 		return
 	}
 
-	HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
-		frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
+	HandleTcpWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
+		muxConn, []byte(pxy.cfg.Sk), m)
 }
 
 func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
@@ -390,7 +394,7 @@ type UdpProxy struct {
 
 	// include msg.UdpPacket and msg.Ping
 	sendCh   chan msg.Message
-	workConn frpNet.Conn
+	workConn net.Conn
 }
 
 func (pxy *UdpProxy) Run() (err error) {
@@ -419,8 +423,9 @@ func (pxy *UdpProxy) Close() {
 	}
 }
 
-func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
-	pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
+func (pxy *UdpProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) {
+	xl := pxy.xl
+	xl.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
 	// close resources releated with old workConn
 	pxy.Close()
 
@@ -435,32 +440,32 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 		for {
 			var udpMsg msg.UdpPacket
 			if errRet := msg.ReadMsgInto(conn, &udpMsg); errRet != nil {
-				pxy.Warn("read from workConn for udp error: %v", errRet)
+				xl.Warn("read from workConn for udp error: %v", errRet)
 				return
 			}
 			if errRet := errors.PanicToError(func() {
-				pxy.Trace("get udp package from workConn: %s", udpMsg.Content)
+				xl.Trace("get udp package from workConn: %s", udpMsg.Content)
 				readCh <- &udpMsg
 			}); errRet != nil {
-				pxy.Info("reader goroutine for udp work connection closed: %v", errRet)
+				xl.Info("reader goroutine for udp work connection closed: %v", errRet)
 				return
 			}
 		}
 	}
 	workConnSenderFn := func(conn net.Conn, sendCh chan msg.Message) {
 		defer func() {
-			pxy.Info("writer goroutine for udp work connection closed")
+			xl.Info("writer goroutine for udp work connection closed")
 		}()
 		var errRet error
 		for rawMsg := range sendCh {
 			switch m := rawMsg.(type) {
 			case *msg.UdpPacket:
-				pxy.Trace("send udp package to workConn: %s", m.Content)
+				xl.Trace("send udp package to workConn: %s", m.Content)
 			case *msg.Ping:
-				pxy.Trace("send ping message to udp workConn")
+				xl.Trace("send ping message to udp workConn")
 			}
 			if errRet = msg.WriteMsg(conn, rawMsg); errRet != nil {
-				pxy.Error("udp work write error: %v", errRet)
+				xl.Error("udp work write error: %v", errRet)
 				return
 			}
 		}
@@ -472,7 +477,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 			if errRet = errors.PanicToError(func() {
 				sendCh <- &msg.Ping{}
 			}); errRet != nil {
-				pxy.Trace("heartbeat goroutine for udp work connection closed")
+				xl.Trace("heartbeat goroutine for udp work connection closed")
 				break
 			}
 		}
@@ -485,20 +490,22 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
 }
 
 // Common handler for tcp work connections.
-func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
-	baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
-
+func HandleTcpWorkConnection(ctx context.Context, localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
+	baseInfo *config.BaseProxyConf, workConn net.Conn, encKey []byte, m *msg.StartWorkConn) {
+	xl := xlog.FromContextSafe(ctx)
 	var (
 		remote io.ReadWriteCloser
 		err    error
 	)
 	remote = workConn
 
+	xl.Trace("handle tcp work connection, use_encryption: %t, use_compression: %t",
+		baseInfo.UseEncryption, baseInfo.UseCompression)
 	if baseInfo.UseEncryption {
 		remote, err = frpIo.WithEncryption(remote, encKey)
 		if err != nil {
 			workConn.Close()
-			workConn.Error("create encryption stream error: %v", err)
+			xl.Error("create encryption stream error: %v", err)
 			return
 		}
 	}
@@ -541,19 +548,19 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
 
 	if proxyPlugin != nil {
 		// if plugin is set, let plugin handle connections first
-		workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
+		xl.Debug("handle by plugin: %s", proxyPlugin.Name())
 		proxyPlugin.Handle(remote, workConn, extraInfo)
-		workConn.Debug("handle by plugin finished")
+		xl.Debug("handle by plugin finished")
 		return
 	} else {
 		localConn, err := frpNet.ConnectServer("tcp", fmt.Sprintf("%s:%d", localInfo.LocalIp, localInfo.LocalPort))
 		if err != nil {
 			workConn.Close()
-			workConn.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
+			xl.Error("connect to local service [%s:%d] error: %v", localInfo.LocalIp, localInfo.LocalPort, err)
 			return
 		}
 
-		workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
+		xl.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
 			localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
 
 		if len(extraInfo) > 0 {
@@ -561,6 +568,6 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
 		}
 
 		frpIo.Join(localConn, remote)
-		workConn.Debug("join connections closed")
+		xl.Debug("join connections closed")
 	}
 }
diff --git a/client/proxy/proxy_manager.go b/client/proxy/proxy_manager.go
index 1d0d8786..a0afd70a 100644
--- a/client/proxy/proxy_manager.go
+++ b/client/proxy/proxy_manager.go
@@ -1,14 +1,15 @@
 package proxy
 
 import (
+	"context"
 	"fmt"
+	"net"
 	"sync"
 
 	"github.com/fatedier/frp/client/event"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
-	"github.com/fatedier/frp/utils/log"
-	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/errors"
 )
@@ -25,19 +26,17 @@ type ProxyManager struct {
 	// The UDP port that the server is listening on
 	serverUDPPort int
 
-	logPrefix string
-	log.Logger
+	ctx context.Context
 }
 
-func NewProxyManager(msgSendCh chan (msg.Message), logPrefix string, clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
+func NewProxyManager(ctx context.Context, msgSendCh chan (msg.Message), clientCfg config.ClientCommonConf, serverUDPPort int) *ProxyManager {
 	return &ProxyManager{
-		proxies:       make(map[string]*ProxyWrapper),
 		sendCh:        msgSendCh,
+		proxies:       make(map[string]*ProxyWrapper),
 		closed:        false,
 		clientCfg:     clientCfg,
 		serverUDPPort: serverUDPPort,
-		logPrefix:     logPrefix,
-		Logger:        log.NewPrefixLogger(logPrefix),
+		ctx:           ctx,
 	}
 }
 
@@ -65,7 +64,7 @@ func (pm *ProxyManager) Close() {
 	pm.proxies = make(map[string]*ProxyWrapper)
 }
 
-func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
+func (pm *ProxyManager) HandleWorkConn(name string, workConn net.Conn, m *msg.StartWorkConn) {
 	pm.mu.RLock()
 	pw, ok := pm.proxies[name]
 	pm.mu.RUnlock()
@@ -104,6 +103,7 @@ func (pm *ProxyManager) GetAllProxyStatus() []*ProxyStatus {
 }
 
 func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
+	xl := xlog.FromContextSafe(pm.ctx)
 	pm.mu.Lock()
 	defer pm.mu.Unlock()
 
@@ -127,13 +127,13 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
 		}
 	}
 	if len(delPxyNames) > 0 {
-		pm.Info("proxy removed: %v", delPxyNames)
+		xl.Info("proxy removed: %v", delPxyNames)
 	}
 
 	addPxyNames := make([]string, 0)
 	for name, cfg := range pxyCfgs {
 		if _, ok := pm.proxies[name]; !ok {
-			pxy := NewProxyWrapper(cfg, pm.clientCfg, pm.HandleEvent, pm.logPrefix, pm.serverUDPPort)
+			pxy := NewProxyWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.serverUDPPort)
 			pm.proxies[name] = pxy
 			addPxyNames = append(addPxyNames, name)
 
@@ -141,6 +141,6 @@ func (pm *ProxyManager) Reload(pxyCfgs map[string]config.ProxyConf) {
 		}
 	}
 	if len(addPxyNames) > 0 {
-		pm.Info("proxy added: %v", addPxyNames)
+		xl.Info("proxy added: %v", addPxyNames)
 	}
 }
diff --git a/client/proxy/proxy_wrapper.go b/client/proxy/proxy_wrapper.go
index b02e5291..458fa438 100644
--- a/client/proxy/proxy_wrapper.go
+++ b/client/proxy/proxy_wrapper.go
@@ -1,7 +1,9 @@
 package proxy
 
 import (
+	"context"
 	"fmt"
+	"net"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -10,8 +12,7 @@ import (
 	"github.com/fatedier/frp/client/health"
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
-	"github.com/fatedier/frp/utils/log"
-	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/errors"
 )
@@ -62,11 +63,13 @@ type ProxyWrapper struct {
 	healthNotifyCh   chan struct{}
 	mu               sync.RWMutex
 
-	log.Logger
+	xl  *xlog.Logger
+	ctx context.Context
 }
 
-func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, logPrefix string, serverUDPPort int) *ProxyWrapper {
+func NewProxyWrapper(ctx context.Context, cfg config.ProxyConf, clientCfg config.ClientCommonConf, eventHandler event.EventHandler, serverUDPPort int) *ProxyWrapper {
 	baseInfo := cfg.GetBaseInfo()
+	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.ProxyName)
 	pw := &ProxyWrapper{
 		ProxyStatus: ProxyStatus{
 			Name:   baseInfo.ProxyName,
@@ -77,20 +80,19 @@ func NewProxyWrapper(cfg config.ProxyConf, clientCfg config.ClientCommonConf, ev
 		closeCh:        make(chan struct{}),
 		healthNotifyCh: make(chan struct{}),
 		handler:        eventHandler,
-		Logger:         log.NewPrefixLogger(logPrefix),
+		xl:             xl,
+		ctx:            xlog.NewContext(ctx, xl),
 	}
-	pw.AddLogPrefix(pw.Name)
 
 	if baseInfo.HealthCheckType != "" {
 		pw.health = 1 // means failed
-		pw.monitor = health.NewHealthCheckMonitor(baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
+		pw.monitor = health.NewHealthCheckMonitor(pw.ctx, baseInfo.HealthCheckType, baseInfo.HealthCheckIntervalS,
 			baseInfo.HealthCheckTimeoutS, baseInfo.HealthCheckMaxFailed, baseInfo.HealthCheckAddr,
 			baseInfo.HealthCheckUrl, pw.statusNormalCallback, pw.statusFailedCallback)
-		pw.monitor.SetLogger(pw.Logger)
-		pw.Trace("enable health check monitor")
+		xl.Trace("enable health check monitor")
 	}
 
-	pw.pxy = NewProxy(pw.Cfg, clientCfg, serverUDPPort)
+	pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, serverUDPPort)
 	return pw
 }
 
@@ -147,6 +149,7 @@ func (pw *ProxyWrapper) Stop() {
 }
 
 func (pw *ProxyWrapper) checkWorker() {
+	xl := pw.xl
 	if pw.monitor != nil {
 		// let monitor do check request first
 		time.Sleep(500 * time.Millisecond)
@@ -161,7 +164,7 @@ func (pw *ProxyWrapper) checkWorker() {
 				(pw.Status == ProxyStatusWaitStart && now.After(pw.lastSendStartMsg.Add(waitResponseTimeout))) ||
 				(pw.Status == ProxyStatusStartErr && now.After(pw.lastStartErr.Add(startErrTimeout))) {
 
-				pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
+				xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusWaitStart)
 				pw.Status = ProxyStatusWaitStart
 
 				var newProxyMsg msg.NewProxy
@@ -180,7 +183,7 @@ func (pw *ProxyWrapper) checkWorker() {
 						ProxyName: pw.Name,
 					},
 				})
-				pw.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
+				xl.Trace("change status from [%s] to [%s]", pw.Status, ProxyStatusCheckFailed)
 				pw.Status = ProxyStatusCheckFailed
 			}
 			pw.mu.Unlock()
@@ -196,6 +199,7 @@ func (pw *ProxyWrapper) checkWorker() {
 }
 
 func (pw *ProxyWrapper) statusNormalCallback() {
+	xl := pw.xl
 	atomic.StoreUint32(&pw.health, 0)
 	errors.PanicToError(func() {
 		select {
@@ -203,10 +207,11 @@ func (pw *ProxyWrapper) statusNormalCallback() {
 		default:
 		}
 	})
-	pw.Info("health check success")
+	xl.Info("health check success")
 }
 
 func (pw *ProxyWrapper) statusFailedCallback() {
+	xl := pw.xl
 	atomic.StoreUint32(&pw.health, 1)
 	errors.PanicToError(func() {
 		select {
@@ -214,15 +219,16 @@ func (pw *ProxyWrapper) statusFailedCallback() {
 		default:
 		}
 	})
-	pw.Info("health check failed")
+	xl.Info("health check failed")
 }
 
-func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
+func (pw *ProxyWrapper) InWorkConn(workConn net.Conn, m *msg.StartWorkConn) {
+	xl := pw.xl
 	pw.mu.RLock()
 	pxy := pw.pxy
 	pw.mu.RUnlock()
 	if pxy != nil {
-		workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
+		xl.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
 		go pxy.InWorkConn(workConn, m)
 	} else {
 		workConn.Close()
diff --git a/client/service.go b/client/service.go
index 9f86ec7a..095df0aa 100644
--- a/client/service.go
+++ b/client/service.go
@@ -15,9 +15,11 @@
 package client
 
 import (
+	"context"
 	"crypto/tls"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"runtime"
 	"sync"
 	"sync/atomic"
@@ -30,6 +32,7 @@ import (
 	frpNet "github.com/fatedier/frp/utils/net"
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/version"
+	"github.com/fatedier/frp/utils/xlog"
 
 	fmux "github.com/hashicorp/yamux"
 )
@@ -55,19 +58,25 @@ type Service struct {
 	// This is configured by the login response from frps
 	serverUDPPort int
 
-	exit     uint32 // 0 means not exit
-	closedCh chan int
+	exit uint32 // 0 means not exit
+
+	// service context
+	ctx context.Context
+	// call cancel to stop service
+	cancel context.CancelFunc
 }
 
-// NewService creates a new client service with the given configuration.
 func NewService(cfg config.ClientCommonConf, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf, cfgFile string) (svr *Service, err error) {
+
+	ctx, cancel := context.WithCancel(context.Background())
 	svr = &Service{
 		cfg:         cfg,
 		cfgFile:     cfgFile,
 		pxyCfgs:     pxyCfgs,
 		visitorCfgs: visitorCfgs,
 		exit:        0,
-		closedCh:    make(chan int),
+		ctx:         xlog.NewContext(ctx, xlog.New()),
+		cancel:      cancel,
 	}
 	return
 }
@@ -79,11 +88,13 @@ func (svr *Service) GetController() *Control {
 }
 
 func (svr *Service) Run() error {
-	// first login
+	xl := xlog.FromContextSafe(svr.ctx)
+
+	// login to frps
 	for {
 		conn, session, err := svr.login()
 		if err != nil {
-			log.Warn("login to server failed: %v", err)
+			xl.Warn("login to server failed: %v", err)
 
 			// if login_fail_exit is true, just exit this program
 			// otherwise sleep a while and try again to connect to server
@@ -94,7 +105,7 @@ func (svr *Service) Run() error {
 			}
 		} else {
 			// login success
-			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
+			ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
@@ -118,12 +129,12 @@ func (svr *Service) Run() error {
 		}
 		log.Info("admin server listen on %s:%d", svr.cfg.AdminAddr, svr.cfg.AdminPort)
 	}
-
-	<-svr.closedCh
+	<-svr.ctx.Done()
 	return nil
 }
 
 func (svr *Service) keepControllerWorking() {
+	xl := xlog.FromContextSafe(svr.ctx)
 	maxDelayTime := 20 * time.Second
 	delayTime := time.Second
 
@@ -134,10 +145,10 @@ func (svr *Service) keepControllerWorking() {
 		}
 
 		for {
-			log.Info("try to reconnect to server...")
+			xl.Info("try to reconnect to server...")
 			conn, session, err := svr.login()
 			if err != nil {
-				log.Warn("reconnect to server error: %v", err)
+				xl.Warn("reconnect to server error: %v", err)
 				time.Sleep(delayTime)
 				delayTime = delayTime * 2
 				if delayTime > maxDelayTime {
@@ -148,7 +159,7 @@ func (svr *Service) keepControllerWorking() {
 			// reconnect success, init delayTime
 			delayTime = time.Second
 
-			ctl := NewControl(svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
+			ctl := NewControl(svr.ctx, svr.runId, conn, session, svr.cfg, svr.pxyCfgs, svr.visitorCfgs, svr.serverUDPPort)
 			ctl.Run()
 			svr.ctlMu.Lock()
 			svr.ctl = ctl
@@ -161,7 +172,8 @@ func (svr *Service) keepControllerWorking() {
 // login creates a connection to frps and registers it self as a client
 // conn: control connection
 // session: if it's not nil, using tcp mux
-func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
+func (svr *Service) login() (conn net.Conn, session *fmux.Session, err error) {
+	xl := xlog.FromContextSafe(svr.ctx)
 	var tlsConfig *tls.Config
 	if svr.cfg.TLSEnable {
 		tlsConfig = &tls.Config{
@@ -197,7 +209,7 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 			err = errRet
 			return
 		}
-		conn = frpNet.WrapConn(stream)
+		conn = stream
 	}
 
 	now := time.Now().Unix()
@@ -225,13 +237,16 @@ func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error)
 
 	if loginRespMsg.Error != "" {
 		err = fmt.Errorf("%s", loginRespMsg.Error)
-		log.Error("%s", loginRespMsg.Error)
+		xl.Error("%s", loginRespMsg.Error)
 		return
 	}
 
 	svr.runId = loginRespMsg.RunId
+	xl.ResetPrefixes()
+	xl.AppendPrefix(svr.runId)
+
 	svr.serverUDPPort = loginRespMsg.ServerUdpPort
-	log.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
+	xl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
 	return
 }
 
@@ -247,5 +262,5 @@ func (svr *Service) ReloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs
 func (svr *Service) Close() {
 	atomic.StoreUint32(&svr.exit, 1)
 	svr.ctl.Close()
-	close(svr.closedCh)
+	svr.cancel()
 }
diff --git a/client/visitor.go b/client/visitor.go
index c1ee0040..a4900e06 100644
--- a/client/visitor.go
+++ b/client/visitor.go
@@ -16,6 +16,7 @@ package client
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -25,9 +26,9 @@ import (
 
 	"github.com/fatedier/frp/models/config"
 	"github.com/fatedier/frp/models/msg"
-	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
 	"github.com/fatedier/frp/utils/util"
+	"github.com/fatedier/frp/utils/xlog"
 
 	frpIo "github.com/fatedier/golib/io"
 	"github.com/fatedier/golib/pool"
@@ -38,13 +39,13 @@ import (
 type Visitor interface {
 	Run() error
 	Close()
-	log.Logger
 }
 
-func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
+func NewVisitor(ctx context.Context, ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
+	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseInfo().ProxyName)
 	baseVisitor := BaseVisitor{
-		ctl:    ctl,
-		Logger: log.NewPrefixLogger(cfg.GetBaseInfo().ProxyName),
+		ctl: ctl,
+		ctx: xlog.NewContext(ctx, xl),
 	}
 	switch cfg := cfg.(type) {
 	case *config.StcpVisitorConf:
@@ -63,10 +64,11 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
 
 type BaseVisitor struct {
 	ctl    *Control
-	l      frpNet.Listener
+	l      net.Listener
 	closed bool
-	mu     sync.RWMutex
-	log.Logger
+
+	mu  sync.RWMutex
+	ctx context.Context
 }
 
 type StcpVisitor struct {
@@ -76,7 +78,7 @@ type StcpVisitor struct {
 }
 
 func (sv *StcpVisitor) Run() (err error) {
-	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
+	sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
 	if err != nil {
 		return
 	}
@@ -90,10 +92,11 @@ func (sv *StcpVisitor) Close() {
 }
 
 func (sv *StcpVisitor) worker() {
+	xl := xlog.FromContextSafe(sv.ctx)
 	for {
 		conn, err := sv.l.Accept()
 		if err != nil {
-			sv.Warn("stcp local listener closed")
+			xl.Warn("stcp local listener closed")
 			return
 		}
 
@@ -101,10 +104,11 @@ func (sv *StcpVisitor) worker() {
 	}
 }
 
-func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
+func (sv *StcpVisitor) handleConn(userConn net.Conn) {
+	xl := xlog.FromContextSafe(sv.ctx)
 	defer userConn.Close()
 
-	sv.Debug("get a new stcp user connection")
+	xl.Debug("get a new stcp user connection")
 	visitorConn, err := sv.ctl.connectServer()
 	if err != nil {
 		return
@@ -121,7 +125,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
 	}
 	err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
 	if err != nil {
-		sv.Warn("send newVisitorConnMsg to server error: %v", err)
+		xl.Warn("send newVisitorConnMsg to server error: %v", err)
 		return
 	}
 
@@ -129,13 +133,13 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
 	visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
 	err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
 	if err != nil {
-		sv.Warn("get newVisitorConnRespMsg error: %v", err)
+		xl.Warn("get newVisitorConnRespMsg error: %v", err)
 		return
 	}
 	visitorConn.SetReadDeadline(time.Time{})
 
 	if newVisitorConnRespMsg.Error != "" {
-		sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
+		xl.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
 		return
 	}
 
@@ -144,7 +148,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
 	if sv.cfg.UseEncryption {
 		remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
 		if err != nil {
-			sv.Error("create encryption stream error: %v", err)
+			xl.Error("create encryption stream error: %v", err)
 			return
 		}
 	}
@@ -163,7 +167,7 @@ type XtcpVisitor struct {
 }
 
 func (sv *XtcpVisitor) Run() (err error) {
-	sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, sv.cfg.BindPort)
+	sv.l, err = net.Listen("tcp", fmt.Sprintf("%s:%d", sv.cfg.BindAddr, sv.cfg.BindPort))
 	if err != nil {
 		return
 	}
@@ -177,10 +181,11 @@ func (sv *XtcpVisitor) Close() {
 }
 
 func (sv *XtcpVisitor) worker() {
+	xl := xlog.FromContextSafe(sv.ctx)
 	for {
 		conn, err := sv.l.Accept()
 		if err != nil {
-			sv.Warn("xtcp local listener closed")
+			xl.Warn("xtcp local listener closed")
 			return
 		}
 
@@ -188,25 +193,26 @@ func (sv *XtcpVisitor) worker() {
 	}
 }
 
-func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
+func (sv *XtcpVisitor) handleConn(userConn net.Conn) {
+	xl := xlog.FromContextSafe(sv.ctx)
 	defer userConn.Close()
 
-	sv.Debug("get a new xtcp user connection")
+	xl.Debug("get a new xtcp user connection")
 	if sv.ctl.serverUDPPort == 0 {
-		sv.Error("xtcp is not supported by server")
+		xl.Error("xtcp is not supported by server")
 		return
 	}
 
 	raddr, err := net.ResolveUDPAddr("udp",
 		fmt.Sprintf("%s:%d", sv.ctl.clientCfg.ServerAddr, sv.ctl.serverUDPPort))
 	if err != nil {
-		sv.Error("resolve server UDP addr error")
+		xl.Error("resolve server UDP addr error")
 		return
 	}
 
 	visitorConn, err := net.DialUDP("udp", nil, raddr)
 	if err != nil {
-		sv.Warn("dial server udp addr error: %v", err)
+		xl.Warn("dial server udp addr error: %v", err)
 		return
 	}
 	defer visitorConn.Close()
@@ -219,7 +225,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	}
 	err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
 	if err != nil {
-		sv.Warn("send natHoleVisitorMsg to server error: %v", err)
+		xl.Warn("send natHoleVisitorMsg to server error: %v", err)
 		return
 	}
 
@@ -229,24 +235,24 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	buf := pool.GetBuf(1024)
 	n, err := visitorConn.Read(buf)
 	if err != nil {
-		sv.Warn("get natHoleRespMsg error: %v", err)
+		xl.Warn("get natHoleRespMsg error: %v", err)
 		return
 	}
 
 	err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
 	if err != nil {
-		sv.Warn("get natHoleRespMsg error: %v", err)
+		xl.Warn("get natHoleRespMsg error: %v", err)
 		return
 	}
 	visitorConn.SetReadDeadline(time.Time{})
 	pool.PutBuf(buf)
 
 	if natHoleRespMsg.Error != "" {
-		sv.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
+		xl.Error("natHoleRespMsg get error info: %s", natHoleRespMsg.Error)
 		return
 	}
 
-	sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
+	xl.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
 
 	// Close visitorConn, so we can use it's local address.
 	visitorConn.Close()
@@ -255,12 +261,12 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
 	daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
 	if err != nil {
-		sv.Error("resolve client udp address error: %v", err)
+		xl.Error("resolve client udp address error: %v", err)
 		return
 	}
 	lConn, err := net.DialUDP("udp", laddr, daddr)
 	if err != nil {
-		sv.Error("dial client udp address error: %v", err)
+		xl.Error("dial client udp address error: %v", err)
 		return
 	}
 	defer lConn.Close()
@@ -272,23 +278,23 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
 	n, err = lConn.Read(sidBuf)
 	if err != nil {
-		sv.Warn("get sid from client error: %v", err)
+		xl.Warn("get sid from client error: %v", err)
 		return
 	}
 	lConn.SetReadDeadline(time.Time{})
 	if string(sidBuf[:n]) != natHoleRespMsg.Sid {
-		sv.Warn("incorrect sid from client")
+		xl.Warn("incorrect sid from client")
 		return
 	}
 	pool.PutBuf(sidBuf)
 
-	sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
+	xl.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
 
 	// wrap kcp connection
 	var remote io.ReadWriteCloser
 	remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
 	if err != nil {
-		sv.Error("create kcp connection from udp connection error: %v", err)
+		xl.Error("create kcp connection from udp connection error: %v", err)
 		return
 	}
 
@@ -297,13 +303,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	fmuxCfg.LogOutput = ioutil.Discard
 	sess, err := fmux.Client(remote, fmuxCfg)
 	if err != nil {
-		sv.Error("create yamux session error: %v", err)
+		xl.Error("create yamux session error: %v", err)
 		return
 	}
 	defer sess.Close()
 	muxConn, err := sess.Open()
 	if err != nil {
-		sv.Error("open yamux stream error: %v", err)
+		xl.Error("open yamux stream error: %v", err)
 		return
 	}
 
@@ -311,7 +317,7 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	if sv.cfg.UseEncryption {
 		muxConnRWCloser, err = frpIo.WithEncryption(muxConnRWCloser, []byte(sv.cfg.Sk))
 		if err != nil {
-			sv.Error("create encryption stream error: %v", err)
+			xl.Error("create encryption stream error: %v", err)
 			return
 		}
 	}
@@ -320,5 +326,5 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
 	}
 
 	frpIo.Join(userConn, muxConnRWCloser)
-	sv.Debug("join connections closed")
+	xl.Debug("join connections closed")
 }
diff --git a/client/visitor_manager.go b/client/visitor_manager.go
index b223d55d..3b9351e7 100644
--- a/client/visitor_manager.go
+++ b/client/visitor_manager.go
@@ -15,11 +15,12 @@
 package client
 
 import (
+	"context"
 	"sync"
 	"time"
 
 	"github.com/fatedier/frp/models/config"
-	"github.com/fatedier/frp/utils/log"
+	"github.com/fatedier/frp/utils/xlog"
 )
 
 type VisitorManager struct {
@@ -30,26 +31,29 @@ type VisitorManager struct {
 
 	checkInterval time.Duration
 
-	mu sync.Mutex
+	mu  sync.Mutex
+	ctx context.Context
 }
 
-func NewVisitorManager(ctl *Control) *VisitorManager {
+func NewVisitorManager(ctx context.Context, ctl *Control) *VisitorManager {
 	return &VisitorManager{
 		ctl:           ctl,
 		cfgs:          make(map[string]config.VisitorConf),
 		visitors:      make(map[string]Visitor),
 		checkInterval: 10 * time.Second,
+		ctx:           ctx,
 	}
 }
 
 func (vm *VisitorManager) Run() {
+	xl := xlog.FromContextSafe(vm.ctx)
 	for {
 		time.Sleep(vm.checkInterval)
 		vm.mu.Lock()
 		for _, cfg := range vm.cfgs {
 			name := cfg.GetBaseInfo().ProxyName
 			if _, exist := vm.visitors[name]; !exist {
-				log.Info("try to start visitor [%s]", name)
+				xl.Info("try to start visitor [%s]", name)
 				vm.startVisitor(cfg)
 			}
 		}
@@ -59,19 +63,21 @@ func (vm *VisitorManager) Run() {
 
 // Hold lock before calling this function.
 func (vm *VisitorManager) startVisitor(cfg config.VisitorConf) (err error) {
+	xl := xlog.FromContextSafe(vm.ctx)
 	name := cfg.GetBaseInfo().ProxyName
-	visitor := NewVisitor(vm.ctl, cfg)
+	visitor := NewVisitor(vm.ctx, vm.ctl, cfg)
 	err = visitor.Run()
 	if err != nil {
-		visitor.Warn("start error: %v", err)
+		xl.Warn("start error: %v", err)
 	} else {
 		vm.visitors[name] = visitor
-		visitor.Info("start visitor success")
+		xl.Info("start visitor success")
 	}
 	return
 }
 
 func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
+	xl := xlog.FromContextSafe(vm.ctx)
 	vm.mu.Lock()
 	defer vm.mu.Unlock()
 
@@ -97,7 +103,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
 		}
 	}
 	if len(delNames) > 0 {
-		log.Info("visitor removed: %v", delNames)
+		xl.Info("visitor removed: %v", delNames)
 	}
 
 	addNames := make([]string, 0)
@@ -109,7 +115,7 @@ func (vm *VisitorManager) Reload(cfgs map[string]config.VisitorConf) {
 		}
 	}
 	if len(addNames) > 0 {
-		log.Info("visitor added: %v", addNames)
+		xl.Info("visitor added: %v", addNames)
 	}
 	return
 }
diff --git a/cmd/frps/root.go b/cmd/frps/root.go
index fd6cdbde..ec175fe3 100644
--- a/cmd/frps/root.go
+++ b/cmd/frps/root.go
@@ -202,7 +202,7 @@ func runServer(cfg config.ServerCommonConf) (err error) {
 	if err != nil {
 		return err
 	}
-	log.Info("Start frps success")
+	log.Info("start frps success")
 	svr.Run()
 	return
 }
diff --git a/go.sum b/go.sum
index 5ddda4ef..b9bdf2a0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,10 @@
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
 github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
 github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
 github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
 github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible h1:ssXat9YXFvigNge/IkkZvFMn8yeYKFX+uI6wn2mLJ74=
 github.com/fatedier/kcp-go v2.0.4-0.20190803094908-fe8645b0a904+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
-github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
 github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -26,28 +22,18 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
 github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
-github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
 github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
-github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
 github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
-github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
 github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
-github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
 github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
-github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
 github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
-github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
 github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
 github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
diff --git a/models/plugin/http_proxy.go b/models/plugin/http_proxy.go
index 3afa2cb8..b0ca9231 100644
--- a/models/plugin/http_proxy.go
+++ b/models/plugin/http_proxy.go
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
 	return PluginHttpProxy
 }
 
-func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
+func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
 
 	sc, rd := gnet.NewSharedConn(wrapConn)
diff --git a/models/plugin/https2http.go b/models/plugin/https2http.go
index f840ebde..65540356 100644
--- a/models/plugin/https2http.go
+++ b/models/plugin/https2http.go
@@ -18,6 +18,7 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"net/http/httputil"
 	"strings"
@@ -115,7 +116,7 @@ func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
 	return config, nil
 }
 
-func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
+func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
 	p.l.PutConn(wrapConn)
 }
diff --git a/models/plugin/plugin.go b/models/plugin/plugin.go
index cfad5510..6850919a 100644
--- a/models/plugin/plugin.go
+++ b/models/plugin/plugin.go
@@ -20,8 +20,6 @@ import (
 	"net"
 	"sync"
 
-	frpNet "github.com/fatedier/frp/utils/net"
-
 	"github.com/fatedier/golib/errors"
 )
 
@@ -46,7 +44,9 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
 
 type Plugin interface {
 	Name() string
-	Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
+
+	// extraBufToLocal will send to local connection first, then join conn with local connection
+	Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte)
 	Close() error
 }
 
diff --git a/models/plugin/socks5.go b/models/plugin/socks5.go
index 447602a9..dc089345 100644
--- a/models/plugin/socks5.go
+++ b/models/plugin/socks5.go
@@ -18,6 +18,7 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
+	"net"
 
 	frpNet "github.com/fatedier/frp/utils/net"
 
@@ -53,7 +54,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
 	return
 }
 
-func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
+func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
 	defer conn.Close()
 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
 	sp.Server.ServeConn(wrapConn)
diff --git a/models/plugin/static_file.go b/models/plugin/static_file.go
index 080ff74f..32325317 100644
--- a/models/plugin/static_file.go
+++ b/models/plugin/static_file.go
@@ -16,6 +16,7 @@ package plugin
 
 import (
 	"io"
+	"net"
 	"net/http"
 
 	frpNet "github.com/fatedier/frp/utils/net"
@@ -72,7 +73,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
 	return sp, nil
 }
 
-func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
+func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
 	wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
 	sp.l.PutConn(wrapConn)
 }
diff --git a/models/plugin/unix_domain_socket.go b/models/plugin/unix_domain_socket.go
index 86833e25..a85ada70 100644
--- a/models/plugin/unix_domain_socket.go
+++ b/models/plugin/unix_domain_socket.go
@@ -19,8 +19,6 @@ import (
 	"io"
 	"net"
 
-	frpNet "github.com/fatedier/frp/utils/net"
-
 	frpIo "github.com/fatedier/golib/io"
 )
 
@@ -53,7 +51,7 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
 	return
 }
 
-func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
+func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extraBufToLocal []byte) {
 	localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
 	if err != nil {
 		return
diff --git a/server/control.go b/server/control.go
index bb802a75..0db61987 100644
--- a/server/control.go
+++ b/server/control.go
@@ -15,8 +15,10 @@
 package server
 
 import (
+	"context"
 	"fmt"
 	"io"
+	"net"
 	"runtime/debug"
 	"sync"
 	"time"
@@ -28,8 +30,8 @@ import (
 	"github.com/fatedier/frp/server/controller"
 	"github.com/fatedier/frp/server/proxy"
 	"github.com/fatedier/frp/server/stats"
-	"github.com/fatedier/frp/utils/net"
 	"github.com/fatedier/frp/utils/version"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/control/shutdown"
 	"github.com/fatedier/golib/crypto"
@@ -131,9 +133,12 @@ type Control struct {
 
 	// Server configuration information
 	serverCfg config.ServerCommonConf
+
+	xl  *xlog.Logger
+	ctx context.Context
 }
 
-func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
+func NewControl(ctx context.Context, rc *controller.ResourceController, pxyManager *proxy.ProxyManager,
 	statsCollector stats.Collector, ctlConn net.Conn, loginMsg *msg.Login,
 	serverCfg config.ServerCommonConf) *Control {
 
@@ -161,6 +166,8 @@ func NewControl(rc *controller.ResourceController, pxyManager *proxy.ProxyManage
 		managerShutdown: shutdown.New(),
 		allShutdown:     shutdown.New(),
 		serverCfg:       serverCfg,
+		xl:              xlog.FromContextSafe(ctx),
+		ctx:             ctx,
 	}
 }
 
@@ -185,18 +192,19 @@ func (ctl *Control) Start() {
 }
 
 func (ctl *Control) RegisterWorkConn(conn net.Conn) {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
 	select {
 	case ctl.workConnCh <- conn:
-		ctl.conn.Debug("new work connection registered")
+		xl.Debug("new work connection registered")
 	default:
-		ctl.conn.Debug("work connection pool is full, discarding")
+		xl.Debug("work connection pool is full, discarding")
 		conn.Close()
 	}
 }
@@ -206,10 +214,11 @@ func (ctl *Control) RegisterWorkConn(conn net.Conn) {
 // and wait until it is available.
 // return an error if wait timeout
 func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
@@ -221,14 +230,14 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
 			err = frpErr.ErrCtlClosed
 			return
 		}
-		ctl.conn.Debug("get work connection from pool")
+		xl.Debug("get work connection from pool")
 	default:
 		// no work connections available in the poll, send message to frpc to get more
 		err = errors.PanicToError(func() {
 			ctl.sendCh <- &msg.ReqWorkConn{}
 		})
 		if err != nil {
-			ctl.conn.Error("%v", err)
+			xl.Error("%v", err)
 			return
 		}
 
@@ -236,13 +245,13 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
 		case workConn, ok = <-ctl.workConnCh:
 			if !ok {
 				err = frpErr.ErrCtlClosed
-				ctl.conn.Warn("no work connections avaiable, %v", err)
+				xl.Warn("no work connections avaiable, %v", err)
 				return
 			}
 
 		case <-time.After(time.Duration(ctl.serverCfg.UserConnTimeout) * time.Second):
 			err = fmt.Errorf("timeout trying to get work connection")
-			ctl.conn.Warn("%v", err)
+			xl.Warn("%v", err)
 			return
 		}
 	}
@@ -255,16 +264,18 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
 }
 
 func (ctl *Control) Replaced(newCtl *Control) {
-	ctl.conn.Info("Replaced by client [%s]", newCtl.runId)
+	xl := ctl.xl
+	xl.Info("Replaced by client [%s]", newCtl.runId)
 	ctl.runId = ""
 	ctl.allShutdown.Start()
 }
 
 func (ctl *Control) writer() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
@@ -273,17 +284,17 @@ func (ctl *Control) writer() {
 
 	encWriter, err := crypto.NewWriter(ctl.conn, []byte(ctl.serverCfg.Token))
 	if err != nil {
-		ctl.conn.Error("crypto new writer error: %v", err)
+		xl.Error("crypto new writer error: %v", err)
 		ctl.allShutdown.Start()
 		return
 	}
 	for {
 		if m, ok := <-ctl.sendCh; !ok {
-			ctl.conn.Info("control writer is closing")
+			xl.Info("control writer is closing")
 			return
 		} else {
 			if err := msg.WriteMsg(encWriter, m); err != nil {
-				ctl.conn.Warn("write message to control connection error: %v", err)
+				xl.Warn("write message to control connection error: %v", err)
 				return
 			}
 		}
@@ -291,10 +302,11 @@ func (ctl *Control) writer() {
 }
 
 func (ctl *Control) reader() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
@@ -305,10 +317,10 @@ func (ctl *Control) reader() {
 	for {
 		if m, err := msg.ReadMsg(encReader); err != nil {
 			if err == io.EOF {
-				ctl.conn.Debug("control connection closed")
+				xl.Debug("control connection closed")
 				return
 			} else {
-				ctl.conn.Warn("read error: %v", err)
+				xl.Warn("read error: %v", err)
 				ctl.conn.Close()
 				return
 			}
@@ -319,10 +331,11 @@ func (ctl *Control) reader() {
 }
 
 func (ctl *Control) stoper() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
@@ -355,7 +368,7 @@ func (ctl *Control) stoper() {
 	}
 
 	ctl.allShutdown.Done()
-	ctl.conn.Info("client exit success")
+	xl.Info("client exit success")
 
 	ctl.statsCollector.Mark(stats.TypeCloseClient, &stats.CloseClientPayload{})
 }
@@ -366,10 +379,11 @@ func (ctl *Control) WaitClosed() {
 }
 
 func (ctl *Control) manager() {
+	xl := ctl.xl
 	defer func() {
 		if err := recover(); err != nil {
-			ctl.conn.Error("panic error: %v", err)
-			ctl.conn.Error(string(debug.Stack()))
+			xl.Error("panic error: %v", err)
+			xl.Error(string(debug.Stack()))
 		}
 	}()
 
@@ -383,7 +397,7 @@ func (ctl *Control) manager() {
 		select {
 		case <-heartbeat.C:
 			if time.Since(ctl.lastPing) > time.Duration(ctl.serverCfg.HeartBeatTimeout)*time.Second {
-				ctl.conn.Warn("heartbeat timeout")
+				xl.Warn("heartbeat timeout")
 				return
 			}
 		case rawMsg, ok := <-ctl.readCh:
@@ -400,10 +414,10 @@ func (ctl *Control) manager() {
 				}
 				if err != nil {
 					resp.Error = err.Error()
-					ctl.conn.Warn("new proxy [%s] error: %v", m.ProxyName, err)
+					xl.Warn("new proxy [%s] error: %v", m.ProxyName, err)
 				} else {
 					resp.RemoteAddr = remoteAddr
-					ctl.conn.Info("new proxy [%s] success", m.ProxyName)
+					xl.Info("new proxy [%s] success", m.ProxyName)
 					ctl.statsCollector.Mark(stats.TypeNewProxy, &stats.NewProxyPayload{
 						Name:      m.ProxyName,
 						ProxyType: m.ProxyType,
@@ -412,10 +426,10 @@ func (ctl *Control) manager() {
 				ctl.sendCh <- resp
 			case *msg.CloseProxy:
 				ctl.CloseProxy(m)
-				ctl.conn.Info("close proxy [%s] success", m.ProxyName)
+				xl.Info("close proxy [%s] success", m.ProxyName)
 			case *msg.Ping:
 				ctl.lastPing = time.Now()
-				ctl.conn.Debug("receive heartbeat")
+				xl.Debug("receive heartbeat")
 				ctl.sendCh <- &msg.Pong{}
 			}
 		}
@@ -432,7 +446,7 @@ func (ctl *Control) RegisterProxy(pxyMsg *msg.NewProxy) (remoteAddr string, err
 
 	// NewProxy will return a interface Proxy.
 	// In fact it create different proxies by different proxy type, we just call run() here.
-	pxy, err := proxy.NewProxy(ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
+	pxy, err := proxy.NewProxy(ctl.ctx, ctl.runId, ctl.rc, ctl.statsCollector, ctl.poolCount, ctl.GetWorkConn, pxyConf, ctl.serverCfg)
 	if err != nil {
 		return remoteAddr, err
 	}
diff --git a/server/controller/visitor.go b/server/controller/visitor.go
index ea8a53e8..22ff6942 100644
--- a/server/controller/visitor.go
+++ b/server/controller/visitor.go
@@ -17,6 +17,7 @@ package controller
 import (
 	"fmt"
 	"io"
+	"net"
 	"sync"
 
 	frpNet "github.com/fatedier/frp/utils/net"
@@ -55,7 +56,7 @@ func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListen
 	return
 }
 
-func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
+func (vm *VisitorManager) NewConn(name string, conn net.Conn, timestamp int64, signKey string,
 	useEncryption bool, useCompression bool) (err error) {
 
 	vm.mu.RLock()
diff --git a/server/group/http.go b/server/group/http.go
index 538dccf3..31609680 100644
--- a/server/group/http.go
+++ b/server/group/http.go
@@ -2,11 +2,10 @@ package group
 
 import (
 	"fmt"
+	"net"
 	"sync"
 	"sync/atomic"
 
-	frpNet "github.com/fatedier/frp/utils/net"
-
 	"github.com/fatedier/frp/utils/vhost"
 )
 
@@ -131,7 +130,7 @@ func (g *HTTPGroup) UnRegister(proxyName string) (isEmpty bool) {
 	return
 }
 
-func (g *HTTPGroup) createConn(remoteAddr string) (frpNet.Conn, error) {
+func (g *HTTPGroup) createConn(remoteAddr string) (net.Conn, error) {
 	var f vhost.CreateConnFunc
 	newIndex := atomic.AddUint64(&g.index, 1)
 
diff --git a/server/proxy/http.go b/server/proxy/http.go
index 5bd3139d..09fb3e44 100644
--- a/server/proxy/http.go
+++ b/server/proxy/http.go
@@ -36,6 +36,7 @@ type HttpProxy struct {
 }
 
 func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
 	routeConfig := vhost.VhostRouteConfig{
 		RewriteHost:  pxy.cfg.HostHeaderRewrite,
 		Headers:      pxy.cfg.Headers,
@@ -88,7 +89,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 				})
 			}
 			addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpPort)))
-			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
+			xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
 	}
 
@@ -120,7 +121,7 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
 			}
 			addrs = append(addrs, util.CanonicalAddr(tmpDomain, pxy.serverCfg.VhostHttpPort))
 
-			pxy.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
+			xl.Info("http proxy listen for host [%s] location [%s] group [%s]", routeConfig.Domain, routeConfig.Location, pxy.cfg.Group)
 		}
 	}
 	remoteAddr = strings.Join(addrs, ",")
@@ -131,10 +132,11 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
 	return pxy.cfg
 }
 
-func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
+func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn net.Conn, err error) {
+	xl := pxy.xl
 	rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
 	if errRet != nil {
-		pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
+		xl.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
 		// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
 	}
 
@@ -148,7 +150,7 @@ func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err
 	if pxy.cfg.UseEncryption {
 		rwc, err = frpIo.WithEncryption(rwc, []byte(pxy.serverCfg.Token))
 		if err != nil {
-			pxy.Error("create encryption stream error: %v", err)
+			xl.Error("create encryption stream error: %v", err)
 			return
 		}
 	}
diff --git a/server/proxy/https.go b/server/proxy/https.go
index 87840421..0191496f 100644
--- a/server/proxy/https.go
+++ b/server/proxy/https.go
@@ -28,6 +28,7 @@ type HttpsProxy struct {
 }
 
 func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
 	routeConfig := &vhost.VhostRouteConfig{}
 
 	defer func() {
@@ -42,26 +43,24 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
 		}
 
 		routeConfig.Domain = domain
-		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
+		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
 		if errRet != nil {
 			err = errRet
 			return
 		}
-		l.AddLogPrefix(pxy.name)
-		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
+		xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
 		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, pxy.serverCfg.VhostHttpsPort))
 	}
 
 	if pxy.cfg.SubDomain != "" {
 		routeConfig.Domain = pxy.cfg.SubDomain + "." + pxy.serverCfg.SubDomainHost
-		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
+		l, errRet := pxy.rc.VhostHttpsMuxer.Listen(pxy.ctx, routeConfig)
 		if errRet != nil {
 			err = errRet
 			return
 		}
-		l.AddLogPrefix(pxy.name)
-		pxy.Info("https proxy listen for host [%s]", routeConfig.Domain)
+		xl.Info("https proxy listen for host [%s]", routeConfig.Domain)
 		pxy.listeners = append(pxy.listeners, l)
 		addrs = append(addrs, util.CanonicalAddr(routeConfig.Domain, int(pxy.serverCfg.VhostHttpsPort)))
 	}
diff --git a/server/proxy/proxy.go b/server/proxy/proxy.go
index 627b0e15..84a04e61 100644
--- a/server/proxy/proxy.go
+++ b/server/proxy/proxy.go
@@ -15,6 +15,7 @@
 package proxy
 
 import (
+	"context"
 	"fmt"
 	"io"
 	"net"
@@ -25,48 +26,54 @@ import (
 	"github.com/fatedier/frp/models/msg"
 	"github.com/fatedier/frp/server/controller"
 	"github.com/fatedier/frp/server/stats"
-	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	frpIo "github.com/fatedier/golib/io"
 )
 
-type GetWorkConnFn func() (frpNet.Conn, error)
+type GetWorkConnFn func() (net.Conn, error)
 
 type Proxy interface {
+	Context() context.Context
 	Run() (remoteAddr string, err error)
 	GetName() string
 	GetConf() config.ProxyConf
-	GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
+	GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error)
 	GetUsedPortsNum() int
 	Close()
-	log.Logger
 }
 
 type BaseProxy struct {
 	name           string
 	rc             *controller.ResourceController
 	statsCollector stats.Collector
-	listeners      []frpNet.Listener
+	listeners      []net.Listener
 	usedPortsNum   int
 	poolCount      int
 	getWorkConnFn  GetWorkConnFn
 	serverCfg      config.ServerCommonConf
 
-	mu sync.RWMutex
-	log.Logger
+	mu  sync.RWMutex
+	xl  *xlog.Logger
+	ctx context.Context
 }
 
 func (pxy *BaseProxy) GetName() string {
 	return pxy.name
 }
 
+func (pxy *BaseProxy) Context() context.Context {
+	return pxy.ctx
+}
+
 func (pxy *BaseProxy) GetUsedPortsNum() int {
 	return pxy.usedPortsNum
 }
 
 func (pxy *BaseProxy) Close() {
-	pxy.Info("proxy closing")
+	xl := xlog.FromContextSafe(pxy.ctx)
+	xl.Info("proxy closing")
 	for _, l := range pxy.listeners {
 		l.Close()
 	}
@@ -74,15 +81,17 @@ func (pxy *BaseProxy) Close() {
 
 // GetWorkConnFromPool try to get a new work connections from pool
 // for quickly response, we immediately send the StartWorkConn message to frpc after take out one from pool
-func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
+func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn, err error) {
+	xl := xlog.FromContextSafe(pxy.ctx)
 	// try all connections from the pool
 	for i := 0; i < pxy.poolCount+1; i++ {
 		if workConn, err = pxy.getWorkConnFn(); err != nil {
-			pxy.Warn("failed to get work connection: %v", err)
+			xl.Warn("failed to get work connection: %v", err)
 			return
 		}
-		pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
-		workConn.AddLogPrefix(pxy.GetName())
+		xl.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
+		xl.Spawn().AppendPrefix(pxy.GetName())
+		workConn = frpNet.NewContextConn(workConn, pxy.ctx)
 
 		var (
 			srcAddr    string
@@ -109,7 +118,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
 			DstPort:   uint16(dstPort),
 		})
 		if err != nil {
-			workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
+			xl.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
 			workConn.Close()
 		} else {
 			break
@@ -117,7 +126,7 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
 	}
 
 	if err != nil {
-		pxy.Error("try to get work connection failed in the end")
+		xl.Error("try to get work connection failed in the end")
 		return
 	}
 	return
@@ -126,36 +135,39 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Co
 // startListenHandler start a goroutine handler for each listener.
 // p: p will just be passed to handler(Proxy, frpNet.Conn).
 // handler: each proxy type can set different handler function to deal with connections accepted from listeners.
-func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, frpNet.Conn, stats.Collector, config.ServerCommonConf)) {
+func (pxy *BaseProxy) startListenHandler(p Proxy, handler func(Proxy, net.Conn, stats.Collector, config.ServerCommonConf)) {
+	xl := xlog.FromContextSafe(pxy.ctx)
 	for _, listener := range pxy.listeners {
-		go func(l frpNet.Listener) {
+		go func(l net.Listener) {
 			for {
 				// block
 				// if listener is closed, err returned
 				c, err := l.Accept()
 				if err != nil {
-					pxy.Info("listener is closed")
+					xl.Info("listener is closed")
 					return
 				}
-				pxy.Debug("get a user connection [%s]", c.RemoteAddr().String())
+				xl.Debug("get a user connection [%s]", c.RemoteAddr().String())
 				go handler(p, c, pxy.statsCollector, pxy.serverCfg)
 			}
 		}(listener)
 	}
 }
 
-func NewProxy(runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
+func NewProxy(ctx context.Context, runId string, rc *controller.ResourceController, statsCollector stats.Collector, poolCount int,
 	getWorkConnFn GetWorkConnFn, pxyConf config.ProxyConf, serverCfg config.ServerCommonConf) (pxy Proxy, err error) {
 
+	xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(pxyConf.GetBaseInfo().ProxyName)
 	basePxy := BaseProxy{
 		name:           pxyConf.GetBaseInfo().ProxyName,
 		rc:             rc,
 		statsCollector: statsCollector,
-		listeners:      make([]frpNet.Listener, 0),
+		listeners:      make([]net.Listener, 0),
 		poolCount:      poolCount,
 		getWorkConnFn:  getWorkConnFn,
-		Logger:         log.NewPrefixLogger(runId),
 		serverCfg:      serverCfg,
+		xl:             xl,
+		ctx:            xlog.NewContext(ctx, xl),
 	}
 	switch cfg := pxyConf.(type) {
 	case *config.TcpProxyConf:
@@ -193,13 +205,13 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
 	default:
 		return pxy, fmt.Errorf("proxy type not support")
 	}
-	pxy.AddLogPrefix(pxy.GetName())
 	return
 }
 
 // HandleUserTcpConnection is used for incoming tcp user connections.
 // It can be used for tcp, http, https type.
-func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
+func HandleUserTcpConnection(pxy Proxy, userConn net.Conn, statsCollector stats.Collector, serverCfg config.ServerCommonConf) {
+	xl := xlog.FromContextSafe(pxy.Context())
 	defer userConn.Close()
 
 	// try all connections from the pool
@@ -211,17 +223,18 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
 
 	var local io.ReadWriteCloser = workConn
 	cfg := pxy.GetConf().GetBaseInfo()
+	xl.Trace("handler user tcp connection, use_encryption: %t, use_compression: %t", cfg.UseEncryption, cfg.UseCompression)
 	if cfg.UseEncryption {
 		local, err = frpIo.WithEncryption(local, []byte(serverCfg.Token))
 		if err != nil {
-			pxy.Error("create encryption stream error: %v", err)
+			xl.Error("create encryption stream error: %v", err)
 			return
 		}
 	}
 	if cfg.UseCompression {
 		local = frpIo.WithCompression(local)
 	}
-	pxy.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
+	xl.Debug("join connections, workConn(l[%s] r[%s]) userConn(l[%s] r[%s])", workConn.LocalAddr().String(),
 		workConn.RemoteAddr().String(), userConn.LocalAddr().String(), userConn.RemoteAddr().String())
 
 	statsCollector.Mark(stats.TypeOpenConnection, &stats.OpenConnectionPayload{ProxyName: pxy.GetName()})
@@ -235,7 +248,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
 		ProxyName:    pxy.GetName(),
 		TrafficBytes: outCount,
 	})
-	pxy.Debug("join connections closed")
+	xl.Debug("join connections closed")
 }
 
 type ProxyManager struct {
diff --git a/server/proxy/stcp.go b/server/proxy/stcp.go
index 8cd43b20..27bfb143 100644
--- a/server/proxy/stcp.go
+++ b/server/proxy/stcp.go
@@ -24,14 +24,14 @@ type StcpProxy struct {
 }
 
 func (pxy *StcpProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
 	listener, errRet := pxy.rc.VisitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
 	if errRet != nil {
 		err = errRet
 		return
 	}
-	listener.AddLogPrefix(pxy.name)
 	pxy.listeners = append(pxy.listeners, listener)
-	pxy.Info("stcp proxy custom listen success")
+	xl.Info("stcp proxy custom listen success")
 
 	pxy.startListenHandler(pxy, HandleUserTcpConnection)
 	return
diff --git a/server/proxy/tcp.go b/server/proxy/tcp.go
index 388531a6..0ecfe260 100644
--- a/server/proxy/tcp.go
+++ b/server/proxy/tcp.go
@@ -16,9 +16,9 @@ package proxy
 
 import (
 	"fmt"
+	"net"
 
 	"github.com/fatedier/frp/models/config"
-	frpNet "github.com/fatedier/frp/utils/net"
 )
 
 type TcpProxy struct {
@@ -29,6 +29,7 @@ type TcpProxy struct {
 }
 
 func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
 	if pxy.cfg.Group != "" {
 		l, realPort, errRet := pxy.rc.TcpGroupCtl.Listen(pxy.name, pxy.cfg.Group, pxy.cfg.GroupKey, pxy.serverCfg.ProxyBindAddr, pxy.cfg.RemotePort)
 		if errRet != nil {
@@ -41,10 +42,8 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 			}
 		}()
 		pxy.realPort = realPort
-		listener := frpNet.WrapLogListener(l)
-		listener.AddLogPrefix(pxy.name)
-		pxy.listeners = append(pxy.listeners, listener)
-		pxy.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
+		pxy.listeners = append(pxy.listeners, l)
+		xl.Info("tcp proxy listen port [%d] in group [%s]", pxy.cfg.RemotePort, pxy.cfg.Group)
 	} else {
 		pxy.realPort, err = pxy.rc.TcpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
 		if err != nil {
@@ -55,14 +54,13 @@ func (pxy *TcpProxy) Run() (remoteAddr string, err error) {
 				pxy.rc.TcpPortManager.Release(pxy.realPort)
 			}
 		}()
-		listener, errRet := frpNet.ListenTcp(pxy.serverCfg.ProxyBindAddr, pxy.realPort)
+		listener, errRet := net.Listen("tcp", fmt.Sprintf("%s:%d", pxy.serverCfg.ProxyBindAddr, pxy.realPort))
 		if errRet != nil {
 			err = errRet
 			return
 		}
-		listener.AddLogPrefix(pxy.name)
 		pxy.listeners = append(pxy.listeners, listener)
-		pxy.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
+		xl.Info("tcp proxy listen port [%d]", pxy.cfg.RemotePort)
 	}
 
 	pxy.cfg.RemotePort = pxy.realPort
diff --git a/server/proxy/udp.go b/server/proxy/udp.go
index 453c7b56..397f60fb 100644
--- a/server/proxy/udp.go
+++ b/server/proxy/udp.go
@@ -54,6 +54,7 @@ type UdpProxy struct {
 }
 
 func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
 	pxy.realPort, err = pxy.rc.UdpPortManager.Acquire(pxy.name, pxy.cfg.RemotePort)
 	if err != nil {
 		return
@@ -74,10 +75,10 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 	udpConn, errRet := net.ListenUDP("udp", addr)
 	if errRet != nil {
 		err = errRet
-		pxy.Warn("listen udp port error: %v", err)
+		xl.Warn("listen udp port error: %v", err)
 		return
 	}
-	pxy.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
+	xl.Info("udp proxy listen port [%d]", pxy.cfg.RemotePort)
 
 	pxy.udpConn = udpConn
 	pxy.sendCh = make(chan *msg.UdpPacket, 1024)
@@ -91,11 +92,11 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 				rawMsg msg.Message
 				errRet error
 			)
-			pxy.Trace("loop waiting message from udp workConn")
+			xl.Trace("loop waiting message from udp workConn")
 			// client will send heartbeat in workConn for keeping alive
 			conn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second))
 			if rawMsg, errRet = msg.ReadMsg(conn); errRet != nil {
-				pxy.Warn("read from workConn for udp error: %v", errRet)
+				xl.Warn("read from workConn for udp error: %v", errRet)
 				conn.Close()
 				// notify proxy to start a new work connection
 				// ignore error here, it means the proxy is closed
@@ -107,11 +108,11 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 			conn.SetReadDeadline(time.Time{})
 			switch m := rawMsg.(type) {
 			case *msg.Ping:
-				pxy.Trace("udp work conn get ping message")
+				xl.Trace("udp work conn get ping message")
 				continue
 			case *msg.UdpPacket:
 				if errRet := errors.PanicToError(func() {
-					pxy.Trace("get udp message from workConn: %s", m.Content)
+					xl.Trace("get udp message from workConn: %s", m.Content)
 					pxy.readCh <- m
 					pxy.statsCollector.Mark(stats.TypeAddTrafficOut, &stats.AddTrafficOutPayload{
 						ProxyName:    pxy.GetName(),
@@ -119,7 +120,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 					})
 				}); errRet != nil {
 					conn.Close()
-					pxy.Info("reader goroutine for udp work connection closed")
+					xl.Info("reader goroutine for udp work connection closed")
 					return
 				}
 			}
@@ -133,15 +134,15 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 			select {
 			case udpMsg, ok := <-pxy.sendCh:
 				if !ok {
-					pxy.Info("sender goroutine for udp work connection closed")
+					xl.Info("sender goroutine for udp work connection closed")
 					return
 				}
 				if errRet = msg.WriteMsg(conn, udpMsg); errRet != nil {
-					pxy.Info("sender goroutine for udp work connection closed: %v", errRet)
+					xl.Info("sender goroutine for udp work connection closed: %v", errRet)
 					conn.Close()
 					return
 				} else {
-					pxy.Trace("send message to udp workConn: %s", udpMsg.Content)
+					xl.Trace("send message to udp workConn: %s", udpMsg.Content)
 					pxy.statsCollector.Mark(stats.TypeAddTrafficIn, &stats.AddTrafficInPayload{
 						ProxyName:    pxy.GetName(),
 						TrafficBytes: int64(len(udpMsg.Content)),
@@ -149,7 +150,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
 					continue
 				}
 			case <-ctx.Done():
-				pxy.Info("sender goroutine for udp work connection closed")
+				xl.Info("sender goroutine for udp work connection closed")
 				return
 			}
 		}
diff --git a/server/proxy/xtcp.go b/server/proxy/xtcp.go
index 925485c1..92291c06 100644
--- a/server/proxy/xtcp.go
+++ b/server/proxy/xtcp.go
@@ -31,8 +31,10 @@ type XtcpProxy struct {
 }
 
 func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
+	xl := pxy.xl
+
 	if pxy.rc.NatHoleController == nil {
-		pxy.Error("udp port for xtcp is not specified.")
+		xl.Error("udp port for xtcp is not specified.")
 		err = fmt.Errorf("xtcp is not supported in frps")
 		return
 	}
@@ -53,7 +55,7 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
 				}
 				errRet = msg.WriteMsg(workConn, m)
 				if errRet != nil {
-					pxy.Warn("write nat hole sid package error, %v", errRet)
+					xl.Warn("write nat hole sid package error, %v", errRet)
 					workConn.Close()
 					break
 				}
@@ -61,12 +63,12 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
 				go func() {
 					raw, errRet := msg.ReadMsg(workConn)
 					if errRet != nil {
-						pxy.Warn("read nat hole client ok package error: %v", errRet)
+						xl.Warn("read nat hole client ok package error: %v", errRet)
 						workConn.Close()
 						return
 					}
 					if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
-						pxy.Warn("read nat hole client ok package format error")
+						xl.Warn("read nat hole client ok package format error")
 						workConn.Close()
 						return
 					}
diff --git a/server/service.go b/server/service.go
index 6d561016..a4d2e9df 100644
--- a/server/service.go
+++ b/server/service.go
@@ -16,6 +16,7 @@ package server
 
 import (
 	"bytes"
+	"context"
 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/tls"
@@ -42,6 +43,7 @@ import (
 	"github.com/fatedier/frp/utils/util"
 	"github.com/fatedier/frp/utils/version"
 	"github.com/fatedier/frp/utils/vhost"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/net/mux"
 	fmux "github.com/hashicorp/yamux"
@@ -57,16 +59,16 @@ type Service struct {
 	muxer *mux.Mux
 
 	// Accept connections from client
-	listener frpNet.Listener
+	listener net.Listener
 
 	// Accept connections using kcp
-	kcpListener frpNet.Listener
+	kcpListener net.Listener
 
 	// Accept connections using websocket
-	websocketListener frpNet.Listener
+	websocketListener net.Listener
 
 	// Accept frp tls connections
-	tlsListener frpNet.Listener
+	tlsListener net.Listener
 
 	// Manage all controllers
 	ctlManager *ControlManager
@@ -135,7 +137,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	go svr.muxer.Serve()
 	ln = svr.muxer.DefaultListener()
 
-	svr.listener = frpNet.WrapLogListener(ln)
+	svr.listener = ln
 	log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
 
 	// Listen for accepting connections from client using kcp protocol.
@@ -194,7 +196,7 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 			}
 		}
 
-		svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(frpNet.WrapLogListener(l), 30*time.Second)
+		svr.rc.VhostHttpsMuxer, err = vhost.NewHttpsMuxer(l, 30*time.Second)
 		if err != nil {
 			err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
 			return
@@ -203,10 +205,9 @@ func NewService(cfg config.ServerCommonConf) (svr *Service, err error) {
 	}
 
 	// frp tls listener
-	tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
+	svr.tlsListener = svr.muxer.Listen(1, 1, func(data []byte) bool {
 		return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
 	})
-	svr.tlsListener = frpNet.WrapLogListener(tlsListener)
 
 	// Create nat hole controller.
 	if cfg.BindUdpPort > 0 {
@@ -258,7 +259,7 @@ func (svr *Service) Run() {
 	svr.HandleListener(svr.listener)
 }
 
-func (svr *Service) HandleListener(l frpNet.Listener) {
+func (svr *Service) HandleListener(l net.Listener) {
 	// Listen for incoming connections from client.
 	for {
 		c, err := l.Accept()
@@ -266,6 +267,9 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 			log.Warn("Listener for incoming connections from client closed")
 			return
 		}
+		// inject xlog object into net.Conn context
+		xl := xlog.New()
+		c = frpNet.NewContextConn(c, xlog.NewContext(context.Background(), xl))
 
 		log.Trace("start check TLS connection...")
 		originConn := c
@@ -278,8 +282,8 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 		log.Trace("success check TLS connection")
 
 		// Start a new goroutine for dealing connections.
-		go func(frpConn frpNet.Conn) {
-			dealFn := func(conn frpNet.Conn) {
+		go func(frpConn net.Conn) {
+			dealFn := func(conn net.Conn) {
 				var rawMsg msg.Message
 				conn.SetReadDeadline(time.Now().Add(connReadTimeout))
 				if rawMsg, err = msg.ReadMsg(conn); err != nil {
@@ -295,7 +299,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 					// If login failed, send error message there.
 					// Otherwise send success message in control's work goroutine.
 					if err != nil {
-						conn.Warn("%v", err)
+						xl.Warn("register control error: %v", err)
 						msg.WriteMsg(conn, &msg.LoginResp{
 							Version: version.Full(),
 							Error:   err.Error(),
@@ -306,7 +310,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 					svr.RegisterWorkConn(conn, m)
 				case *msg.NewVisitorConn:
 					if err = svr.RegisterVisitorConn(conn, m); err != nil {
-						conn.Warn("%v", err)
+						xl.Warn("register visitor conn error: %v", err)
 						msg.WriteMsg(conn, &msg.NewVisitorConnResp{
 							ProxyName: m.ProxyName,
 							Error:     err.Error(),
@@ -342,8 +346,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 						session.Close()
 						return
 					}
-					wrapConn := frpNet.WrapConn(stream)
-					go dealFn(wrapConn)
+					go dealFn(stream)
 				}
 			} else {
 				dealFn(frpConn)
@@ -352,8 +355,21 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
 	}
 }
 
-func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (err error) {
-	ctlConn.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
+func (svr *Service) RegisterControl(ctlConn net.Conn, loginMsg *msg.Login) (err error) {
+	// If client's RunId is empty, it's a new client, we just create a new controller.
+	// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
+	if loginMsg.RunId == "" {
+		loginMsg.RunId, err = util.RandId()
+		if err != nil {
+			return
+		}
+	}
+
+	ctx := frpNet.NewContextFromConn(ctlConn)
+	xl := xlog.FromContextSafe(ctx)
+	xl.AppendPrefix(loginMsg.RunId)
+	ctx = xlog.NewContext(ctx, xl)
+	xl.Info("client login info: ip [%s] version [%s] hostname [%s] os [%s] arch [%s]",
 		ctlConn.RemoteAddr().String(), loginMsg.Version, loginMsg.Hostname, loginMsg.Os, loginMsg.Arch)
 
 	// Check client version.
@@ -368,22 +384,12 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 		return
 	}
 
-	// If client's RunId is empty, it's a new client, we just create a new controller.
-	// Otherwise, we check if there is one controller has the same run id. If so, we release previous controller and start new one.
-	if loginMsg.RunId == "" {
-		loginMsg.RunId, err = util.RandId()
-		if err != nil {
-			return
-		}
-	}
-
-	ctl := NewControl(svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
+	ctl := NewControl(ctx, svr.rc, svr.pxyManager, svr.statsCollector, ctlConn, loginMsg, svr.cfg)
 
 	if oldCtl := svr.ctlManager.Add(loginMsg.RunId, ctl); oldCtl != nil {
 		oldCtl.allShutdown.WaitDone()
 	}
 
-	ctlConn.AddLogPrefix(loginMsg.RunId)
 	ctl.Start()
 
 	// for statistics
@@ -398,17 +404,18 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
 }
 
 // RegisterWorkConn register a new work connection to control and proxies need it.
-func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkConn) {
+func (svr *Service) RegisterWorkConn(workConn net.Conn, newMsg *msg.NewWorkConn) {
+	xl := frpNet.NewLogFromConn(workConn)
 	ctl, exist := svr.ctlManager.GetById(newMsg.RunId)
 	if !exist {
-		workConn.Warn("No client control found for run id [%s]", newMsg.RunId)
+		xl.Warn("No client control found for run id [%s]", newMsg.RunId)
 		return
 	}
 	ctl.RegisterWorkConn(workConn)
 	return
 }
 
-func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
+func (svr *Service) RegisterVisitorConn(visitorConn net.Conn, newMsg *msg.NewVisitorConn) error {
 	return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
 		newMsg.UseEncryption, newMsg.UseCompression)
 }
diff --git a/tests/ci/auto_test_frpc.ini b/tests/ci/auto_test_frpc.ini
index 28ea5fd5..b8d90bf2 100644
--- a/tests/ci/auto_test_frpc.ini
+++ b/tests/ci/auto_test_frpc.ini
@@ -2,8 +2,7 @@
 server_addr = 127.0.0.1
 server_port = 10700
 log_file = console
-# debug, info, warn, error
-log_level = debug
+log_level = trace
 token = 123456
 admin_port = 10600
 admin_user = abc
diff --git a/tests/ci/auto_test_frps.ini b/tests/ci/auto_test_frps.ini
index 9300b551..8948c987 100644
--- a/tests/ci/auto_test_frps.ini
+++ b/tests/ci/auto_test_frps.ini
@@ -2,8 +2,7 @@
 bind_addr = 0.0.0.0
 bind_port = 10700
 vhost_http_port = 10804
-log_file = console
-log_level = debug
+log_level = trace
 token = 123456
 allow_ports = 10000-20000,20002,30000-50000
 subdomain_host = sub.com
diff --git a/tests/mock/echo_server.go b/tests/mock/echo_server.go
index e029f505..5ae40ee9 100644
--- a/tests/mock/echo_server.go
+++ b/tests/mock/echo_server.go
@@ -11,7 +11,7 @@ import (
 )
 
 type EchoServer struct {
-	l frpNet.Listener
+	l net.Listener
 
 	port        int
 	repeatedNum int
@@ -30,7 +30,7 @@ func NewEchoServer(port int, repeatedNum int, specifyStr string) *EchoServer {
 }
 
 func (es *EchoServer) Start() error {
-	l, err := frpNet.ListenTcp("127.0.0.1", es.port)
+	l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", es.port))
 	if err != nil {
 		fmt.Printf("echo server listen error: %v\n", err)
 		return err
diff --git a/tests/util/util.go b/tests/util/util.go
index 4a4e6ffc..163ddc26 100644
--- a/tests/util/util.go
+++ b/tests/util/util.go
@@ -14,7 +14,6 @@ import (
 	"time"
 
 	"github.com/fatedier/frp/client"
-	frpNet "github.com/fatedier/frp/utils/net"
 )
 
 func GetProxyStatus(statusAddr string, user string, passwd string, name string) (status *client.ProxyStatusResp, err error) {
@@ -98,7 +97,7 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
 }
 
 func SendTcpMsg(addr string, msg string) (res string, err error) {
-	c, err := frpNet.ConnectTcpServer(addr)
+	c, err := net.Dial("tcp", addr)
 	if err != nil {
 		err = fmt.Errorf("connect to tcp server error: %v", err)
 		return
diff --git a/utils/log/log.go b/utils/log/log.go
index 1a9033bf..1ddf4cdc 100644
--- a/utils/log/log.go
+++ b/utils/log/log.go
@@ -91,71 +91,3 @@ func Debug(format string, v ...interface{}) {
 func Trace(format string, v ...interface{}) {
 	Log.Trace(format, v...)
 }
-
-// Logger is the log interface
-type Logger interface {
-	AddLogPrefix(string)
-	GetPrefixStr() string
-	GetAllPrefix() []string
-	ClearLogPrefix()
-	Error(string, ...interface{})
-	Warn(string, ...interface{})
-	Info(string, ...interface{})
-	Debug(string, ...interface{})
-	Trace(string, ...interface{})
-}
-
-type PrefixLogger struct {
-	prefix    string
-	allPrefix []string
-}
-
-func NewPrefixLogger(prefix string) *PrefixLogger {
-	logger := &PrefixLogger{
-		allPrefix: make([]string, 0),
-	}
-	logger.AddLogPrefix(prefix)
-	return logger
-}
-
-func (pl *PrefixLogger) AddLogPrefix(prefix string) {
-	if len(prefix) == 0 {
-		return
-	}
-
-	pl.prefix += "[" + prefix + "] "
-	pl.allPrefix = append(pl.allPrefix, prefix)
-}
-
-func (pl *PrefixLogger) GetPrefixStr() string {
-	return pl.prefix
-}
-
-func (pl *PrefixLogger) GetAllPrefix() []string {
-	return pl.allPrefix
-}
-
-func (pl *PrefixLogger) ClearLogPrefix() {
-	pl.prefix = ""
-	pl.allPrefix = make([]string, 0)
-}
-
-func (pl *PrefixLogger) Error(format string, v ...interface{}) {
-	Log.Error(pl.prefix+format, v...)
-}
-
-func (pl *PrefixLogger) Warn(format string, v ...interface{}) {
-	Log.Warn(pl.prefix+format, v...)
-}
-
-func (pl *PrefixLogger) Info(format string, v ...interface{}) {
-	Log.Info(pl.prefix+format, v...)
-}
-
-func (pl *PrefixLogger) Debug(format string, v ...interface{}) {
-	Log.Debug(pl.prefix+format, v...)
-}
-
-func (pl *PrefixLogger) Trace(format string, v ...interface{}) {
-	Log.Trace(pl.prefix+format, v...)
-}
diff --git a/utils/net/conn.go b/utils/net/conn.go
index 9f415ec8..6b1d3fa2 100644
--- a/utils/net/conn.go
+++ b/utils/net/conn.go
@@ -15,6 +15,7 @@
 package net
 
 import (
+	"context"
 	"crypto/tls"
 	"errors"
 	"fmt"
@@ -23,41 +24,64 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/fatedier/frp/utils/log"
-
+	"github.com/fatedier/frp/utils/xlog"
 	gnet "github.com/fatedier/golib/net"
 	kcp "github.com/fatedier/kcp-go"
 )
 
-// Conn is the interface of connections used in frp.
-type Conn interface {
-	net.Conn
-	log.Logger
+type ContextGetter interface {
+	Context() context.Context
 }
 
-type WrapLogConn struct {
-	net.Conn
-	log.Logger
+type ContextSetter interface {
+	WithContext(ctx context.Context)
 }
 
-func WrapConn(c net.Conn) Conn {
-	return &WrapLogConn{
-		Conn:   c,
-		Logger: log.NewPrefixLogger(""),
+func NewLogFromConn(conn net.Conn) *xlog.Logger {
+	if c, ok := conn.(ContextGetter); ok {
+		return xlog.FromContextSafe(c.Context())
 	}
+	return xlog.New()
+}
+
+func NewContextFromConn(conn net.Conn) context.Context {
+	if c, ok := conn.(ContextGetter); ok {
+		return c.Context()
+	}
+	return context.Background()
+}
+
+// ContextConn is the connection with context
+type ContextConn struct {
+	net.Conn
+
+	ctx context.Context
+}
+
+func NewContextConn(c net.Conn, ctx context.Context) *ContextConn {
+	return &ContextConn{
+		Conn: c,
+		ctx:  ctx,
+	}
+}
+
+func (c *ContextConn) WithContext(ctx context.Context) {
+	c.ctx = ctx
+}
+
+func (c *ContextConn) Context() context.Context {
+	return c.ctx
 }
 
 type WrapReadWriteCloserConn struct {
 	io.ReadWriteCloser
-	log.Logger
 
 	underConn net.Conn
 }
 
-func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) Conn {
+func WrapReadWriteCloserToConn(rwc io.ReadWriteCloser, underConn net.Conn) net.Conn {
 	return &WrapReadWriteCloserConn{
 		ReadWriteCloser: rwc,
-		Logger:          log.NewPrefixLogger(""),
 		underConn:       underConn,
 	}
 }
@@ -99,7 +123,6 @@ func (conn *WrapReadWriteCloserConn) SetWriteDeadline(t time.Time) error {
 
 type CloseNotifyConn struct {
 	net.Conn
-	log.Logger
 
 	// 1 means closed
 	closeFlag int32
@@ -108,10 +131,9 @@ type CloseNotifyConn struct {
 }
 
 // closeFn will be only called once
-func WrapCloseNotifyConn(c net.Conn, closeFn func()) Conn {
+func WrapCloseNotifyConn(c net.Conn, closeFn func()) net.Conn {
 	return &CloseNotifyConn{
 		Conn:    c,
-		Logger:  log.NewPrefixLogger(""),
 		closeFn: closeFn,
 	}
 }
@@ -128,7 +150,7 @@ func (cc *CloseNotifyConn) Close() (err error) {
 }
 
 type StatsConn struct {
-	Conn
+	net.Conn
 
 	closed     int64 // 1 means closed
 	totalRead  int64
@@ -136,7 +158,7 @@ type StatsConn struct {
 	statsFunc  func(totalRead, totalWrite int64)
 }
 
-func WrapStatsConn(conn Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
+func WrapStatsConn(conn net.Conn, statsFunc func(total, totalWrite int64)) *StatsConn {
 	return &StatsConn{
 		Conn:      conn,
 		statsFunc: statsFunc,
@@ -166,10 +188,10 @@ func (statsConn *StatsConn) Close() (err error) {
 	return
 }
 
-func ConnectServer(protocol string, addr string) (c Conn, err error) {
+func ConnectServer(protocol string, addr string) (c net.Conn, err error) {
 	switch protocol {
 	case "tcp":
-		return ConnectTcpServer(addr)
+		return net.Dial("tcp", addr)
 	case "kcp":
 		kcpConn, errRet := kcp.DialWithOptions(addr, nil, 10, 3)
 		if errRet != nil {
@@ -184,21 +206,17 @@ func ConnectServer(protocol string, addr string) (c Conn, err error) {
 		kcpConn.SetACKNoDelay(false)
 		kcpConn.SetReadBuffer(4194304)
 		kcpConn.SetWriteBuffer(4194304)
-		c = WrapConn(kcpConn)
+		c = kcpConn
 		return
 	default:
 		return nil, fmt.Errorf("unsupport protocol: %s", protocol)
 	}
 }
 
-func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn, err error) {
+func ConnectServerByProxy(proxyURL string, protocol string, addr string) (c net.Conn, err error) {
 	switch protocol {
 	case "tcp":
-		var conn net.Conn
-		if conn, err = gnet.DialTcpByProxy(proxyUrl, addr); err != nil {
-			return
-		}
-		return WrapConn(conn), nil
+		return gnet.DialTcpByProxy(proxyURL, addr)
 	case "kcp":
 		// http proxy is not supported for kcp
 		return ConnectServer(protocol, addr)
@@ -209,7 +227,7 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
 	}
 }
 
-func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
+func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c net.Conn, err error) {
 	c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
 	if err != nil {
 		return
diff --git a/utils/net/kcp.go b/utils/net/kcp.go
index 3d080fdd..39eb8987 100644
--- a/utils/net/kcp.go
+++ b/utils/net/kcp.go
@@ -18,17 +18,13 @@ import (
 	"fmt"
 	"net"
 
-	"github.com/fatedier/frp/utils/log"
-
 	kcp "github.com/fatedier/kcp-go"
 )
 
 type KcpListener struct {
-	net.Addr
 	listener  net.Listener
-	accept    chan Conn
+	acceptCh  chan net.Conn
 	closeFlag bool
-	log.Logger
 }
 
 func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
@@ -40,11 +36,9 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
 	listener.SetWriteBuffer(4194304)
 
 	l = &KcpListener{
-		Addr:      listener.Addr(),
 		listener:  listener,
-		accept:    make(chan Conn),
+		acceptCh:  make(chan net.Conn),
 		closeFlag: false,
-		Logger:    log.NewPrefixLogger(""),
 	}
 
 	go func() {
@@ -52,7 +46,7 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
 			conn, err := listener.AcceptKCP()
 			if err != nil {
 				if l.closeFlag {
-					close(l.accept)
+					close(l.acceptCh)
 					return
 				}
 				continue
@@ -64,14 +58,14 @@ func ListenKcp(bindAddr string, bindPort int) (l *KcpListener, err error) {
 			conn.SetWindowSize(1024, 1024)
 			conn.SetACKNoDelay(false)
 
-			l.accept <- WrapConn(conn)
+			l.acceptCh <- conn
 		}
 	}()
 	return l, err
 }
 
-func (l *KcpListener) Accept() (Conn, error) {
-	conn, ok := <-l.accept
+func (l *KcpListener) Accept() (net.Conn, error) {
+	conn, ok := <-l.acceptCh
 	if !ok {
 		return conn, fmt.Errorf("channel for kcp listener closed")
 	}
@@ -86,6 +80,10 @@ func (l *KcpListener) Close() error {
 	return nil
 }
 
+func (l *KcpListener) Addr() net.Addr {
+	return l.listener.Addr()
+}
+
 func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
 	kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
 	if err != nil {
diff --git a/utils/net/listener.go b/utils/net/listener.go
index 90ea59b4..3b199c83 100644
--- a/utils/net/listener.go
+++ b/utils/net/listener.go
@@ -19,65 +19,34 @@ import (
 	"net"
 	"sync"
 
-	"github.com/fatedier/frp/utils/log"
-
 	"github.com/fatedier/golib/errors"
 )
 
-type Listener interface {
-	Accept() (Conn, error)
-	Close() error
-	log.Logger
-}
-
-type LogListener struct {
-	l net.Listener
-	net.Listener
-	log.Logger
-}
-
-func WrapLogListener(l net.Listener) Listener {
-	return &LogListener{
-		l:        l,
-		Listener: l,
-		Logger:   log.NewPrefixLogger(""),
-	}
-}
-
-func (logL *LogListener) Accept() (Conn, error) {
-	c, err := logL.l.Accept()
-	return WrapConn(c), err
-}
-
 // Custom listener
 type CustomListener struct {
-	conns  chan Conn
-	closed bool
-	mu     sync.Mutex
-
-	log.Logger
+	acceptCh chan net.Conn
+	closed   bool
+	mu       sync.Mutex
 }
 
 func NewCustomListener() *CustomListener {
 	return &CustomListener{
-		conns:  make(chan Conn, 64),
-		Logger: log.NewPrefixLogger(""),
+		acceptCh: make(chan net.Conn, 64),
 	}
 }
 
-func (l *CustomListener) Accept() (Conn, error) {
-	conn, ok := <-l.conns
+func (l *CustomListener) Accept() (net.Conn, error) {
+	conn, ok := <-l.acceptCh
 	if !ok {
 		return nil, fmt.Errorf("listener closed")
 	}
-	conn.AddLogPrefix(l.GetPrefixStr())
 	return conn, nil
 }
 
-func (l *CustomListener) PutConn(conn Conn) error {
+func (l *CustomListener) PutConn(conn net.Conn) error {
 	err := errors.PanicToError(func() {
 		select {
-		case l.conns <- conn:
+		case l.acceptCh <- conn:
 		default:
 			conn.Close()
 		}
@@ -89,7 +58,7 @@ func (l *CustomListener) Close() error {
 	l.mu.Lock()
 	defer l.mu.Unlock()
 	if !l.closed {
-		close(l.conns)
+		close(l.acceptCh)
 		l.closed = true
 	}
 	return nil
diff --git a/utils/net/tcp.go b/utils/net/tcp.go
deleted file mode 100644
index 5c490d90..00000000
--- a/utils/net/tcp.go
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2016 fatedier, fatedier@gmail.com
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package net
-
-import (
-	"fmt"
-	"net"
-
-	"github.com/fatedier/frp/utils/log"
-)
-
-type TcpListener struct {
-	net.Addr
-	listener  net.Listener
-	accept    chan Conn
-	closeFlag bool
-	log.Logger
-}
-
-func ListenTcp(bindAddr string, bindPort int) (l *TcpListener, err error) {
-	tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", bindAddr, bindPort))
-	if err != nil {
-		return l, err
-	}
-	listener, err := net.ListenTCP("tcp", tcpAddr)
-	if err != nil {
-		return l, err
-	}
-
-	l = &TcpListener{
-		Addr:      listener.Addr(),
-		listener:  listener,
-		accept:    make(chan Conn),
-		closeFlag: false,
-		Logger:    log.NewPrefixLogger(""),
-	}
-
-	go func() {
-		for {
-			conn, err := listener.AcceptTCP()
-			if err != nil {
-				if l.closeFlag {
-					close(l.accept)
-					return
-				}
-				continue
-			}
-
-			c := NewTcpConn(conn)
-			l.accept <- c
-		}
-	}()
-	return l, err
-}
-
-// Wait util get one new connection or listener is closed
-// if listener is closed, err returned.
-func (l *TcpListener) Accept() (Conn, error) {
-	conn, ok := <-l.accept
-	if !ok {
-		return conn, fmt.Errorf("channel for tcp listener closed")
-	}
-	return conn, nil
-}
-
-func (l *TcpListener) Close() error {
-	if !l.closeFlag {
-		l.closeFlag = true
-		l.listener.Close()
-	}
-	return nil
-}
-
-// Wrap for TCPConn.
-type TcpConn struct {
-	net.Conn
-	log.Logger
-}
-
-func NewTcpConn(conn net.Conn) (c *TcpConn) {
-	c = &TcpConn{
-		Conn:   conn,
-		Logger: log.NewPrefixLogger(""),
-	}
-	return
-}
-
-func ConnectTcpServer(addr string) (c Conn, err error) {
-	servertAddr, err := net.ResolveTCPAddr("tcp", addr)
-	if err != nil {
-		return
-	}
-	conn, err := net.DialTCP("tcp", nil, servertAddr)
-	if err != nil {
-		return
-	}
-	c = NewTcpConn(conn)
-	return
-}
diff --git a/utils/net/tls.go b/utils/net/tls.go
index 4ac51d5f..b9fca317 100644
--- a/utils/net/tls.go
+++ b/utils/net/tls.go
@@ -26,13 +26,13 @@ var (
 	FRP_TLS_HEAD_BYTE = 0x17
 )
 
-func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
+func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out net.Conn) {
 	c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
-	out = WrapConn(tls.Client(c, tlsConfig))
+	out = tls.Client(c, tlsConfig)
 	return
 }
 
-func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
+func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out net.Conn, err error) {
 	sc, r := gnet.NewSharedConnSize(c, 2)
 	buf := make([]byte, 1)
 	var n int
@@ -44,9 +44,9 @@ func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, t
 	}
 
 	if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
-		out = WrapConn(tls.Server(c, tlsConfig))
+		out = tls.Server(c, tlsConfig)
 	} else {
-		out = WrapConn(sc)
+		out = sc
 	}
 	return
 }
diff --git a/utils/net/udp.go b/utils/net/udp.go
index e748e433..28a68139 100644
--- a/utils/net/udp.go
+++ b/utils/net/udp.go
@@ -21,8 +21,6 @@ import (
 	"sync"
 	"time"
 
-	"github.com/fatedier/frp/utils/log"
-
 	"github.com/fatedier/golib/pool"
 )
 
@@ -33,7 +31,6 @@ type UdpPacket struct {
 }
 
 type FakeUdpConn struct {
-	log.Logger
 	l *UdpListener
 
 	localAddr  net.Addr
@@ -47,7 +44,6 @@ type FakeUdpConn struct {
 
 func NewFakeUdpConn(l *UdpListener, laddr, raddr net.Addr) *FakeUdpConn {
 	fc := &FakeUdpConn{
-		Logger:     log.NewPrefixLogger(""),
 		l:          l,
 		localAddr:  laddr,
 		remoteAddr: raddr,
@@ -157,15 +153,13 @@ func (c *FakeUdpConn) SetWriteDeadline(t time.Time) error {
 }
 
 type UdpListener struct {
-	net.Addr
-	accept    chan Conn
+	addr      net.Addr
+	acceptCh  chan net.Conn
 	writeCh   chan *UdpPacket
 	readConn  net.Conn
 	closeFlag bool
 
 	fakeConns map[string]*FakeUdpConn
-
-	log.Logger
 }
 
 func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
@@ -176,11 +170,10 @@ func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
 	readConn, err := net.ListenUDP("udp", udpAddr)
 
 	l = &UdpListener{
-		Addr:      udpAddr,
-		accept:    make(chan Conn),
+		addr:      udpAddr,
+		acceptCh:  make(chan net.Conn),
 		writeCh:   make(chan *UdpPacket, 1000),
 		fakeConns: make(map[string]*FakeUdpConn),
-		Logger:    log.NewPrefixLogger(""),
 	}
 
 	// for reading
@@ -189,19 +182,19 @@ func ListenUDP(bindAddr string, bindPort int) (l *UdpListener, err error) {
 			buf := pool.GetBuf(1450)
 			n, remoteAddr, err := readConn.ReadFromUDP(buf)
 			if err != nil {
-				close(l.accept)
+				close(l.acceptCh)
 				close(l.writeCh)
 				return
 			}
 
 			fakeConn, exist := l.fakeConns[remoteAddr.String()]
 			if !exist || fakeConn.IsClosed() {
-				fakeConn = NewFakeUdpConn(l, l.Addr, remoteAddr)
+				fakeConn = NewFakeUdpConn(l, l.Addr(), remoteAddr)
 				l.fakeConns[remoteAddr.String()] = fakeConn
 			}
 			fakeConn.putPacket(buf[:n])
 
-			l.accept <- fakeConn
+			l.acceptCh <- fakeConn
 		}
 	}()
 
@@ -226,7 +219,6 @@ func (l *UdpListener) writeUdpPacket(packet *UdpPacket) (err error) {
 	defer func() {
 		if errRet := recover(); errRet != nil {
 			err = fmt.Errorf("udp write closed listener")
-			l.Info("udp write closed listener")
 		}
 	}()
 	l.writeCh <- packet
@@ -243,8 +235,8 @@ func (l *UdpListener) WriteMsg(buf []byte, remoteAddr *net.UDPAddr) (err error)
 	return
 }
 
-func (l *UdpListener) Accept() (Conn, error) {
-	conn, ok := <-l.accept
+func (l *UdpListener) Accept() (net.Conn, error) {
+	conn, ok := <-l.acceptCh
 	if !ok {
 		return conn, fmt.Errorf("channel for udp listener closed")
 	}
@@ -258,3 +250,7 @@ func (l *UdpListener) Close() error {
 	}
 	return nil
 }
+
+func (l *UdpListener) Addr() net.Addr {
+	return l.addr
+}
diff --git a/utils/net/websocket.go b/utils/net/websocket.go
index 99423373..36b6440c 100644
--- a/utils/net/websocket.go
+++ b/utils/net/websocket.go
@@ -8,8 +8,6 @@ import (
 	"net/url"
 	"time"
 
-	"github.com/fatedier/frp/utils/log"
-
 	"golang.org/x/net/websocket"
 )
 
@@ -22,10 +20,8 @@ const (
 )
 
 type WebsocketListener struct {
-	net.Addr
-	ln     net.Listener
-	accept chan Conn
-	log.Logger
+	ln       net.Listener
+	acceptCh chan net.Conn
 
 	server    *http.Server
 	httpMutex *http.ServeMux
@@ -35,9 +31,7 @@ type WebsocketListener struct {
 // ln: tcp listener for websocket connections
 func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
 	wl = &WebsocketListener{
-		Addr:   ln.Addr(),
-		accept: make(chan Conn),
-		Logger: log.NewPrefixLogger(""),
+		acceptCh: make(chan net.Conn),
 	}
 
 	muxer := http.NewServeMux()
@@ -46,7 +40,7 @@ func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
 		conn := WrapCloseNotifyConn(c, func() {
 			close(notifyCh)
 		})
-		wl.accept <- conn
+		wl.acceptCh <- conn
 		<-notifyCh
 	}))
 
@@ -68,8 +62,8 @@ func ListenWebsocket(bindAddr string, bindPort int) (*WebsocketListener, error)
 	return l, nil
 }
 
-func (p *WebsocketListener) Accept() (Conn, error) {
-	c, ok := <-p.accept
+func (p *WebsocketListener) Accept() (net.Conn, error) {
+	c, ok := <-p.acceptCh
 	if !ok {
 		return nil, ErrWebsocketListenerClosed
 	}
@@ -80,8 +74,12 @@ func (p *WebsocketListener) Close() error {
 	return p.server.Close()
 }
 
+func (p *WebsocketListener) Addr() net.Addr {
+	return p.ln.Addr()
+}
+
 // addr: domain:port
-func ConnectWebsocketServer(addr string) (Conn, error) {
+func ConnectWebsocketServer(addr string) (net.Conn, error) {
 	addr = "ws://" + addr + FrpWebsocketPath
 	uri, err := url.Parse(addr)
 	if err != nil {
@@ -101,6 +99,5 @@ func ConnectWebsocketServer(addr string) (Conn, error) {
 	if err != nil {
 		return nil, err
 	}
-	c := WrapConn(conn)
-	return c, nil
+	return conn, nil
 }
diff --git a/utils/vhost/https.go b/utils/vhost/https.go
index 12fc8d0c..53177019 100644
--- a/utils/vhost/https.go
+++ b/utils/vhost/https.go
@@ -17,11 +17,10 @@ package vhost
 import (
 	"fmt"
 	"io"
+	"net"
 	"strings"
 	"time"
 
-	frpNet "github.com/fatedier/frp/utils/net"
-
 	gnet "github.com/fatedier/golib/net"
 	"github.com/fatedier/golib/pool"
 )
@@ -48,7 +47,7 @@ type HttpsMuxer struct {
 	*VhostMuxer
 }
 
-func NewHttpsMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpsMuxer, error) {
+func NewHttpsMuxer(listener net.Listener, timeout time.Duration) (*HttpsMuxer, error) {
 	mux, err := NewVhostMuxer(listener, GetHttpsHostname, nil, nil, timeout)
 	return &HttpsMuxer{mux}, err
 }
@@ -182,7 +181,7 @@ func readHandshake(rd io.Reader) (host string, err error) {
 	return
 }
 
-func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
+func GetHttpsHostname(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
 	reqInfoMap := make(map[string]string, 0)
 	sc, rd := gnet.NewSharedConn(c)
 	host, err := readHandshake(rd)
@@ -191,5 +190,5 @@ func GetHttpsHostname(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err er
 	}
 	reqInfoMap["Host"] = host
 	reqInfoMap["Scheme"] = "https"
-	return frpNet.WrapConn(sc), reqInfoMap, nil
+	return sc, reqInfoMap, nil
 }
diff --git a/utils/vhost/vhost.go b/utils/vhost/vhost.go
index d3e54fa1..57f82394 100644
--- a/utils/vhost/vhost.go
+++ b/utils/vhost/vhost.go
@@ -13,22 +13,25 @@
 package vhost
 
 import (
+	"context"
 	"fmt"
+	"net"
 	"strings"
 	"time"
 
 	"github.com/fatedier/frp/utils/log"
 	frpNet "github.com/fatedier/frp/utils/net"
+	"github.com/fatedier/frp/utils/xlog"
 
 	"github.com/fatedier/golib/errors"
 )
 
-type muxFunc func(frpNet.Conn) (frpNet.Conn, map[string]string, error)
-type httpAuthFunc func(frpNet.Conn, string, string, string) (bool, error)
-type hostRewriteFunc func(frpNet.Conn, string) (frpNet.Conn, error)
+type muxFunc func(net.Conn) (net.Conn, map[string]string, error)
+type httpAuthFunc func(net.Conn, string, string, string) (bool, error)
+type hostRewriteFunc func(net.Conn, string) (net.Conn, error)
 
 type VhostMuxer struct {
-	listener       frpNet.Listener
+	listener       net.Listener
 	timeout        time.Duration
 	vhostFunc      muxFunc
 	authFunc       httpAuthFunc
@@ -36,7 +39,7 @@ type VhostMuxer struct {
 	registryRouter *VhostRouters
 }
 
-func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
+func NewVhostMuxer(listener net.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
 	mux = &VhostMuxer{
 		listener:       listener,
 		timeout:        timeout,
@@ -49,7 +52,7 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
 	return mux, nil
 }
 
-type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
+type CreateConnFunc func(remoteAddr string) (net.Conn, error)
 
 // VhostRouteConfig is the params used to match HTTP requests
 type VhostRouteConfig struct {
@@ -65,7 +68,7 @@ type VhostRouteConfig struct {
 
 // listen for a new domain name, if rewriteHost is not empty  and rewriteFunc is not nil
 // then rewrite the host header to rewriteHost
-func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
+func (v *VhostMuxer) Listen(ctx context.Context, cfg *VhostRouteConfig) (l *Listener, err error) {
 	l = &Listener{
 		name:        cfg.Domain,
 		location:    cfg.Location,
@@ -73,8 +76,8 @@ func (v *VhostMuxer) Listen(cfg *VhostRouteConfig) (l *Listener, err error) {
 		userName:    cfg.Username,
 		passWord:    cfg.Password,
 		mux:         v,
-		accept:      make(chan frpNet.Conn),
-		Logger:      log.NewPrefixLogger(""),
+		accept:      make(chan net.Conn),
+		ctx:         ctx,
 	}
 	err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
 	if err != nil {
@@ -123,7 +126,7 @@ func (v *VhostMuxer) run() {
 	}
 }
 
-func (v *VhostMuxer) handle(c frpNet.Conn) {
+func (v *VhostMuxer) handle(c net.Conn) {
 	if err := c.SetDeadline(time.Now().Add(v.timeout)); err != nil {
 		c.Close()
 		return
@@ -146,13 +149,14 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
 		c.Close()
 		return
 	}
+	xl := xlog.FromContextSafe(l.ctx)
 
 	// if authFunc is exist and userName/password is set
 	// then verify user access
 	if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
 		bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
 		if bAccess == false || err != nil {
-			l.Debug("check http Authorization failed")
+			xl.Debug("check http Authorization failed")
 			res := noAuthResponse()
 			res.Write(c)
 			c.Close()
@@ -166,12 +170,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
 	}
 	c = sConn
 
-	l.Debug("get new http request host [%s] path [%s]", name, path)
+	xl.Debug("get new http request host [%s] path [%s]", name, path)
 	err = errors.PanicToError(func() {
 		l.accept <- c
 	})
 	if err != nil {
-		l.Warn("listener is already closed, ignore this request")
+		xl.Warn("listener is already closed, ignore this request")
 	}
 }
 
@@ -182,11 +186,12 @@ type Listener struct {
 	userName    string
 	passWord    string
 	mux         *VhostMuxer // for closing VhostMuxer
-	accept      chan frpNet.Conn
-	log.Logger
+	accept      chan net.Conn
+	ctx         context.Context
 }
 
-func (l *Listener) Accept() (frpNet.Conn, error) {
+func (l *Listener) Accept() (net.Conn, error) {
+	xl := xlog.FromContextSafe(l.ctx)
 	conn, ok := <-l.accept
 	if !ok {
 		return nil, fmt.Errorf("Listener closed")
@@ -198,17 +203,13 @@ func (l *Listener) Accept() (frpNet.Conn, error) {
 	if l.mux.rewriteFunc != nil {
 		sConn, err := l.mux.rewriteFunc(conn, l.rewriteHost)
 		if err != nil {
-			l.Warn("host header rewrite failed: %v", err)
+			xl.Warn("host header rewrite failed: %v", err)
 			return nil, fmt.Errorf("host header rewrite failed")
 		}
-		l.Debug("rewrite host to [%s] success", l.rewriteHost)
+		xl.Debug("rewrite host to [%s] success", l.rewriteHost)
 		conn = sConn
 	}
-
-	for _, prefix := range l.GetAllPrefix() {
-		conn.AddLogPrefix(prefix)
-	}
-	return conn, nil
+	return frpNet.NewContextConn(conn, l.ctx), nil
 }
 
 func (l *Listener) Close() error {
@@ -220,3 +221,7 @@ func (l *Listener) Close() error {
 func (l *Listener) Name() string {
 	return l.name
 }
+
+func (l *Listener) Addr() net.Addr {
+	return (*net.TCPAddr)(nil)
+}
diff --git a/utils/xlog/ctx.go b/utils/xlog/ctx.go
new file mode 100644
index 00000000..1d3619be
--- /dev/null
+++ b/utils/xlog/ctx.go
@@ -0,0 +1,42 @@
+// Copyright 2019 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package xlog
+
+import (
+	"context"
+)
+
+type key int
+
+const (
+	xlogKey key = 0
+)
+
+func NewContext(ctx context.Context, xl *Logger) context.Context {
+	return context.WithValue(ctx, xlogKey, xl)
+}
+
+func FromContext(ctx context.Context) (xl *Logger, ok bool) {
+	xl, ok = ctx.Value(xlogKey).(*Logger)
+	return
+}
+
+func FromContextSafe(ctx context.Context) *Logger {
+	xl, ok := ctx.Value(xlogKey).(*Logger)
+	if !ok {
+		xl = New()
+	}
+	return xl
+}
diff --git a/utils/xlog/xlog.go b/utils/xlog/xlog.go
new file mode 100644
index 00000000..a1c48672
--- /dev/null
+++ b/utils/xlog/xlog.go
@@ -0,0 +1,73 @@
+// Copyright 2019 fatedier, fatedier@gmail.com
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package xlog
+
+import (
+	"github.com/fatedier/frp/utils/log"
+)
+
+// Logger is not thread safety for operations on prefix
+type Logger struct {
+	prefixes []string
+
+	prefixString string
+}
+
+func New() *Logger {
+	return &Logger{
+		prefixes: make([]string, 0),
+	}
+}
+
+func (l *Logger) ResetPrefixes() (old []string) {
+	old = l.prefixes
+	l.prefixes = make([]string, 0)
+	l.prefixString = ""
+	return
+}
+
+func (l *Logger) AppendPrefix(prefix string) *Logger {
+	l.prefixes = append(l.prefixes, prefix)
+	l.prefixString += "[" + prefix + "] "
+	return l
+}
+
+func (l *Logger) Spawn() *Logger {
+	nl := New()
+	for _, v := range l.prefixes {
+		nl.AppendPrefix(v)
+	}
+	return nl
+}
+
+func (l *Logger) Error(format string, v ...interface{}) {
+	log.Log.Error(l.prefixString+format, v...)
+}
+
+func (l *Logger) Warn(format string, v ...interface{}) {
+	log.Log.Warn(l.prefixString+format, v...)
+}
+
+func (l *Logger) Info(format string, v ...interface{}) {
+	log.Log.Info(l.prefixString+format, v...)
+}
+
+func (l *Logger) Debug(format string, v ...interface{}) {
+	log.Log.Debug(l.prefixString+format, v...)
+}
+
+func (l *Logger) Trace(format string, v ...interface{}) {
+	log.Log.Trace(l.prefixString+format, v...)
+}

From dc0fd60d3058025a6f0fde9f52216d6eca45c6ed Mon Sep 17 00:00:00 2001
From: Weisi Dai <weisi@x-research.com>
Date: Sun, 13 Oct 2019 14:46:08 -0700
Subject: [PATCH 5/7] frp: Fix typos in English Readme.

---
 README.md | 339 +++++++++++++++++++++++++++---------------------------
 1 file changed, 169 insertions(+), 170 deletions(-)

diff --git a/README.md b/README.md
index dbb977a6..ab9d8356 100644
--- a/README.md
+++ b/README.md
@@ -6,53 +6,53 @@
 
 ## What is frp?
 
-frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. As of now, it supports tcp & udp, as well as http and https protocols, where requests can be forwarded to internal services by domain name.
+frp is a fast reverse proxy to help you expose a local server behind a NAT or firewall to the Internet. As of now, it supports **TCP** and **UDP**, as well as **HTTP** and **HTTPS** protocols, where requests can be forwarded to internal services by domain name.
 
-Now it also tries to support p2p connect.
+frp also has a P2P connect mode.
 
 ## Table of Contents
 
 <!-- vim-markdown-toc GFM -->
 
-* [Status](#status)
+* [Development Status](#development-status)
 * [Architecture](#architecture)
 * [Example Usage](#example-usage)
     * [Access your computer in LAN by SSH](#access-your-computer-in-lan-by-ssh)
     * [Visit your web service in LAN by custom domains](#visit-your-web-service-in-lan-by-custom-domains)
     * [Forward DNS query request](#forward-dns-query-request)
-    * [Forward unix domain socket](#forward-unix-domain-socket)
-    * [Expose a simple http file server](#expose-a-simple-http-file-server)
+    * [Forward Unix domain socket](#forward-unix-domain-socket)
+    * [Expose a simple HTTP file server](#expose-a-simple-http-file-server)
     * [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
-    * [Expose your service in security](#expose-your-service-in-security)
+    * [Expose your service privately](#expose-your-service-privately)
     * [P2P Mode](#p2p-mode)
 * [Features](#features)
-    * [Configuration File](#configuration-file)
-    * [Configuration file template](#configuration-file-template)
+    * [Configuration Files](#configuration-files)
+    * [Using Environment Variables](#using-environment-variables)
     * [Dashboard](#dashboard)
     * [Admin UI](#admin-ui)
-    * [Authentication](#authentication)
+    * [Authenticating the Client](#authenticating-the-client)
     * [Encryption and Compression](#encryption-and-compression)
         * [TLS](#tls)
-    * [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
+    * [Hot-Reloading frpc configuration](#hot-reloading-frpc-configuration)
     * [Get proxy status from client](#get-proxy-status-from-client)
-    * [Port White List](#port-white-list)
+    * [Only allowing certain ports on the server](#only-allowing-certain-ports-on-the-server)
     * [Port Reuse](#port-reuse)
     * [TCP Stream Multiplexing](#tcp-stream-multiplexing)
     * [Support KCP Protocol](#support-kcp-protocol)
-    * [Connection Pool](#connection-pool)
+    * [Connection Pooling](#connection-pooling)
     * [Load balancing](#load-balancing)
-    * [Health Check](#health-check)
-    * [Rewriting the Host Header](#rewriting-the-host-header)
-    * [Set Headers In HTTP Request](#set-headers-in-http-request)
+    * [Service Health Check](#service-health-check)
+    * [Rewriting the HTTP Host Header](#rewriting-the-http-host-header)
+    * [Setting other HTTP Headers](#setting-other-http-headers)
     * [Get Real IP](#get-real-ip)
         * [HTTP X-Forwarded-For](#http-x-forwarded-for)
         * [Proxy Protocol](#proxy-protocol)
-    * [Password protecting your web service](#password-protecting-your-web-service)
+    * [Require HTTP Basic auth (password) for web services](#require-http-basic-auth-password-for-web-services)
     * [Custom subdomain names](#custom-subdomain-names)
     * [URL routing](#url-routing)
-    * [Connect frps by HTTP PROXY](#connect-frps-by-http-proxy)
+    * [Connecting to frps via HTTP PROXY](#connecting-to-frps-via-http-proxy)
     * [Range ports mapping](#range-ports-mapping)
-    * [Plugin](#plugin)
+    * [Plugins](#plugins)
 * [Development Plan](#development-plan)
 * [Contributing](#contributing)
 * [Donation](#donation)
@@ -62,11 +62,11 @@ Now it also tries to support p2p connect.
 
 <!-- vim-markdown-toc -->
 
-## Status
+## Development Status
 
-frp is under development, you can try by using the latest release version under the 'master' branch. You can use the 'dev' branch instead for the version in development.
+frp is under development. Try the latest release version in the `master` branch, or use the `dev` branch for the version in development.
 
-**We may change any protocol and can't promise backward compatibility. Please check the release log when upgrading.**
+**The protocol might change at a release and we don't promise backwards compatibility. Please check the release log when upgrading the client and the server.**
 
 ## Architecture
 
@@ -74,15 +74,15 @@ frp is under development, you can try by using the latest release version under
 
 ## Example Usage
 
-Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your os and arch.
+Firstly, download the latest programs from [Release](https://github.com/fatedier/frp/releases) page according to your operating system and architecture.
 
-Put **frps** and **frps.ini** to your server with public IP.
+Put `frps` and `frps.ini` onto your server A with public IP.
 
-Put **frpc** and **frpc.ini** to your server in LAN.
+Put `frpc` and `frpc.ini` onto your server B in LAN (that can't be connected from public Internet).
 
 ### Access your computer in LAN by SSH
 
-1. Modify frps.ini:
+1. Modify `frps.ini` on server A:
 
   ```ini
   # frps.ini
@@ -90,11 +90,11 @@ Put **frpc** and **frpc.ini** to your server in LAN.
   bind_port = 7000
   ```
 
-2. Start frps:
+2. Start `frps` on server A:
 
   `./frps -c ./frps.ini`
 
-3. Modify frpc.ini, `server_addr` is your frps's server IP:
+3. On server B, modify `frpc.ini` to put in your `frps` server public IP as `server_addr` field:
 
   ```ini
   # frpc.ini
@@ -109,21 +109,21 @@ Put **frpc** and **frpc.ini** to your server in LAN.
   remote_port = 6000
   ```
 
-4. Start frpc:
+4. Start `frpc` on server B:
 
   `./frpc -c ./frpc.ini`
 
-5. Connect to server in LAN by ssh assuming that username is test:
+5. From another machine, SSH to server B like this (assuming that username is `test`):
 
   `ssh -oPort=6000 test@x.x.x.x`
 
 ### Visit your web service in LAN by custom domains
 
-Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local ip.
+Sometimes we want to expose a local web service behind a NAT network to others for testing with your own domain name and unfortunately we can't resolve a domain name to a local IP.
 
-However, we can expose a http or https service using frp.
+However, we can expose an HTTP(S) service using frp.
 
-1. Modify frps.ini, configure http port 8080:
+1. Modify `frps.ini`, set the vhost HTTP port to 8080:
 
   ```ini
   # frps.ini
@@ -132,11 +132,11 @@ However, we can expose a http or https service using frp.
   vhost_http_port = 8080
   ```
 
-2. Start frps:
+2. Start `frps`:
 
   `./frps -c ./frps.ini`
 
-3. Modify frpc.ini and set remote frps server's IP as x.x.x.x. The `local_port` is the port of your web service:
+3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server. The `local_port` is the port of your web service:
 
   ```ini
   # frpc.ini
@@ -147,20 +147,20 @@ However, we can expose a http or https service using frp.
   [web]
   type = http
   local_port = 80
-  custom_domains = www.yourdomain.com
+  custom_domains = www.example.com
   ```
 
-4. Start frpc:
+4. Start `frpc`:
 
   `./frpc -c ./frpc.ini`
 
-5. Resolve A record of `www.yourdomain.com` to IP `x.x.x.x` or CNAME record to your origin domain.
+5. Resolve A record of `www.example.com` to the public IP of the remote frps server or CNAME record to your origin domain.
 
-6. Now visit your local web service using url `http://www.yourdomain.com:8080`.
+6. Now visit your local web service using url `http://www.example.com:8080`.
 
 ### Forward DNS query request
 
-1. Modify frps.ini:
+1. Modify `frps.ini`:
 
   ```ini
   # frps.ini
@@ -168,11 +168,11 @@ However, we can expose a http or https service using frp.
   bind_port = 7000
   ```
 
-2. Start frps:
+2. Start `frps`:
 
   `./frps -c ./frps.ini`
 
-3. Modify frpc.ini, set remote frps's server IP as x.x.x.x, forward dns query request to Google's dns server `8.8.8.8:53`:
+3. Modify `frpc.ini` and set `server_addr` to the IP address of the remote frps server, forward DNS query request to Google Public DNS server `8.8.8.8:53`:
 
   ```ini
   # frpc.ini
@@ -191,17 +191,17 @@ However, we can expose a http or https service using frp.
 
   `./frpc -c ./frpc.ini`
 
-5. Send dns query request by dig:
+5. Test DNS resolution using `dig` command:
 
   `dig @x.x.x.x -p 6000 www.google.com`
 
-### Forward unix domain socket
+### Forward Unix domain socket
 
-Use tcp port to connect to a unix domain socket (e.g. Docker daemon's socket).
+Expose a Unix domain socket (e.g. the Docker daemon socket) as TCP.
 
-Configure frps same as above.
+Configure `frps` same as above.
 
-1. Start frpc with configurations:
+1. Start `frpc` with configuration:
 
   ```ini
   # frpc.ini
@@ -216,17 +216,17 @@ Configure frps same as above.
   plugin_unix_path = /var/run/docker.sock
   ```
 
-2. Get docker version by curl command:
+2. Test: Get Docker version using `curl`:
 
   `curl http://x.x.x.x:6000/version`
 
-### Expose a simple http file server
+### Expose a simple HTTP file server
 
-A simple way to browse files in the LAN.
+Browser your files stored in the LAN, from public Internet.
 
-Configure frps same as above.
+Configure `frps` same as above.
 
-1. Start frpc with configurations:
+1. Start `frpc` with configuration:
 
   ```ini
   # frpc.ini
@@ -238,17 +238,17 @@ Configure frps same as above.
   type = tcp
   remote_port = 6000
   plugin = static_file
-  plugin_local_path = /tmp/file
+  plugin_local_path = /tmp/files
   plugin_strip_prefix = static
   plugin_http_user = abc
   plugin_http_passwd = abc
   ```
 
-2. Visit `http://x.x.x.x:6000/static/` by your browser, specify correct user and password, so you can see files in `/tmp/file`.
+2. Visit `http://x.x.x.x:6000/static/` from your browser and specify correct user and password to view files in `/tmp/files` on the `frpc` machine.
 
 ### Enable HTTPS for local HTTP service
 
-1. Start frpc with configurations:
+1. Start `frpc` with configuration:
 
   ```ini
   # frpc.ini
@@ -256,9 +256,9 @@ Configure frps same as above.
   server_addr = x.x.x.x
   server_port = 7000
 
-  [test_htts2http]
+  [test_https2http]
   type = https
-  custom_domains = test.yourdomain.com
+  custom_domains = test.example.com
 
   plugin = https2http
   plugin_local_addr = 127.0.0.1:80
@@ -268,17 +268,15 @@ Configure frps same as above.
   plugin_header_X-From-Where = frp
   ```
 
-2. Visit `https://test.yourdomain.com`.
+2. Visit `https://test.example.com`.
 
-### Expose your service in security
+### Expose your service privately
 
-Some services will be at risk if exposed directly to the public network.
+Some services will be at risk if exposed directly to the public network. With **STCP** (secret TCP) mode, a preshared key is needed to access the service from another client.
 
-**stcp(secret tcp)** helps you create a proxy while keeping the service secure.
+Configure `frps` same as above.
 
-Configure frps same as above.
-
-1. Start frpc, forward ssh port and `remote_port` are useless:
+1. Start `frpc` on machine B with the following config. This example is for exposing the SSH service (port 22), and note the `sk` field for the preshared key, and that the `remote_port` field is removed here:
 
   ```ini
   # frpc.ini
@@ -293,7 +291,7 @@ Configure frps same as above.
   local_port = 22
   ```
 
-2. Start another frpc in which you want to connect this ssh server:
+2. Start another `frpc` (typically on another machine C) with the following config to access the SSH service with a security key (`sk` field):
 
   ```ini
   # frpc.ini
@@ -310,23 +308,24 @@ Configure frps same as above.
   bind_port = 6000
   ```
 
-3. Connect to server in LAN using ssh assuming that username is test:
+3. On machine C, connect to SSH on machine B, using this command:
 
-  `ssh -oPort=6000 test@127.0.0.1`
+  `ssh -oPort=6000 127.0.0.1`
 
 ### P2P Mode
 
-**xtcp** is designed for transmitting a large amount of data directly between two client.
+**xtcp** is designed for transmitting large amounts of data directly between clients. A frps server is still needed, as P2P here only refers the actual data transmission.
 
-It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
+Note it can't penetrate all types of NAT devices. You might want to fallback to **stcp** if **xtcp** doesn't work.
 
-1. Configure a udp port for xtcp:
+1. In `frps.ini` configure a UDP port for xtcp:
 
   ```ini
+  # frps.ini
   bind_udp_port = 7001
   ```
 
-2. Start frpc, forward ssh port and `remote_port` are useless:
+2. Start `frpc` on machine B, expose the SSH port. Note that `remote_port` field is removed:
 
   ```ini
   # frpc.ini
@@ -341,7 +340,7 @@ It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** do
   local_port = 22
   ```
 
-3. Start another frpc in which you want to connect this ssh server:
+3. Start another `frpc` (typically on another machine C) with the config to connect to SSH using P2P mode:
 
   ```ini
   # frpc.ini
@@ -358,23 +357,23 @@ It can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** do
   bind_port = 6000
   ```
 
-4. Connect to server in LAN using ssh assuming that username is test:
+4. On machine C, connect to SSH on machine B, using this command:
 
-  `ssh -oPort=6000 test@127.0.0.1`
+  `ssh -oPort=6000 127.0.0.1`
 
 ## Features
 
-### Configuration File
+### Configuration Files
 
-You can find features not mentioned in this document from the full example configuration files.
+Read the full example configuration files to find out even more features not described here.
 
-[frps full configuration file](./conf/frps_full.ini)
+[Full configuration file for frps (Server)](./conf/frps_full.ini)
 
-[frpc full configuration file](./conf/frpc_full.ini)
+[Full configuration file for frpc (Client)](./conf/frpc_full.ini)
 
-### Configuration file template
+### Using Environment Variables
 
-Configuration file template can be rendered using os environments. Template uses Go's standard format.
+Environment variables can be referenced in the configuration file, using Go's standard format:
 
 ```ini
 # frpc.ini
@@ -389,7 +388,7 @@ local_port = 22
 remote_port = {{ .Envs.FRP_SSH_REMOTE_PORT }}
 ```
 
-Start frpc program:
+With the config above, variables can be passed into `frpc` program like this:
 
 ```
 export FRP_SERVER_ADDR="x.x.x.x"
@@ -397,8 +396,7 @@ export FRP_SSH_REMOTE_PORT="6000"
 ./frpc -c ./frpc.ini
 ```
 
-frpc will auto render configuration file template using os environments.
-All environments has prefix `.Envs`.
+`frpc` will render configuration file template using OS environment variables. Remember to prefix your reference with `.Envs`.
 
 ### Dashboard
 
@@ -414,13 +412,13 @@ dashboard_user = admin
 dashboard_pwd = admin
 ```
 
-Then visit `http://[server_addr]:7500` to see the dashboard, default username and password are both `admin`.
+Then visit `http://[server_addr]:7500` to see the dashboard, with username and password both being `admin` by default.
 
 ![dashboard](/doc/pic/dashboard.png)
 
 ### Admin UI
 
-Admin UI help you check and manage frpc's configuration.
+The Admin UI helps you check and manage frpc's configuration.
 
 Configure an address for admin UI to enable this feature:
 
@@ -432,15 +430,15 @@ admin_user = admin
 admin_pwd = admin
 ```
 
-Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
+Then visit `http://127.0.0.1:7400` to see admin UI, with username and password both being `admin` by default.
 
-### Authentication
+### Authenticating the Client
 
-`token` in frps.ini and frpc.ini should be equal.
+Always use the same `token` in the `[common]` section in `frps.ini` and `frpc.ini`.
 
 ### Encryption and Compression
 
-Default value is false, you could decide if the proxy will use encryption or compression:
+The features are off by default. You can turn on encryption and/or compression:
 
 ```ini
 # frpc.ini
@@ -454,15 +452,15 @@ use_compression = true
 
 #### TLS
 
-frp supports TLS protocol between frpc and frps since v0.25.0.
+frp supports the TLS protocol between `frpc` and `frps` since v0.25.0.
 
-Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
+Config `tls_enable = true` in the `[common]` section to `frpc.ini` to enable this feature.
 
-For port multiplexing, frp sends a first byte 0x17 to dial a TLS connection.
+For port multiplexing, frp sends a first byte `0x17` to dial a TLS connection.
 
-### Hot-Reload frpc configuration
+### Hot-Reloading frpc configuration
 
-First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
+The `admin_addr` and `admin_port` fields are required for enabling HTTP API:
 
 ```ini
 # frpc.ini
@@ -471,17 +469,17 @@ admin_addr = 127.0.0.1
 admin_port = 7400
 ```
 
-Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let frpc create or update or delete proxies.
+Then run command `frpc reload -c ./frpc.ini` and wait for about 10 seconds to let `frpc` create or update or delete proxies.
 
 **Note that parameters in [common] section won't be modified except 'start'.**
 
 ### Get proxy status from client
 
-Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set admin port in frpc's configuration file.
+Use `frpc status -c ./frpc.ini` to get status of all proxies. The `admin_addr` and `admin_port` fields are required for enabling HTTP API.
 
-### Port White List
+### Only allowing certain ports on the server
 
-`allow_ports` in frps.ini is used to prevent abuse of ports:
+`allow_ports` in `frps.ini` is used to avoid abuse of ports:
 
 ```ini
 # frps.ini
@@ -489,7 +487,7 @@ Use `frpc status -c ./frpc.ini` to get status of all proxies. You need to set ad
 allow_ports = 2000-3000,3001,3003,4000-50000
 ```
 
-`allow_ports` consists of a specific port or a range of ports divided by `,`.
+`allow_ports` consists of specific ports or port ranges (lowest port number, dash `-`, highest port number), separated by comma `,`.
 
 ### Port Reuse
 
@@ -499,9 +497,9 @@ We would like to try to allow multiple proxies bind a same remote port with diff
 
 ### TCP Stream Multiplexing
 
-frp support tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing. All user requests to same frpc can use only one tcp connection.
+frp supports tcp stream multiplexing since v0.10.0 like HTTP2 Multiplexing, in which case all logic connections to the same frpc are multiplexed into the same TCP connection.
 
-You can disable this feature by modify frps.ini and frpc.ini:
+You can disable this feature by modify `frps.ini` and `frpc.ini`:
 
 ```ini
 # frps.ini and frpc.ini, must be same
@@ -513,36 +511,38 @@ tcp_mux = false
 
 KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
 
-Using kcp in frp:
+KCP mode uses UDP as the underlying transport. Using KCP in frp:
 
-1. Enable kcp protocol in frps:
+1. Enable KCP in frps:
 
   ```ini
   # frps.ini
   [common]
   bind_port = 7000
-  # kcp needs to bind a udp port, it can be same with 'bind_port'
+  # Specify a UDP port for KCP.
   kcp_bind_port = 7000
   ```
 
-2. Configure the protocol used in frpc to connect to frps:
+  The `kcp_bind_port` number can be the same number as `bind_port`, since `bind_port` field specifies a TCP port.
+
+2. Configure `frpc.ini` to use KCP to connect to frps:
 
   ```ini
   # frpc.ini
   [common]
   server_addr = x.x.x.x
-  # specify the 'kcp_bind_port' in frps
+  # Same as the 'kcp_bind_port' in frps.ini
   server_port = 7000
   protocol = kcp
   ```
 
-### Connection Pool
+### Connection Pooling
 
-By default, frps sends a message to frpc to create a new connection to the backward service when getting a user request. If a proxy's connection pool is enabled, there will be a specified number of connections pre-established.
+By default, frps creates a new frpc connection to the backend service upon a user request. With connection pooling, frps keeps a certain number of pre-established connections, reducing the time needed to establish a connection.
 
-This feature is fit for a large number of short connections.
+This feature is suitable for a large number of short connections.
 
-1. Configure the limit of pool count each proxy can use in frps.ini:
+1. Configure the limit of pool count each proxy can use in `frps.ini`:
 
   ```ini
   # frps.ini
@@ -562,7 +562,7 @@ This feature is fit for a large number of short connections.
 
 Load balancing is supported by `group`.
 
-This feature is available only for type `tcp` and `http` now.
+This feature is only available for types `tcp` and `http` now.
 
 ```ini
 # frpc.ini
@@ -583,23 +583,19 @@ group_key = 123
 
 `group_key` is used for authentication.
 
-Proxies in same group will accept connections from port 80 randomly.
+Connections to port 80 will be dispatched to proxies in the same group randomly.
 
-For `tcp` type, `remote_port` in the same group should be same.
+For type `tcp`, `remote_port` in the same group should be the same.
 
-For `http` type, `custom_domains, subdomain, locations` should be same.
+For type `http`, `custom_domains`, `subdomain`, `locations` should be the same.
 
-### Health Check
+### Service Health Check
 
 Health check feature can help you achieve high availability with load balancing.
 
-Add `health_check_type = {type}` to enable health check.
+Add `health_check_type = tcp` or `health_check_type = http` to enable health check.
 
-**type** can be tcp or http.
-
-Type tcp will dial the service port and type http will send a http request to the service and require a HTTP 200 response.
-
-Type tcp configuration:
+With health check type **tcp**, the service port will be pinged (TCPing):
 
 ```ini
 # frpc.ini
@@ -607,77 +603,81 @@ Type tcp configuration:
 type = tcp
 local_port = 22
 remote_port = 6000
-# enable tcp health check
+# Enable TCP health check
 health_check_type = tcp
-# dial timeout seconds
+# TCPing timeout seconds
 health_check_timeout_s = 3
-# if health check failed 3 times in a row, the proxy will be removed from frps
+# If health check failed 3 times in a row, the proxy will be removed from frps
 health_check_max_failed = 3
-# health check every 10 seconds
+# A health check every 10 seconds
 health_check_interval_s = 10
 ```
 
-Type http configuration:
+With health check type **http**, an HTTP request will be sent to the service and an HTTP 2xx OK response is expected:
+
 ```ini
 # frpc.ini
 [web]
 type = http
 local_ip = 127.0.0.1
 local_port = 80
-custom_domains = test.yourdomain.com
-# enable http health check
+custom_domains = test.example.com
+# Enable HTTP health check
 health_check_type = http
-# frpc will send a GET http request '/status' to local http service
-# http service is alive when it return 2xx http response code
+# frpc will send a GET request to '/status'
+# and expect an HTTP 2xx OK response
 health_check_url = /status
-health_check_interval_s = 10
-health_check_max_failed = 3
 health_check_timeout_s = 3
+health_check_max_failed = 3
+health_check_interval_s = 10
 ```
 
-### Rewriting the Host Header
+### Rewriting the HTTP Host Header
 
-When forwarding to a local port, frp does not modify the tunneled HTTP requests at all, they are copied to your server byte-for-byte as they are received. Some application servers use the Host header for determining which development site to display. For this reason, frp can rewrite your requests with a modified host header. Use the `host_header_rewrite` switch to rewrite incoming HTTP requests.
+By default frp does not modify the tunneled HTTP requests at all as it's a byte-for-byte copy.
+
+However, speaking of web servers and HTTP requests, your web server might rely on the `Host` HTTP header to determine the website to be accessed. frp can rewrite the `Host` header when forwarding the HTTP requests, with the `host_header_rewrite` field:
 
 ```ini
 # frpc.ini
 [web]
 type = http
 local_port = 80
-custom_domains = test.yourdomain.com
-host_header_rewrite = dev.yourdomain.com
+custom_domains = test.example.com
+host_header_rewrite = dev.example.com
 ```
 
-The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
+The HTTP request will have the the `Host` header rewritten to `Host: dev.example.com` when it reaches the actual web server, although the request from the browser probably has `Host: test.example.com`.
 
-### Set Headers In HTTP Request
+### Setting other HTTP Headers
 
-You can set headers for proxy which type is `http`.
+Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
 
 ```ini
 # frpc.ini
 [web]
 type = http
 local_port = 80
-custom_domains = test.yourdomain.com
-host_header_rewrite = dev.yourdomain.com
+custom_domains = test.example.com
+host_header_rewrite = dev.example.com
 header_X-From-Where = frp
 ```
 
-Note that parameters that have `header_` prefix will be added to http request headers.
-In this example, it will set header `X-From-Where: frp` to http request.
+Note that parameter(s) prefixed with `header_` will be added to HTTP request headers.
+
+In this example, it will set header `X-From-Where: frp` in the HTTP request.
 
 ### Get Real IP
 
 #### HTTP X-Forwarded-For
 
-These features are for http proxy only.
+This feature is for http proxy only.
 
-You can get the user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
+You can get user's real IP from HTTP request headers `X-Forwarded-For` and `X-Real-IP`.
 
 #### Proxy Protocol
 
-frp support Proxy Protocol to send user's real IP to local service. It support all types except UDP.
+frp supports Proxy Protocol to send user's real IP to local services. It support all types except UDP.
 
 Here is an example for https service:
 
@@ -686,21 +686,19 @@ Here is an example for https service:
 [web]
 type = https
 local_port = 443
-custom_domains = test.yourdomain.com
+custom_domains = test.example.com
 
-# now v1 and v2 is supported
+# now v1 and v2 are supported
 proxy_protocol_version = v2
 ```
 
-You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
+You can enable Proxy Protocol support in nginx to expose user's real IP in HTTP header `X-Real-IP`, and then read `X-Real-IP` header in your web service for the real IP.
 
-Then you can get it from HTTP request header in your local service.
-
-### Password protecting your web service
+### Require HTTP Basic auth (password) for web services
 
 Anyone who can guess your tunnel URL can access your local web server unless you protect it with a password.
 
-This enforces HTTP Basic Auth on all requests with the username and password you specify in frpc's configure file.
+This enforces HTTP Basic Auth on all requests with the username and password specified in frpc's configure file.
 
 It can only be enabled when proxy type is http.
 
@@ -709,23 +707,23 @@ It can only be enabled when proxy type is http.
 [web]
 type = http
 local_port = 80
-custom_domains = test.yourdomain.com
+custom_domains = test.example.com
 http_user = abc
 http_pwd = abc
 ```
 
-Visit `http://test.yourdomain.com` and now you need to input username and password.
+Visit `http://test.example.com` in the browser and now you are prompted to enter the username and password.
 
 ### Custom subdomain names
 
-It is convenient to use `subdomain` configure for http、https type when many people use one frps server together.
+It is convenient to use `subdomain` configure for http and https types when many people share one frps server.
 
 ```ini
 # frps.ini
 subdomain_host = frps.com
 ```
 
-Resolve `*.frps.com` to the frps server's IP.
+Resolve `*.frps.com` to the frps server's IP. This is usually called a Wildcard DNS record.
 
 ```ini
 # frpc.ini
@@ -735,35 +733,36 @@ local_port = 80
 subdomain = test
 ```
 
-Now you can visit your web service by host `test.frps.com`.
+Now you can visit your web service on `test.frps.com`.
 
 Note that if `subdomain_host` is not empty, `custom_domains` should not be the subdomain of `subdomain_host`.
 
 ### URL routing
 
-frp support forward http requests to different backward web services by url routing.
+frp supports forwarding HTTP requests to different backend web services by url routing.
 
-`locations` specify the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
+`locations` specifies the prefix of URL used for routing. frps first searches for the most specific prefix location given by literal strings regardless of the listed order.
 
 ```ini
 # frpc.ini
 [web01]
 type = http
 local_port = 80
-custom_domains = web.yourdomain.com
+custom_domains = web.example.com
 locations = /
 
 [web02]
 type = http
 local_port = 81
-custom_domains = web.yourdomain.com
+custom_domains = web.example.com
 locations = /news,/about
 ```
-Http requests with url prefix `/news` and `/about` will be forwarded to **web02** and others to **web01**.
 
-### Connect frps by HTTP PROXY
+HTTP requests with URL prefix `/news` or `/about` will be forwarded to **web02** and other requests to **web01**.
 
-frpc can connect frps using HTTP PROXY if you set os environment `HTTP_PROXY` or configure `http_proxy` param in frpc.ini file.
+### Connecting to frps via HTTP PROXY
+
+frpc can connect to frps using HTTP proxy if you set OS environment variable `HTTP_PROXY`, or if `http_proxy` is set in frpc.ini file.
 
 It only works when protocol is tcp.
 
@@ -777,7 +776,7 @@ http_proxy = http://user:pwd@192.168.1.128:8080
 
 ### Range ports mapping
 
-Proxy name that has starts with `range:` will support mapping range ports.
+Proxy with names that start with `range:` will support mapping range ports.
 
 ```ini
 # frpc.ini
@@ -788,15 +787,15 @@ local_port = 6000-6006,6007
 remote_port = 6000-6006,6007
 ```
 
-frpc will generate 8 proxies like `test_tcp_0, test_tcp_1 ... test_tcp_7`.
+frpc will generate 8 proxies like `test_tcp_0`, `test_tcp_1`, ..., `test_tcp_7`.
 
-### Plugin
+### Plugins
 
-frpc only forwards request to local tcp or udp port by default.
+frpc only forwards requests to local TCP or UDP ports by default.
 
 Plugins are used for providing rich features. There are built-in plugins such as `unix_domain_socket`, `http_proxy`, `socks5`, `static_file` and you can see [example usage](#example-usage).
 
-Specify which plugin to use by `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` is useless for plugin.
+Specify which plugin to use with the `plugin` parameter. Configuration parameters of plugin should be started with `plugin_`. `local_ip` and `local_port` are not used for plugin.
 
 Using plugin **http_proxy**:
 
@@ -814,7 +813,7 @@ plugin_http_passwd = abc
 
 ## Development Plan
 
-* Log http request information in frps.
+* Log HTTP request information in frps.
 
 ## Contributing
 

From 8eb945ee9b14c1e4b513d401ddf21a8ce922651e Mon Sep 17 00:00:00 2001
From: lzhfromustc <lzhfromustc@gmail.com>
Date: Mon, 14 Oct 2019 23:35:08 -0400
Subject: [PATCH 6/7] dev:udp: Add an Unlock before a continue, to fix a double
 lock bug

---
 models/proto/udp/udp.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/models/proto/udp/udp.go b/models/proto/udp/udp.go
index 26776341..ed7f95a9 100644
--- a/models/proto/udp/udp.go
+++ b/models/proto/udp/udp.go
@@ -117,6 +117,7 @@ func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<-
 			if !ok {
 				udpConn, err = net.DialUDP("udp", nil, dstAddr)
 				if err != nil {
+					mu.Unlock()
 					continue
 				}
 				udpConnMap[udpMsg.RemoteAddr.String()] = udpConn

From 22a79710d825a0f5db799180d02ecfdfd165974f Mon Sep 17 00:00:00 2001
From: fatedier <fatedier@gmail.com>
Date: Sat, 2 Nov 2019 21:05:37 +0800
Subject: [PATCH 7/7] bump version to v0.29.1

---
 utils/version/version.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/utils/version/version.go b/utils/version/version.go
index 7a73de8f..cc47e55f 100644
--- a/utils/version/version.go
+++ b/utils/version/version.go
@@ -19,7 +19,7 @@ import (
 	"strings"
 )
 
-var version string = "0.29.0"
+var version string = "0.29.1"
 
 func Full() string {
 	return version