add proxy log level
diff --git a/app/dubboctl/cmd/proxy.go b/app/dubboctl/cmd/proxy.go
index 2cb53b2..49dd267 100644
--- a/app/dubboctl/cmd/proxy.go
+++ b/app/dubboctl/cmd/proxy.go
@@ -19,20 +19,17 @@
 
 import (
 	"context"
+	"fmt"
+	"github.com/apache/dubbo-kubernetes/pkg/core"
+	"github.com/apache/dubbo-kubernetes/pkg/util/proto"
+	"github.com/pkg/errors"
 	"io"
 	"os"
 	"path/filepath"
-)
-
-import (
-	"github.com/pkg/errors"
+	"strings"
 
 	"github.com/spf13/cobra"
 
-	"go.uber.org/zap/zapcore"
-)
-
-import (
 	mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1"
 	"github.com/apache/dubbo-kubernetes/app/dubboctl/internal/envoy"
 	"github.com/apache/dubbo-kubernetes/pkg/config/app/dubboctl"
@@ -42,7 +39,9 @@
 	"github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
 	"github.com/apache/dubbo-kubernetes/pkg/core/resources/model/rest"
 	core_xds "github.com/apache/dubbo-kubernetes/pkg/core/xds"
+	dubbo_log "github.com/apache/dubbo-kubernetes/pkg/log"
 	"github.com/apache/dubbo-kubernetes/pkg/util/template"
+	"go.uber.org/zap/zapcore"
 )
 
 var runLog = controlPlaneLog.WithName("proxy")
@@ -92,14 +91,39 @@
 
 func addProxy(opts dubbo_cmd.RunCmdOpts, cmd *cobra.Command) {
 	proxyArgs := DefaultProxyConfig()
+
 	cfg := proxyArgs.Config
 	var proxyResource model.Resource
+	arg := struct {
+		logLevel   string
+		outputPath string
+		maxSize    int
+		maxBackups int
+		maxAge     int
+	}{}
+
 	proxyCmd := &cobra.Command{
 		Use:   "proxy",
 		Short: "Commands related to proxy",
 		Long:  "Commands help user to generate Ingress and Egress",
 		RunE: func(cmd *cobra.Command, args []string) error {
 			logger.InitCmdSugar(zapcore.AddSync(cmd.OutOrStdout()))
+			level, err := dubbo_log.ParseLogLevel(arg.logLevel)
+			if err != nil {
+				return err
+			}
+			proxyArgs.LogLevel = level
+			if arg.outputPath != "" {
+				output, err := filepath.Abs(arg.outputPath)
+				if err != nil {
+					return err
+				}
+
+				fmt.Printf("%s: logs will be stored in %q\n", "kuma-dp", output)
+				core.SetLogger(core.NewLoggerWithRotation(level, output, arg.maxSize, arg.maxBackups, arg.maxAge))
+			} else {
+				core.SetLogger(core.NewLogger(level))
+			}
 			return nil
 		},
 		PreRunE: func(cmd *cobra.Command, args []string) error {
@@ -110,6 +134,10 @@
 			if _, ok := proxyTypeMap[cfg.Dataplane.ProxyType]; !ok {
 				return errors.Errorf("invalid proxy type %q", cfg.Dataplane.ProxyType)
 			}
+			if cfg.DataplaneRuntime.EnvoyLogLevel == "" {
+				cfg.DataplaneRuntime.EnvoyLogLevel = proxyArgs.LogLevel.String()
+			}
+
 			proxyResource, err := readResource(cmd, &cfg.DataplaneRuntime)
 			if err != nil {
 				runLog.Error(err, "failed to read policy", "proxyType", cfg.Dataplane.ProxyType)
@@ -162,13 +190,12 @@
 				return errors.Errorf("Failed to generate Envoy bootstrap config. %v", err)
 			}
 			runLog.Info("received bootstrap configuration", "adminPort", bootstrap.GetAdmin().GetAddress().GetSocketAddress().GetPortValue())
+			opts.BootstrapConfig, err = proto.ToYAML(bootstrap)
+			if err != nil {
+				return errors.Errorf("could not convert to yaml. %v", err)
+			}
+			opts.AdminPort = bootstrap.GetAdmin().GetAddress().GetSocketAddress().GetPortValue()
 
-			//runLog.Info("received bootstrap configuration", "adminPort", bootstrap.GetAdmin().GetAddress().GetSocketAddress().GetPortValue())
-			//
-			//opts.BootstrapConfig, err = proto.ToYAML(&bootstrap)
-			//if err != nil {
-			//	return errors.Errorf("could not convert to yaml. %v", err)
-			//}
 			stopComponents := make(chan struct{})
 			envoyComponent, err := envoy.New(opts)
 			err = envoyComponent.Start(stopComponents)
@@ -180,9 +207,24 @@
 			return nil
 		},
 	}
+
+	// root flags
+	cmd.PersistentFlags().StringVar(&arg.logLevel, "log-level", dubbo_log.InfoLevel.String(), UsageOptions("log level", dubbo_log.OffLevel, dubbo_log.InfoLevel, dubbo_log.DebugLevel))
+	cmd.PersistentFlags().StringVar(&arg.outputPath, "log-output-path", arg.outputPath, "path to the file that will be filled with logs. Example: if we set it to /tmp/kuma.log then after the file is rotated we will have /tmp/kuma-2021-06-07T09-15-18.265.log")
+	cmd.PersistentFlags().IntVar(&arg.maxBackups, "log-max-retained-files", 1000, "maximum number of the old log files to retain")
+	cmd.PersistentFlags().IntVar(&arg.maxSize, "log-max-size", 100, "maximum size in megabytes of a log file before it gets rotated")
+	cmd.PersistentFlags().IntVar(&arg.maxAge, "log-max-age", 30, "maximum number of days to retain old log files based on the timestamp encoded in their filename")
+
 	proxyCmd.PersistentFlags().StringVar(&cfg.Dataplane.Name, "name", cfg.Dataplane.Name, "Name of the Dataplane")
 	proxyCmd.PersistentFlags().StringVar(&cfg.Dataplane.Mesh, "mesh", cfg.Dataplane.Mesh, "Mesh that Dataplane belongs to")
 	proxyCmd.PersistentFlags().StringVar(&cfg.Dataplane.ProxyType, "proxy-type", "dataplane", `type of the Dataplane ("dataplane", "ingress")`)
 	proxyCmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.ResourcePath, "dataplane-file", "Path to Ingress and Egress template to apply (YAML or JSON)", "data-plane-file")
 	cmd.AddCommand(proxyCmd)
 }
+func UsageOptions(desc string, options ...interface{}) string {
+	values := make([]string, 0, len(options))
+	for _, option := range options {
+		values = append(values, fmt.Sprintf("%v", option))
+	}
+	return fmt.Sprintf("%s: one of %s", desc, strings.Join(values, "|"))
+}
diff --git a/app/dubboctl/cmd/proxy_context.go b/app/dubboctl/cmd/proxy_context.go
index 8b6427d..33cbaff 100644
--- a/app/dubboctl/cmd/proxy_context.go
+++ b/app/dubboctl/cmd/proxy_context.go
@@ -18,6 +18,7 @@
 package cmd
 
 import (
+	"github.com/apache/dubbo-kubernetes/pkg/log"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -47,6 +48,7 @@
 	Config                   *dubboctl.Config
 	BootstrapGenerator       envoy.BootstrapConfigFactoryFunc
 	DataplaneTokenGenerator  func(cfg *dubboctl.Config) (component.Component, error)
+	LogLevel                 log.LogLevel
 }
 
 var features = []string{core_xds.FeatureTCPAccessLogViaNamedPipe}
diff --git a/app/dubboctl/cmd/proxy_test.go b/app/dubboctl/cmd/proxy_test.go
new file mode 100644
index 0000000..ecb6552
--- /dev/null
+++ b/app/dubboctl/cmd/proxy_test.go
@@ -0,0 +1,266 @@
+//go:build !windows
+// +build !windows
+
+package cmd
+
+import (
+	"context"
+	"fmt"
+	dubbo_cmd "github.com/apache/dubbo-kubernetes/pkg/core/cmd"
+	"github.com/apache/dubbo-kubernetes/pkg/test"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"io"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"syscall"
+	"testing"
+)
+
+func TestCmd(t *testing.T) {
+	test.RunSpecs(t, "cmd Suite")
+}
+
+var _ = Describe("proxy", func() {
+	var cancel func()
+	var ctx context.Context
+	_ = dubbo_cmd.RunCmdOpts{
+		SetupSignalHandler: func() (context.Context, context.Context) {
+			return ctx, ctx
+		},
+	}
+	var tmpDir string
+	BeforeEach(func() {
+		ctx, cancel = context.WithCancel(context.Background())
+		var err error
+		tmpDir, err = os.MkdirTemp("", "")
+		Expect(err).ToNot(HaveOccurred())
+	})
+	AfterEach(func() {
+		if tmpDir != "" {
+			if tmpDir != "" {
+				// when
+				err := os.RemoveAll(tmpDir)
+				// then
+				Expect(err).ToNot(HaveOccurred())
+			}
+		}
+	})
+	type testCase struct {
+		envVars      map[string]string
+		args         []string
+		expectedFile string
+	}
+	DescribeTable("should be possible to start dataplane (Envoy) using `dubbo-proxy run`",
+		func(giveFunc func() testCase) {
+			given := giveFunc()
+
+			// setup
+			envoyPidFile := filepath.Join(tmpDir, "envoy-mock.pid")
+			envoyCmdlineFile := filepath.Join(tmpDir, "envoy-mock.cmdline")
+			corednsPidFile := filepath.Join(tmpDir, "coredns-mock.pid")
+			corednsCmdlineFile := filepath.Join(tmpDir, "coredns-mock.cmdline")
+
+			// and
+			env := given.envVars
+			env["ENVOY_MOCK_PID_FILE"] = envoyPidFile
+			env["ENVOY_MOCK_CMDLINE_FILE"] = envoyCmdlineFile
+			env["COREDNS_MOCK_PID_FILE"] = corednsPidFile
+			env["COREDNS_MOCK_CMDLINE_FILE"] = corednsCmdlineFile
+			for key, value := range env {
+				Expect(os.Setenv(key, value)).To(Succeed())
+			}
+
+			// given
+
+			reader, writer := io.Pipe()
+			go func() {
+				defer GinkgoRecover()
+				io.ReadAll(reader)
+			}()
+
+			cmd := getRootCmd([]string{"proxy", "--proxy-type=ingress", "--dataplane-file=/mnt/d/code/go/test/1.yaml"})
+			cmd.SetOut(writer)
+			cmd.SetErr(writer)
+			cancel()
+
+			// when
+			By("starting the Dubbo proxy")
+			errCh := make(chan error)
+			go func() {
+				defer close(errCh)
+				errCh <- cmd.Execute()
+			}()
+
+			// then
+			var actualConfigFile string
+			envoyPid := verifyComponentProcess("Envoy", envoyPidFile, envoyCmdlineFile, func(actualArgs []string) {
+				Expect(actualArgs[0]).To(Equal("--version"))
+				Expect(actualArgs[1]).To(Equal("--config-path"))
+				actualConfigFile = actualArgs[2]
+				Expect(actualConfigFile).To(BeARegularFile())
+				if given.expectedFile != "" {
+					Expect(actualArgs[2]).To(Equal(given.expectedFile))
+				}
+			})
+
+			err := <-errCh
+			Expect(err).ToNot(HaveOccurred())
+
+			By("waiting for dataplane (Envoy) to get stopped")
+			Eventually(func() bool {
+				//send sig 0 to check whether Envoy process still exists
+				err := syscall.Kill(int(envoyPid), syscall.Signal(0))
+				// we expect Envoy process to get killed by now
+				return err != nil
+			}, "5s", "100ms").Should(BeTrue())
+
+		},
+		Entry("can be launched with env vars", func() testCase {
+			return testCase{
+				envVars: map[string]string{
+					"DUBBO_CONTROL_PLANE_API_SERVER_URL":  "http://localhost:1234",
+					"DUBBO_DATAPLANE_NAME":                "example",
+					"DUBBO_DATAPLANE_MESH":                "default",
+					"DUBBO_DATAPLANE_RUNTIME_BINARY_PATH": filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					// Notice: DUBBO_DATAPLANE_RUNTIME_CONFIG_DIR is not set in order to let `dubbo-dp` to create a temporary directory
+					"DUBBO_DNS_CORE_DNS_BINARY_PATH": filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				args:         []string{},
+				expectedFile: "",
+			}
+		}),
+		Entry("can be launched with env vars and given config dir", func() testCase {
+			return testCase{
+				envVars: map[string]string{
+					"DUBBO_CONTROL_PLANE_API_SERVER_URL":  "http://localhost:1234",
+					"DUBBO_DATAPLANE_NAME":                "example",
+					"DUBBO_DATAPLANE_MESH":                "default",
+					"DUBBO_DATAPLANE_RUNTIME_BINARY_PATH": filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					"DUBBO_DATAPLANE_RUNTIME_CONFIG_DIR":  tmpDir,
+					"DUBBO_DNS_CORE_DNS_BINARY_PATH":      filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				args:         []string{},
+				expectedFile: filepath.Join(tmpDir, "bootstrap.yaml"),
+			}
+		}),
+		Entry("can be launched with args", func() testCase {
+			return testCase{
+				envVars: map[string]string{},
+				args: []string{
+					"--cp-address", "http://localhost:1234",
+					"--name", "example",
+					"--mesh", "default",
+					"--binary-path", filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					// Notice: --config-dir is not set in order to let `dubbo-dp` to create a temporary directory
+					"--dns-coredns-path", filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				expectedFile: "",
+			}
+		}),
+		Entry("can be launched with args and given config dir", func() testCase {
+			return testCase{
+				envVars: map[string]string{},
+				args: []string{
+					"--cp-address", "http://localhost:1234",
+					"--name", "example",
+					"--mesh", "default",
+					"--binary-path", filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					"--config-dir", tmpDir,
+					"--dns-coredns-path", filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				expectedFile: filepath.Join(tmpDir, "bootstrap.yaml"),
+			}
+		}),
+		Entry("can be launched with args and dataplane token", func() testCase {
+			return testCase{
+				envVars: map[string]string{},
+				args: []string{
+					"--cp-address", "http://localhost:1234",
+					"--name", "example",
+					"--mesh", "default",
+					"--binary-path", filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					"--dataplane-token-file", filepath.Join("testdata", "token"),
+					// Notice: --config-dir is not set in order to let `dubbo-dp` to create a temporary directory
+					"--dns-coredns-path", filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				expectedFile: "",
+			}
+		}),
+		Entry("can be launched without Envoy Admin API (env vars)", func() testCase {
+			return testCase{
+				envVars: map[string]string{
+					"DUBBO_CONTROL_PLANE_API_SERVER_URL":  "http://localhost:1234",
+					"DUBBO_DATAPLANE_NAME":                "example",
+					"DUBBO_DATAPLANE_MESH":                "default",
+					"DUBBO_DATAPLANE_RUNTIME_BINARY_PATH": filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					// Notice: DUBBO_DATAPLANE_RUNTIME_CONFIG_DIR is not set in order to let `dubbo-dp` to create a temporary directory
+					"DUBBO_DNS_CORE_DNS_BINARY_PATH": filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				args:         []string{},
+				expectedFile: "",
+			}
+		}),
+		Entry("can be launched without Envoy Admin API (command-line args)", func() testCase {
+			return testCase{
+				envVars: map[string]string{},
+				args: []string{
+					"--cp-address", "http://localhost:1234",
+					"--name", "example",
+					"--mesh", "default",
+					"--binary-path", filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					// Notice: --config-dir is not set in order to let `dubbo-dp` to create a temporary directory
+					"--dns-coredns-path", filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				expectedFile: "",
+			}
+		}),
+		Entry("can be launched with dataplane template", func() testCase {
+			return testCase{
+				envVars: map[string]string{},
+				args: []string{
+					"--cp-address", "http://localhost:1234",
+					"--binary-path", filepath.Join("testdata", "envoy-mock.sleep.sh"),
+					"--dataplane-token-file", filepath.Join("testdata", "token"),
+					"--dataplane-file", filepath.Join("testdata", "dataplane_template.yaml"),
+					"--dataplane-var", "name=example",
+					"--dataplane-var", "address=127.0.0.1",
+					"--dns-coredns-path", filepath.Join("testdata", "coredns-mock.sleep.sh"),
+				},
+				expectedFile: "",
+			}
+		}),
+	)
+
+})
+
+func verifyComponentProcess(processDescription, pidfile string, cmdlinefile string, argsVerifier func(expectedArgs []string)) int64 {
+	var pid int64
+	By(fmt.Sprintf("waiting for dataplane (%s) to get started", processDescription))
+	Eventually(func() bool {
+		data, err := os.ReadFile(pidfile)
+		if err != nil {
+			return false
+		}
+		pid, err = strconv.ParseInt(strings.TrimSpace(string(data)), 10, 32)
+		return err == nil
+	}, "5s", "100ms").Should(BeTrue())
+	Expect(pid).ToNot(BeZero())
+
+	By(fmt.Sprintf("verifying the arguments %s was launched with", processDescription))
+	// when
+	cmdline, err := os.ReadFile(cmdlinefile)
+
+	// then
+	Expect(err).ToNot(HaveOccurred())
+	// and
+	if argsVerifier != nil {
+		actualArgs := strings.FieldsFunc(string(cmdline), func(c rune) bool {
+			return c == '\n'
+		})
+		argsVerifier(actualArgs)
+	}
+	return pid
+}
diff --git a/app/dubboctl/internal/envoy/envoy.go b/app/dubboctl/internal/envoy/envoy.go
index fea3d39..fe4ef7d 100644
--- a/app/dubboctl/internal/envoy/envoy.go
+++ b/app/dubboctl/internal/envoy/envoy.go
@@ -183,6 +183,7 @@
 	err = command.Wait()
 	if err != nil && !errors.Is(err, context.Canceled) {
 		runLog.Error(err, "Envoy terminated with an error")
+
 		return err
 	}
 	runLog.Info("Envoy terminated successfully")
diff --git a/app/dubboctl/internal/envoy/remote_bootstrap.go b/app/dubboctl/internal/envoy/remote_bootstrap.go
index 63a2869..a963f12 100644
--- a/app/dubboctl/internal/envoy/remote_bootstrap.go
+++ b/app/dubboctl/internal/envoy/remote_bootstrap.go
@@ -36,6 +36,7 @@
 
 	"github.com/pkg/errors"
 
+	_ "github.com/apache/dubbo-kubernetes/pkg/xds/envoy"
 	"github.com/sethvargo/go-retry"
 )
 
diff --git a/pkg/config/app/dubboctl/proxy_config.go b/pkg/config/app/dubboctl/proxy_config.go
index 2e37844..2d9d219 100644
--- a/pkg/config/app/dubboctl/proxy_config.go
+++ b/pkg/config/app/dubboctl/proxy_config.go
@@ -18,6 +18,7 @@
 package dubboctl
 
 import (
+	mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1"
 	"time"
 )
 
@@ -177,3 +178,8 @@
 	// Drain time for listeners.
 	DrainTime config_types.Duration `json:"drainTime,omitempty" envconfig:"dubbo_dataplane_drain_time"`
 }
+
+func (d *Dataplane) IsZoneProxy() bool {
+	return d.ProxyType == string(mesh_proto.IngressProxyType) ||
+		d.ProxyType == string(mesh_proto.EgressProxyType)
+}
diff --git a/pkg/xds/bootstrap/templatae_v3.go b/pkg/xds/bootstrap/template_v3.go
similarity index 100%
rename from pkg/xds/bootstrap/templatae_v3.go
rename to pkg/xds/bootstrap/template_v3.go