diff --git a/agent/checks/grpc.go b/agent/checks/grpc.go index 974ba9aa36..8577ae6e7c 100644 --- a/agent/checks/grpc.go +++ b/agent/checks/grpc.go @@ -67,7 +67,7 @@ func (probe *GrpcHealthProbe) Check() error { if err != nil { return err } - if response == nil || (response != nil && response.Status != hv1.HealthCheckResponse_SERVING) { + if response == nil || response.Status != hv1.HealthCheckResponse_SERVING { return ErrGRPCUnhealthy } diff --git a/agent/config/builder.go b/agent/config/builder.go index 2c499688ea..4ff46d1290 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -846,6 +846,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) { LogFile: b.stringVal(c.LogFile), LogRotateBytes: b.intVal(c.LogRotateBytes), LogRotateDuration: b.durationVal("log_rotate_duration", c.LogRotateDuration), + LogRotateMaxFiles: b.intVal(c.LogRotateMaxFiles), NodeID: types.NodeID(b.stringVal(c.NodeID)), NodeMeta: c.NodeMeta, NodeName: b.nodeName(c.NodeName), diff --git a/agent/config/config.go b/agent/config/config.go index 68a6fc74ab..91470370de 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -228,6 +228,7 @@ type Config struct { LogFile *string `json:"log_file,omitempty" hcl:"log_file" mapstructure:"log_file"` LogRotateDuration *string `json:"log_rotate_duration,omitempty" hcl:"log_rotate_duration" mapstructure:"log_rotate_duration"` LogRotateBytes *int `json:"log_rotate_bytes,omitempty" hcl:"log_rotate_bytes" mapstructure:"log_rotate_bytes"` + LogRotateMaxFiles *int `json:"log_rotate_max_files,omitempty" hcl:"log_rotate_max_files" mapstructure:"log_rotate_max_files"` NodeID *string `json:"node_id,omitempty" hcl:"node_id" mapstructure:"node_id"` NodeMeta map[string]string `json:"node_meta,omitempty" hcl:"node_meta" mapstructure:"node_meta"` NodeName *string `json:"node_name,omitempty" hcl:"node_name" mapstructure:"node_name"` diff --git a/agent/config/flags.go b/agent/config/flags.go index b6cc5966b8..ef39ddc576 100644 --- a/agent/config/flags.go +++ b/agent/config/flags.go @@ -84,6 +84,7 @@ func AddFlags(fs *flag.FlagSet, f *Flags) { add(&f.Config.LogFile, "log-file", "Path to the file the logs get written to") add(&f.Config.LogRotateBytes, "log-rotate-bytes", "Maximum number of bytes that should be written to a log file") add(&f.Config.LogRotateDuration, "log-rotate-duration", "Time after which log rotation needs to be performed") + add(&f.Config.LogRotateMaxFiles, "log-rotate-max-files", "Maximum number of log file archives to keep") add(&f.Config.NodeName, "node", "Name of this node. Must be unique in the cluster.") add(&f.Config.NodeID, "node-id", "A unique ID for this node across space and time. Defaults to a randomly-generated ID that persists in the data-dir.") add(&f.Config.NodeMeta, "node-meta", "An arbitrary metadata key/value pair for this node, of the format `key:value`. Can be specified multiple times.") diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 90504845e8..a6021b4a1a 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -865,6 +865,12 @@ type RuntimeConfig struct { // flags: -log-rotate-bytes int LogRotateBytes int + // LogRotateMaxFiles is the maximum number of log file archives to keep + // + // hcl: log_rotate_max_files = int + // flags: -log-rotate-max-files int + LogRotateMaxFiles int + // Node ID is a unique ID for this node across space and time. Defaults // to a randomly-generated ID that persists in the data-dir. // diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 4620178653..ad18b199e4 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -514,6 +514,19 @@ func TestConfigFlagsAndEdgecases(t *testing.T) { rt.DataDir = dataDir }, }, + { + desc: "-log-rotate-max-files", + args: []string{ + `-log-rotate-max-files=2`, + `-data-dir=` + dataDir, + }, + json: []string{`{ "log_rotate_max_files": 2 }`}, + hcl: []string{`log_rotate_max_files = 2`}, + patch: func(rt *RuntimeConfig) { + rt.LogRotateMaxFiles = 2 + rt.DataDir = dataDir + }, + }, { desc: "-node", args: []string{ @@ -5860,6 +5873,7 @@ func TestSanitize(t *testing.T) { "LogFile": "", "LogRotateBytes": 0, "LogRotateDuration": "0s", + "LogRotateMaxFiles": 0, "NodeID": "", "NodeMeta": {}, "NodeName": "", @@ -6239,92 +6253,6 @@ func TestRuntime_ToTLSUtilConfig(t *testing.T) { require.Equal(t, []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}, r.CipherSuites) } -func TestReadPath(t *testing.T) { - dataDir := testutil.TempDir(t, "consul") - defer os.RemoveAll(dataDir) - - tt := []struct { - name string - pre func() - args []string - expect int - }{ - { - name: "dir skip non json or hcl if config-format not set", - pre: func() { - writeFile(filepath.Join(dataDir, "conf.d/conf.json"), []byte(`{}`)) - writeFile(filepath.Join(dataDir, "conf.d/conf.foobar"), []byte(`{}`)) - }, - args: []string{ - `-config-dir`, filepath.Join(dataDir, "conf.d"), - }, - expect: 1, - }, - { - name: "dir read non json or hcl if config-format set", - pre: func() { - writeFile(filepath.Join(dataDir, "conf.d/conf.json"), []byte(`{}`)) - writeFile(filepath.Join(dataDir, "conf.d/conf.foobar"), []byte(`{}`)) - }, - args: []string{ - `-config-dir`, filepath.Join(dataDir, "conf.d"), - `-config-format`, "json", - }, - expect: 2, - }, - { - name: "file skip non json or hcl if config-format not set", - pre: func() { - writeFile(filepath.Join(dataDir, "conf.d/conf.foobar"), []byte(`{}`)) - }, - args: []string{ - `-config-file`, filepath.Join(dataDir, "conf.d"), - }, - expect: 0, - }, - { - name: "file read non json or hcl if config-format set", - pre: func() { - writeFile(filepath.Join(dataDir, "conf.d/conf.foobar"), []byte(`{}`)) - }, - args: []string{ - `-config-file`, filepath.Join(dataDir, "conf.d"), - `-config-format`, "json", - }, - expect: 1, - }, - } - - for _, tc := range tt { - cleanDir(dataDir) - - t.Run(tc.name, func(t *testing.T) { - flags := Flags{} - fs := flag.NewFlagSet("", flag.ContinueOnError) - AddFlags(fs, &flags) - err := fs.Parse(tc.args) - if err != nil { - t.Fatalf("ParseFlags failed: %s", err) - } - flags.Args = fs.Args() - - // write cfg files - tc.pre() - - // Then create a builder with the flags. - b, err := NewBuilder(flags) - if err != nil { - t.Fatal("NewBuilder", err) - } - - got := len(b.Sources) - if tc.expect != got { - t.Fatalf("expected %d sources, got %d", tc.expect, got) - } - }) - } -} - func Test_UIPathBuilder(t *testing.T) { cases := []struct { name string diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index e7def76fb0..fda4b302a9 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -602,7 +602,7 @@ func (a *ACL) tokenSetInternal(args *structs.ACLTokenSetRequest, reply *structs. } // Don't check expiration times here as it doesn't really matter. - if _, updatedToken, err := a.srv.fsm.State().ACLTokenGetByAccessor(nil, token.AccessorID); err == nil && token != nil { + if _, updatedToken, err := a.srv.fsm.State().ACLTokenGetByAccessor(nil, token.AccessorID); err == nil && updatedToken != nil { *reply = *updatedToken } else { return fmt.Errorf("Failed to retrieve the token after insertion") diff --git a/agent/consul/acl_replication_types.go b/agent/consul/acl_replication_types.go index 26ac8dbc43..3009bec983 100644 --- a/agent/consul/acl_replication_types.go +++ b/agent/consul/acl_replication_types.go @@ -90,7 +90,7 @@ func (r *aclTokenReplicator) DeleteLocalBatch(srv *Server, batch []string) error if err != nil { return err } - if respErr, ok := resp.(error); ok && err != nil { + if respErr, ok := resp.(error); ok { return respErr } return nil @@ -119,7 +119,7 @@ func (r *aclTokenReplicator) UpdateLocalBatch(ctx context.Context, srv *Server, if err != nil { return err } - if respErr, ok := resp.(error); ok && err != nil { + if respErr, ok := resp.(error); ok { return respErr } @@ -202,7 +202,7 @@ func (r *aclPolicyReplicator) DeleteLocalBatch(srv *Server, batch []string) erro if err != nil { return err } - if respErr, ok := resp.(error); ok && err != nil { + if respErr, ok := resp.(error); ok { return respErr } return nil diff --git a/agent/consul/state/acl_test.go b/agent/consul/state/acl_test.go index c066e21a21..58c69f7433 100644 --- a/agent/consul/state/acl_test.go +++ b/agent/consul/state/acl_test.go @@ -2728,7 +2728,7 @@ func TestStateStore_ACLRole_Delete(t *testing.T) { t.Parallel() s := testACLStateStore(t) - // deletion of non-existant roles is not an error + // deletion of non-existent roles is not an error require.NoError(t, s.ACLRoleDeleteByName(3, "not-found")) require.NoError(t, s.ACLRoleDeleteByID(3, testRoleID_A)) }) @@ -3021,7 +3021,7 @@ func TestStateStore_ACLAuthMethod_Delete(t *testing.T) { t.Parallel() s := testACLStateStore(t) - // deletion of non-existant methods is not an error + // deletion of non-existent methods is not an error require.NoError(t, s.ACLAuthMethodDeleteByName(3, "not-found")) }) } @@ -3453,7 +3453,7 @@ func TestStateStore_ACLBindingRule_Delete(t *testing.T) { t.Parallel() s := testACLStateStore(t) - // deletion of non-existant rules is not an error + // deletion of non-existent rules is not an error require.NoError(t, s.ACLBindingRuleDeleteByID(3, "ed3ce1b8-3a16-4e2f-b82e-f92e3b92410d")) }) } diff --git a/agent/consul/state/catalog_test.go b/agent/consul/state/catalog_test.go index 8180ac60fd..9e5a15757f 100644 --- a/agent/consul/state/catalog_test.go +++ b/agent/consul/state/catalog_test.go @@ -3884,7 +3884,7 @@ func TestStateStore_ensureServiceCASTxn(t *testing.T) { require.NoError(t, err) tx.Commit() - // ensure no update happend + // ensure no update happened tx = s.db.Txn(false) _, nsRead, err = s.NodeService("node1", "foo") require.NoError(t, err) diff --git a/agent/txn_endpoint.go b/agent/txn_endpoint.go index d02baf775a..f47ff3c294 100644 --- a/agent/txn_endpoint.go +++ b/agent/txn_endpoint.go @@ -135,7 +135,7 @@ func (s *HTTPServer) convertOps(resp http.ResponseWriter, req *http.Request) (st } netKVSize += size - verb := api.KVOp(in.KV.Verb) + verb := in.KV.Verb if isWrite(verb) { writes++ } diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index a68165f73b..0a128c2348 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -141,31 +141,29 @@ func (s *Server) makeAppCluster(cfgSnap *proxycfg.ConfigSnapshot) (*envoy.Cluste return makeClusterFromUserConfig(cfg.LocalClusterJSON) } - if c == nil { - addr := cfgSnap.Proxy.LocalServiceAddress - if addr == "" { - addr = "127.0.0.1" - } - c = &envoy.Cluster{ - Name: LocalAppClusterName, - ConnectTimeout: time.Duration(cfg.LocalConnectTimeoutMs) * time.Millisecond, - ClusterDiscoveryType: &envoy.Cluster_Type{Type: envoy.Cluster_STATIC}, - LoadAssignment: &envoy.ClusterLoadAssignment{ - ClusterName: LocalAppClusterName, - Endpoints: []envoyendpoint.LocalityLbEndpoints{ - { - LbEndpoints: []envoyendpoint.LbEndpoint{ - makeEndpoint(LocalAppClusterName, - addr, - cfgSnap.Proxy.LocalServicePort), - }, + addr := cfgSnap.Proxy.LocalServiceAddress + if addr == "" { + addr = "127.0.0.1" + } + c = &envoy.Cluster{ + Name: LocalAppClusterName, + ConnectTimeout: time.Duration(cfg.LocalConnectTimeoutMs) * time.Millisecond, + ClusterDiscoveryType: &envoy.Cluster_Type{Type: envoy.Cluster_STATIC}, + LoadAssignment: &envoy.ClusterLoadAssignment{ + ClusterName: LocalAppClusterName, + Endpoints: []envoyendpoint.LocalityLbEndpoints{ + { + LbEndpoints: []envoyendpoint.LbEndpoint{ + makeEndpoint(LocalAppClusterName, + addr, + cfgSnap.Proxy.LocalServicePort), }, }, }, - } - if cfg.Protocol == "http2" || cfg.Protocol == "grpc" { - c.Http2ProtocolOptions = &envoycore.Http2ProtocolOptions{} - } + }, + } + if cfg.Protocol == "http2" || cfg.Protocol == "grpc" { + c.Http2ProtocolOptions = &envoycore.Http2ProtocolOptions{} } return c, err diff --git a/command/agent/agent.go b/command/agent/agent.go index 1ca91531a2..a39c6ae4ab 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -194,6 +194,7 @@ func (c *cmd) run(args []string) int { LogFilePath: config.LogFile, LogRotateDuration: config.LogRotateDuration, LogRotateBytes: config.LogRotateBytes, + LogRotateMaxFiles: config.LogRotateMaxFiles, } logFilter, logGate, logWriter, logOutput, ok := logger.Setup(logConfig, c.UI) if !ok { diff --git a/command/kv/put/kv_put_test.go b/command/kv/put/kv_put_test.go index 86d4cb2257..0a3058ba5f 100644 --- a/command/kv/put/kv_put_test.go +++ b/command/kv/put/kv_put_test.go @@ -165,7 +165,7 @@ func TestKVPutCommand_Base64(t *testing.T) { t.Fatal(err) } - if !bytes.Equal(data.Value, []byte(expected)) { + if !bytes.Equal(data.Value, expected) { t.Errorf("bad: %#v, %s", data.Value, data.Value) } } diff --git a/logger/logfile.go b/logger/logfile.go index bf13c0955e..8b6ad4aa2a 100644 --- a/logger/logfile.go +++ b/logger/logfile.go @@ -1,6 +1,7 @@ package logger import ( + "fmt" "os" "path/filepath" "strconv" @@ -41,11 +42,14 @@ type LogFile struct { //BytesWritten is the number of bytes written in the current log file BytesWritten int64 + // Max rotated files to keep before removing them. + MaxFiles int + //acquire is the mutex utilized to ensure we have no concurrency issues acquire sync.Mutex } -func (l *LogFile) openNew() error { +func (l *LogFile) fileNamePattern() string { // Extract the file extension fileExt := filepath.Ext(l.fileName) // If we have no file extension we append .log @@ -53,16 +57,21 @@ func (l *LogFile) openNew() error { fileExt = ".log" } // Remove the file extension from the filename - fileName := strings.TrimSuffix(l.fileName, fileExt) + return strings.TrimSuffix(l.fileName, fileExt) + "-%s" + fileExt +} + +func (l *LogFile) openNew() error { + fileNamePattern := l.fileNamePattern() // New file name has the format : filename-timestamp.extension createTime := now() - newfileName := fileName + "-" + strconv.FormatInt(createTime.UnixNano(), 10) + fileExt + newfileName := fmt.Sprintf(fileNamePattern, strconv.FormatInt(createTime.UnixNano(), 10)) newfilePath := filepath.Join(l.logPath, newfileName) // Try creating a file. We truncate the file because we are the only authority to write the logs filePointer, err := os.OpenFile(newfilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0640) if err != nil { return err } + l.FileInfo = filePointer // New file, new bytes tracker, new creation time :) l.LastCreated = createTime @@ -76,11 +85,35 @@ func (l *LogFile) rotate() error { // Rotate if we hit the byte file limit or the time limit if (l.BytesWritten >= int64(l.MaxBytes) && (l.MaxBytes > 0)) || timeElapsed >= l.duration { l.FileInfo.Close() + if err := l.pruneFiles(); err != nil { + return err + } return l.openNew() } return nil } +func (l *LogFile) pruneFiles() error { + if l.MaxFiles == 0 { + return nil + } + pattern := l.fileNamePattern() + //get all the files that match the log file pattern + globExpression := filepath.Join(l.logPath, fmt.Sprintf(pattern, "*")) + matches, err := filepath.Glob(globExpression) + if err != nil { + return err + } + // Prune if there are more files stored than the configured max + stale := len(matches) - l.MaxFiles + for i := 0; i < stale; i++ { + if err := os.Remove(matches[i]); err != nil { + return err + } + } + return nil +} + // Write is used to implement io.Writer func (l *LogFile) Write(b []byte) (n int, err error) { // Filter out log entries that do not match log level criteria diff --git a/logger/logfile_test.go b/logger/logfile_test.go index 3f13ac0488..44d55a245f 100644 --- a/logger/logfile_test.go +++ b/logger/logfile_test.go @@ -3,6 +3,7 @@ package logger import ( "io/ioutil" "os" + "path/filepath" "testing" "time" @@ -21,7 +22,12 @@ func TestLogFile_timeRotation(t *testing.T) { tempDir := testutil.TempDir(t, "LogWriterTime") defer os.Remove(tempDir) filt := LevelFilter() - logFile := LogFile{logFilter: filt, fileName: testFileName, logPath: tempDir, duration: testDuration} + logFile := LogFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + duration: testDuration, + } logFile.Write([]byte("Hello World")) time.Sleep(2 * time.Second) logFile.Write([]byte("Second File")) @@ -51,7 +57,13 @@ func TestLogFile_byteRotation(t *testing.T) { defer os.Remove(tempDir) filt := LevelFilter() filt.MinLevel = logutils.LogLevel("INFO") - logFile := LogFile{logFilter: filt, fileName: testFileName, logPath: tempDir, MaxBytes: testBytes, duration: 24 * time.Hour} + logFile := LogFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + MaxBytes: testBytes, + duration: 24 * time.Hour, + } logFile.Write([]byte("Hello World")) logFile.Write([]byte("Second File")) want := 2 @@ -66,7 +78,13 @@ func TestLogFile_logLevelFiltering(t *testing.T) { tempDir := testutil.TempDir(t, "LogWriterTime") defer os.Remove(tempDir) filt := LevelFilter() - logFile := LogFile{logFilter: filt, fileName: testFileName, logPath: tempDir, MaxBytes: testBytes, duration: testDuration} + logFile := LogFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + MaxBytes: testBytes, + duration: testDuration, + } logFile.Write([]byte("[INFO] This is an info message")) logFile.Write([]byte("[DEBUG] This is a debug message")) logFile.Write([]byte("[ERR] This is an error message")) @@ -75,3 +93,68 @@ func TestLogFile_logLevelFiltering(t *testing.T) { t.Errorf("Expected %d files, got %v file(s)", want, len(got)) } } + +func TestLogFile_deleteArchives(t *testing.T) { + t.Parallel() + tempDir := testutil.TempDir(t, "LogWriteDeleteArchives") + defer os.Remove(tempDir) + filt := LevelFilter() + filt.MinLevel = logutils.LogLevel("INFO") + logFile := LogFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + MaxBytes: testBytes, + duration: 24 * time.Hour, + MaxFiles: 1, + } + logFile.Write([]byte("[INFO] Hello World")) + logFile.Write([]byte("[INFO] Second File")) + logFile.Write([]byte("[INFO] Third File")) + want := 2 + tempFiles, _ := ioutil.ReadDir(tempDir) + if got := tempFiles; len(got) != want { + t.Errorf("Expected %d files, got %v file(s)", want, len(got)) + return + } + for _, tempFile := range tempFiles { + var bytes []byte + var err error + path := filepath.Join(tempDir, tempFile.Name()) + if bytes, err = ioutil.ReadFile(path); err != nil { + t.Errorf(err.Error()) + return + } + contents := string(bytes) + + if contents == "[INFO] Hello World" { + t.Errorf("Should have deleted the eldest log file") + return + } + } +} + +func TestLogFile_deleteArchivesDisabled(t *testing.T) { + t.Parallel() + tempDir := testutil.TempDir(t, "LogWriteDeleteArchivesDisabled") + defer os.Remove(tempDir) + filt := LevelFilter() + filt.MinLevel = logutils.LogLevel("INFO") + logFile := LogFile{ + logFilter: filt, + fileName: testFileName, + logPath: tempDir, + MaxBytes: testBytes, + duration: 24 * time.Hour, + MaxFiles: 0, + } + logFile.Write([]byte("[INFO] Hello World")) + logFile.Write([]byte("[INFO] Second File")) + logFile.Write([]byte("[INFO] Third File")) + want := 3 + tempFiles, _ := ioutil.ReadDir(tempDir) + if got := tempFiles; len(got) != want { + t.Errorf("Expected %d files, got %v file(s)", want, len(got)) + return + } +} diff --git a/logger/logger.go b/logger/logger.go index 8a061936b2..d2147c03bf 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -31,6 +31,9 @@ type Config struct { //LogRotateBytes is the user specified byte limit to rotate logs LogRotateBytes int + + //LogRotateMaxFiles is the maximum number of past archived log files to keep + LogRotateMaxFiles int } const ( @@ -123,7 +126,14 @@ func Setup(config *Config, ui cli.Ui) (*logutils.LevelFilter, *GatedWriter, *Log if config.LogRotateBytes != 0 { logRotateBytes = config.LogRotateBytes } - logFile := &LogFile{logFilter: logFilter, fileName: fileName, logPath: dir, duration: logRotateDuration, MaxBytes: logRotateBytes} + logFile := &LogFile{ + logFilter: logFilter, + fileName: fileName, + logPath: dir, + duration: logRotateDuration, + MaxBytes: logRotateBytes, + MaxFiles: config.LogRotateMaxFiles, + } writers = append(writers, logFile) } diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md index bf4235bd09..2dd58ac5e6 100644 --- a/website/source/docs/agent/options.html.md +++ b/website/source/docs/agent/options.html.md @@ -273,6 +273,8 @@ will exit with an error at startup. * `-log-rotate-duration` - to specify the maximum duration a log should be written to before it needs to be rotated. Must be a duration value such as 30s. Defaults to 24h. +* `-log-rotate-max-files` - to specify the maximum number of older log file archives to keep. Defaults to 0 (no files are ever deleted). Set to -1 to disable rotation and discard all log files. + * `-join` - Address of another agent to join upon starting up. This can be specified multiple times to specify multiple agents to join. If Consul is diff --git a/website/source/docs/connect/proxies.html.md b/website/source/docs/connect/proxies.html.md index 18a249957c..87fc8ef6a9 100644 --- a/website/source/docs/connect/proxies.html.md +++ b/website/source/docs/connect/proxies.html.md @@ -12,7 +12,7 @@ A Connect-aware proxy enables unmodified applications to use Connect. A per-service proxy sidecar transparently handles inbound and outbound service connections, automatically wrapping and verifying TLS connections. Consul includes its own built-in L4 proxy and has first class support for Envoy. You -can chose other proxies to plug in as well. This section describes how to +can choose other proxies to plug in as well. This section describes how to configure Envoy or the built-in proxy using Connect, and how to integrate the proxy of your choice.