From 7fabe995742a59c805e85599bd50beaf44eb36d9 Mon Sep 17 00:00:00 2001 From: Michal Pristas Date: Tue, 24 Nov 2020 10:57:19 +0100 Subject: [PATCH] [Ingest Manager] Log level reloadable from fleet (#22690) [Ingest Manager] Log level reloadable from fleet (#22690) --- libbeat/cmd/instance/beat.go | 5 +- libbeat/cmd/instance/metrics.go | 348 ------------------ libbeat/cmd/instance/metrics_common.go | 57 --- .../cmd/instance/metrics_file_descriptors.go | 87 ----- .../instance/metrics_file_descriptors_stub.go | 24 -- libbeat/cmd/instance/metrics_handles.go | 67 ---- libbeat/cmd/instance/metrics_handles_stub.go | 23 -- libbeat/cmd/instance/metrics_other.go | 31 -- libbeat/logp/core.go | 12 +- libbeat/logp/level.go | 3 +- x-pack/elastic-agent/CHANGELOG.next.asciidoc | 1 + .../pkg/agent/application/application.go | 15 +- .../pkg/agent/application/config_test.go | 4 +- .../application/handler_action_settings.go | 52 +++ .../pkg/agent/application/info/agent_id.go | 38 +- .../pkg/agent/application/info/agent_info.go | 41 ++- .../agent/application/info/agent_metadata.go | 4 + .../agent/application/inspect_config_cmd.go | 2 +- .../agent/application/inspect_output_cmd.go | 4 +- .../pkg/agent/application/local_mode.go | 5 +- .../pkg/agent/application/managed_mode.go | 15 +- x-pack/elastic-agent/pkg/agent/cmd/run.go | 153 +++++++- .../pkg/agent/operation/monitoring.go | 59 +++ .../elastic-agent/pkg/core/logger/logger.go | 8 +- .../pkg/core/monitoring/beats/monitoring.go | 18 + x-pack/elastic-agent/pkg/fleetapi/action.go | 41 +++ 26 files changed, 422 insertions(+), 695 deletions(-) delete mode 100644 libbeat/cmd/instance/metrics.go delete mode 100644 libbeat/cmd/instance/metrics_common.go delete mode 100644 libbeat/cmd/instance/metrics_file_descriptors.go delete mode 100644 libbeat/cmd/instance/metrics_file_descriptors_stub.go delete mode 100644 libbeat/cmd/instance/metrics_handles.go delete mode 100644 libbeat/cmd/instance/metrics_handles_stub.go delete mode 100644 libbeat/cmd/instance/metrics_other.go create mode 100644 x-pack/elastic-agent/pkg/agent/application/handler_action_settings.go diff --git a/libbeat/cmd/instance/beat.go b/libbeat/cmd/instance/beat.go index b873ddebf95..acd1087f66e 100644 --- a/libbeat/cmd/instance/beat.go +++ b/libbeat/cmd/instance/beat.go @@ -42,6 +42,7 @@ import ( "github.com/elastic/beats/v7/libbeat/beat" "github.com/elastic/beats/v7/libbeat/cfgfile" "github.com/elastic/beats/v7/libbeat/cloudid" + "github.com/elastic/beats/v7/libbeat/cmd/instance/metrics" "github.com/elastic/beats/v7/libbeat/common" "github.com/elastic/beats/v7/libbeat/common/file" "github.com/elastic/beats/v7/libbeat/common/reload" @@ -235,7 +236,7 @@ func NewBeat(name, indexPrefix, v string, elasticLicensed bool) (*Beat, error) { Name: hostname, Hostname: hostname, ID: id, - EphemeralID: ephemeralID, + EphemeralID: metrics.EphemeralID(), }, Fields: fields, } @@ -314,7 +315,7 @@ func (b *Beat) createBeater(bt beat.Creator) (beat.Beater, error) { reg = monitoring.Default.NewRegistry("libbeat") } - err = setupMetrics(b.Info.Beat) + err = metrics.SetupMetrics(b.Info.Beat) if err != nil { return nil, err } diff --git a/libbeat/cmd/instance/metrics.go b/libbeat/cmd/instance/metrics.go deleted file mode 100644 index 2e727446d5d..00000000000 --- a/libbeat/cmd/instance/metrics.go +++ /dev/null @@ -1,348 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build darwin,cgo freebsd,cgo linux windows - -package instance - -import ( - "fmt" - "runtime" - - "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/logp" - "github.com/elastic/beats/v7/libbeat/metric/system/cpu" - "github.com/elastic/beats/v7/libbeat/metric/system/process" - "github.com/elastic/beats/v7/libbeat/monitoring" - "github.com/elastic/gosigar/cgroup" -) - -var ( - beatProcessStats *process.Stats - systemMetrics *monitoring.Registry -) - -func init() { - systemMetrics = monitoring.Default.NewRegistry("system") -} - -func setupMetrics(name string) error { - monitoring.NewFunc(systemMetrics, "cpu", reportSystemCPUUsage, monitoring.Report) - - //if the beat name is longer than 15 characters, truncate it so we don't fail process checks later on - // On *nix, the process name comes from /proc/PID/stat, which uses a comm value of 16 bytes, plus the null byte - if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && len(name) > 15 { - name = name[:15] - } - - beatProcessStats = &process.Stats{ - Procs: []string{name}, - EnvWhitelist: nil, - CpuTicks: true, - CacheCmdLine: true, - IncludeTop: process.IncludeTopConfig{}, - } - - err := beatProcessStats.Init() - if err != nil { - return err - } - - monitoring.NewFunc(beatMetrics, "memstats", reportMemStats, monitoring.Report) - monitoring.NewFunc(beatMetrics, "cpu", reportBeatCPU, monitoring.Report) - monitoring.NewFunc(beatMetrics, "runtime", reportRuntime, monitoring.Report) - - setupPlatformSpecificMetrics() - - return nil -} - -func setupPlatformSpecificMetrics() { - switch runtime.GOOS { - case "linux": - monitoring.NewFunc(beatMetrics, "cgroup", reportBeatCgroups, monitoring.Report) - case "windows": - setupWindowsHandlesMetrics() - } - - if runtime.GOOS != "windows" { - monitoring.NewFunc(systemMetrics, "load", reportSystemLoadAverage, monitoring.Report) - } - - setupLinuxBSDFDMetrics() -} - -func reportMemStats(m monitoring.Mode, V monitoring.Visitor) { - var stats runtime.MemStats - runtime.ReadMemStats(&stats) - - V.OnRegistryStart() - defer V.OnRegistryFinished() - - monitoring.ReportInt(V, "memory_total", int64(stats.TotalAlloc)) - if m == monitoring.Full { - monitoring.ReportInt(V, "memory_alloc", int64(stats.Alloc)) - monitoring.ReportInt(V, "gc_next", int64(stats.NextGC)) - } - - rss, err := getRSSSize() - if err != nil { - logp.Err("Error while getting memory usage: %v", err) - return - } - monitoring.ReportInt(V, "rss", int64(rss)) -} - -func getRSSSize() (uint64, error) { - state, err := getBeatProcessState() - if err != nil { - return 0, err - } - - iRss, err := state.GetValue("memory.rss.bytes") - if err != nil { - return 0, fmt.Errorf("error getting Resident Set Size: %v", err) - } - - rss, ok := iRss.(uint64) - if !ok { - return 0, fmt.Errorf("error converting Resident Set Size to uint64: %v", iRss) - } - return rss, nil -} - -func getBeatProcessState() (common.MapStr, error) { - pid, err := process.GetSelfPid() - if err != nil { - return nil, fmt.Errorf("error getting PID for self process: %v", err) - } - - state, err := beatProcessStats.GetOne(pid) - if err != nil { - return nil, fmt.Errorf("error retrieving process stats: %v", err) - } - - return state, nil -} - -func reportBeatCPU(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - totalCPUUsage, cpuTicks, err := getCPUUsage() - if err != nil { - logp.Err("Error retrieving CPU percentages: %v", err) - return - } - - userTime, systemTime, err := process.GetOwnResourceUsageTimeInMillis() - if err != nil { - logp.Err("Error retrieving CPU usage time: %v", err) - return - } - - monitoring.ReportNamespace(V, "user", func() { - monitoring.ReportInt(V, "ticks", int64(cpuTicks.User)) - monitoring.ReportNamespace(V, "time", func() { - monitoring.ReportInt(V, "ms", userTime) - }) - }) - monitoring.ReportNamespace(V, "system", func() { - monitoring.ReportInt(V, "ticks", int64(cpuTicks.System)) - monitoring.ReportNamespace(V, "time", func() { - monitoring.ReportInt(V, "ms", systemTime) - }) - }) - monitoring.ReportNamespace(V, "total", func() { - monitoring.ReportFloat(V, "value", totalCPUUsage) - monitoring.ReportInt(V, "ticks", int64(cpuTicks.Total)) - monitoring.ReportNamespace(V, "time", func() { - monitoring.ReportInt(V, "ms", userTime+systemTime) - }) - }) -} - -func getCPUUsage() (float64, *process.Ticks, error) { - state, err := getBeatProcessState() - if err != nil { - return 0.0, nil, err - } - - iTotalCPUUsage, err := state.GetValue("cpu.total.value") - if err != nil { - return 0.0, nil, fmt.Errorf("error getting total CPU since start: %v", err) - } - - totalCPUUsage, ok := iTotalCPUUsage.(float64) - if !ok { - return 0.0, nil, fmt.Errorf("error converting value of CPU usage since start to float64: %v", iTotalCPUUsage) - } - - iTotalCPUUserTicks, err := state.GetValue("cpu.user.ticks") - if err != nil { - return 0.0, nil, fmt.Errorf("error getting number of user CPU ticks since start: %v", err) - } - - totalCPUUserTicks, ok := iTotalCPUUserTicks.(uint64) - if !ok { - return 0.0, nil, fmt.Errorf("error converting value of user CPU ticks since start to uint64: %v", iTotalCPUUserTicks) - } - - iTotalCPUSystemTicks, err := state.GetValue("cpu.system.ticks") - if err != nil { - return 0.0, nil, fmt.Errorf("error getting number of system CPU ticks since start: %v", err) - } - - totalCPUSystemTicks, ok := iTotalCPUSystemTicks.(uint64) - if !ok { - return 0.0, nil, fmt.Errorf("error converting value of system CPU ticks since start to uint64: %v", iTotalCPUSystemTicks) - } - - iTotalCPUTicks, err := state.GetValue("cpu.total.ticks") - if err != nil { - return 0.0, nil, fmt.Errorf("error getting total number of CPU ticks since start: %v", err) - } - - totalCPUTicks, ok := iTotalCPUTicks.(uint64) - if !ok { - return 0.0, nil, fmt.Errorf("error converting total value of CPU ticks since start to uint64: %v", iTotalCPUTicks) - } - - p := process.Ticks{ - User: totalCPUUserTicks, - System: totalCPUSystemTicks, - Total: totalCPUTicks, - } - - return totalCPUUsage, &p, nil -} - -func reportSystemLoadAverage(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - load, err := cpu.Load() - if err != nil { - logp.Err("Error retrieving load average: %v", err) - return - } - avgs := load.Averages() - monitoring.ReportFloat(V, "1", avgs.OneMinute) - monitoring.ReportFloat(V, "5", avgs.FiveMinute) - monitoring.ReportFloat(V, "15", avgs.FifteenMinute) - - normAvgs := load.NormalizedAverages() - monitoring.ReportNamespace(V, "norm", func() { - monitoring.ReportFloat(V, "1", normAvgs.OneMinute) - monitoring.ReportFloat(V, "5", normAvgs.FiveMinute) - monitoring.ReportFloat(V, "15", normAvgs.FifteenMinute) - }) -} - -func reportSystemCPUUsage(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - monitoring.ReportInt(V, "cores", int64(process.NumCPU)) -} - -func reportRuntime(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - monitoring.ReportInt(V, "goroutines", int64(runtime.NumGoroutine())) -} - -func reportBeatCgroups(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - pid, err := process.GetSelfPid() - if err != nil { - logp.Err("error getting PID for self process: %v", err) - return - } - - cgroups, err := cgroup.NewReader("", true) - if err != nil { - if err == cgroup.ErrCgroupsMissing { - logp.Warn("cgroup data collection disabled: %v", err) - } else { - logp.Err("cgroup data collection disabled: %v", err) - } - return - } - selfStats, err := cgroups.GetStatsForProcess(pid) - if err != nil { - logp.Err("error getting group status: %v", err) - return - } - // GetStatsForProcess returns a nil selfStats and no error when there's no stats - if selfStats == nil { - return - } - - if cpu := selfStats.CPU; cpu != nil { - monitoring.ReportNamespace(V, "cpu", func() { - if cpu.ID != "" { - monitoring.ReportString(V, "id", cpu.ID) - } - monitoring.ReportNamespace(V, "cfs", func() { - monitoring.ReportNamespace(V, "period", func() { - monitoring.ReportInt(V, "us", int64(cpu.CFS.PeriodMicros)) - }) - monitoring.ReportNamespace(V, "quota", func() { - monitoring.ReportInt(V, "us", int64(cpu.CFS.QuotaMicros)) - }) - }) - monitoring.ReportNamespace(V, "stats", func() { - monitoring.ReportInt(V, "periods", int64(cpu.Stats.Periods)) - monitoring.ReportNamespace(V, "throttled", func() { - monitoring.ReportInt(V, "periods", int64(cpu.Stats.ThrottledPeriods)) - monitoring.ReportInt(V, "ns", int64(cpu.Stats.ThrottledTimeNanos)) - }) - }) - }) - } - - if cpuacct := selfStats.CPUAccounting; cpuacct != nil { - monitoring.ReportNamespace(V, "cpuacct", func() { - if cpuacct.ID != "" { - monitoring.ReportString(V, "id", cpuacct.ID) - } - monitoring.ReportNamespace(V, "total", func() { - monitoring.ReportInt(V, "ns", int64(cpuacct.TotalNanos)) - }) - }) - } - - if memory := selfStats.Memory; memory != nil { - monitoring.ReportNamespace(V, "memory", func() { - if memory.ID != "" { - monitoring.ReportString(V, "id", memory.ID) - } - monitoring.ReportNamespace(V, "mem", func() { - monitoring.ReportNamespace(V, "limit", func() { - monitoring.ReportInt(V, "bytes", int64(memory.Mem.Limit)) - }) - monitoring.ReportNamespace(V, "usage", func() { - monitoring.ReportInt(V, "bytes", int64(memory.Mem.Usage)) - }) - }) - }) - } -} diff --git a/libbeat/cmd/instance/metrics_common.go b/libbeat/cmd/instance/metrics_common.go deleted file mode 100644 index feff54b4841..00000000000 --- a/libbeat/cmd/instance/metrics_common.go +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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 instance - -import ( - "time" - - "github.com/gofrs/uuid" - - "github.com/elastic/beats/v7/libbeat/logp" - "github.com/elastic/beats/v7/libbeat/monitoring" - "github.com/elastic/beats/v7/libbeat/monitoring/report/log" -) - -var ( - ephemeralID uuid.UUID - beatMetrics *monitoring.Registry -) - -func init() { - beatMetrics = monitoring.Default.NewRegistry("beat") - monitoring.NewFunc(beatMetrics, "info", reportInfo, monitoring.Report) - - var err error - ephemeralID, err = uuid.NewV4() - if err != nil { - logp.Err("Error while generating ephemeral ID for Beat") - } -} - -func reportInfo(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - delta := time.Since(log.StartTime) - uptime := int64(delta / time.Millisecond) - monitoring.ReportNamespace(V, "uptime", func() { - monitoring.ReportInt(V, "ms", uptime) - }) - - monitoring.ReportString(V, "ephemeral_id", ephemeralID.String()) -} diff --git a/libbeat/cmd/instance/metrics_file_descriptors.go b/libbeat/cmd/instance/metrics_file_descriptors.go deleted file mode 100644 index 5d255fd483d..00000000000 --- a/libbeat/cmd/instance/metrics_file_descriptors.go +++ /dev/null @@ -1,87 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build linux freebsd,cgo - -package instance - -import ( - "fmt" - - "github.com/elastic/beats/v7/libbeat/logp" - "github.com/elastic/beats/v7/libbeat/monitoring" -) - -func setupLinuxBSDFDMetrics() { - monitoring.NewFunc(beatMetrics, "handles", reportFDUsage, monitoring.Report) -} - -func reportFDUsage(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - open, hardLimit, softLimit, err := getFDUsage() - if err != nil { - logp.Err("Error while retrieving FD information: %v", err) - return - } - - monitoring.ReportInt(V, "open", int64(open)) - monitoring.ReportNamespace(V, "limit", func() { - monitoring.ReportInt(V, "hard", int64(hardLimit)) - monitoring.ReportInt(V, "soft", int64(softLimit)) - }) -} - -func getFDUsage() (open, hardLimit, softLimit uint64, err error) { - state, err := getBeatProcessState() - if err != nil { - return 0, 0, 0, err - } - - iOpen, err := state.GetValue("fd.open") - if err != nil { - return 0, 0, 0, fmt.Errorf("error getting number of open FD: %v", err) - } - - open, ok := iOpen.(uint64) - if !ok { - return 0, 0, 0, fmt.Errorf("error converting value of open FDs to uint64: %v", iOpen) - } - - iHardLimit, err := state.GetValue("fd.limit.hard") - if err != nil { - return 0, 0, 0, fmt.Errorf("error getting FD hard limit: %v", err) - } - - hardLimit, ok = iHardLimit.(uint64) - if !ok { - return 0, 0, 0, fmt.Errorf("error converting values of FD hard limit: %v", iHardLimit) - } - - iSoftLimit, err := state.GetValue("fd.limit.soft") - if err != nil { - return 0, 0, 0, fmt.Errorf("error getting FD hard limit: %v", err) - } - - softLimit, ok = iSoftLimit.(uint64) - if !ok { - return 0, 0, 0, fmt.Errorf("error converting values of FD hard limit: %v", iSoftLimit) - } - - return open, hardLimit, softLimit, nil -} diff --git a/libbeat/cmd/instance/metrics_file_descriptors_stub.go b/libbeat/cmd/instance/metrics_file_descriptors_stub.go deleted file mode 100644 index f6665ce2944..00000000000 --- a/libbeat/cmd/instance/metrics_file_descriptors_stub.go +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build !linux -// +build !freebsd !cgo - -package instance - -// FDUsage is only supported on Linux and FreeBSD. -func setupLinuxBSDFDMetrics() {} diff --git a/libbeat/cmd/instance/metrics_handles.go b/libbeat/cmd/instance/metrics_handles.go deleted file mode 100644 index 497fe5edcd6..00000000000 --- a/libbeat/cmd/instance/metrics_handles.go +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build windows - -package instance - -import ( - "github.com/elastic/beats/v7/libbeat/logp" - "github.com/elastic/beats/v7/libbeat/monitoring" - sysinfo "github.com/elastic/go-sysinfo" - "github.com/elastic/go-sysinfo/types" -) - -const ( - fileHandlesNotReported = "Following metrics will not be reported: beat.handles.open" -) - -var ( - handleCounter types.OpenHandleCounter -) - -func setupWindowsHandlesMetrics() { - beatProcessSysInfo, err := sysinfo.Self() - if err != nil { - logp.Err("Error while getting own process info: %v", err) - logp.Err(fileHandlesNotReported) - return - } - - var ok bool - handleCounter, ok = beatProcessSysInfo.(types.OpenHandleCounter) - if !ok { - logp.Err("Process does not implement types.OpenHandleCounter: %v", beatProcessSysInfo) - logp.Err(fileHandlesNotReported) - return - } - - monitoring.NewFunc(beatMetrics, "handles", reportOpenHandles, monitoring.Report) -} - -func reportOpenHandles(_ monitoring.Mode, V monitoring.Visitor) { - V.OnRegistryStart() - defer V.OnRegistryFinished() - - n, err := handleCounter.OpenHandleCount() - if err != nil { - logp.Err("Error while retrieving the number of open file handles: %v", err) - return - } - - monitoring.ReportInt(V, "open", int64(n)) -} diff --git a/libbeat/cmd/instance/metrics_handles_stub.go b/libbeat/cmd/instance/metrics_handles_stub.go deleted file mode 100644 index 677cc5aaf28..00000000000 --- a/libbeat/cmd/instance/metrics_handles_stub.go +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build !windows - -package instance - -// Counting number of open handles is only supported on Windows. -func setupWindowsHandlesMetrics() {} diff --git a/libbeat/cmd/instance/metrics_other.go b/libbeat/cmd/instance/metrics_other.go deleted file mode 100644 index 2ea96cd6c8e..00000000000 --- a/libbeat/cmd/instance/metrics_other.go +++ /dev/null @@ -1,31 +0,0 @@ -// Licensed to Elasticsearch B.V. under one or more contributor -// license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright -// ownership. Elasticsearch B.V. licenses this file to you 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. - -// +build !darwin !cgo -// +build !freebsd !cgo -// +build !linux,!windows - -package instance - -import ( - "github.com/elastic/beats/v7/libbeat/logp" -) - -func setupMetrics(name string) error { - logp.Warn("Metrics not implemented for this OS.") - return nil -} diff --git a/libbeat/logp/core.go b/libbeat/logp/core.go index afb4f57378d..7ccfacde5ff 100644 --- a/libbeat/logp/core.go +++ b/libbeat/logp/core.go @@ -79,7 +79,7 @@ func ConfigureWithOutputs(cfg Config, outputs ...zapcore.Core) error { // Build a single output (stderr has priority if more than one are enabled). if cfg.toObserver { - sink, observedLogs = observer.New(cfg.Level.zapLevel()) + sink, observedLogs = observer.New(cfg.Level.ZapLevel()) } else { sink, err = createLogOutput(cfg) } @@ -201,16 +201,16 @@ func makeOptions(cfg Config) []zap.Option { func makeStderrOutput(cfg Config) (zapcore.Core, error) { stderr := zapcore.Lock(os.Stderr) - return newCore(cfg, buildEncoder(cfg), stderr, cfg.Level.zapLevel()), nil + return newCore(cfg, buildEncoder(cfg), stderr, cfg.Level.ZapLevel()), nil } func makeDiscardOutput(cfg Config) (zapcore.Core, error) { discard := zapcore.AddSync(ioutil.Discard) - return newCore(cfg, buildEncoder(cfg), discard, cfg.Level.zapLevel()), nil + return newCore(cfg, buildEncoder(cfg), discard, cfg.Level.ZapLevel()), nil } func makeSyslogOutput(cfg Config) (zapcore.Core, error) { - core, err := newSyslog(buildEncoder(cfg), cfg.Level.zapLevel()) + core, err := newSyslog(buildEncoder(cfg), cfg.Level.ZapLevel()) if err != nil { return nil, err } @@ -218,7 +218,7 @@ func makeSyslogOutput(cfg Config) (zapcore.Core, error) { } func makeEventLogOutput(cfg Config) (zapcore.Core, error) { - core, err := newEventLog(cfg.Beat, buildEncoder(cfg), cfg.Level.zapLevel()) + core, err := newEventLog(cfg.Beat, buildEncoder(cfg), cfg.Level.ZapLevel()) if err != nil { return nil, err } @@ -244,7 +244,7 @@ func makeFileOutput(cfg Config) (zapcore.Core, error) { return nil, errors.Wrap(err, "failed to create file rotator") } - return newCore(cfg, buildEncoder(cfg), rotator, cfg.Level.zapLevel()), nil + return newCore(cfg, buildEncoder(cfg), rotator, cfg.Level.ZapLevel()), nil } func newCore(cfg Config, enc zapcore.Encoder, ws zapcore.WriteSyncer, enab zapcore.LevelEnabler) zapcore.Core { diff --git a/libbeat/logp/level.go b/libbeat/logp/level.go index 95e7c5c7794..f1699d46e53 100644 --- a/libbeat/logp/level.go +++ b/libbeat/logp/level.go @@ -101,7 +101,8 @@ func (l Level) MarshalJSON() ([]byte, error) { return nil, errors.Errorf("invalid level '%d'", l) } -func (l Level) zapLevel() zapcore.Level { +// ZapLevel returns zap alternative to logp.Level. +func (l Level) ZapLevel() zapcore.Level { z, found := zapLevels[l] if found { return z diff --git a/x-pack/elastic-agent/CHANGELOG.next.asciidoc b/x-pack/elastic-agent/CHANGELOG.next.asciidoc index 9864728cc14..f2e70273436 100644 --- a/x-pack/elastic-agent/CHANGELOG.next.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.next.asciidoc @@ -51,3 +51,4 @@ - Removed `install-service.ps1` and `uninstall-service.ps1` from Windows .zip packaging {pull}21694[21694] - Add `priority` to `AddOrUpdate` on dynamic composable input providers communication channel {pull}22352[22352] - Ship `endpoint-security` logs to elasticsearch {pull}22526[22526] +- Log level reloadable from fleet {pull}22690[22690] diff --git a/x-pack/elastic-agent/pkg/agent/application/application.go b/x-pack/elastic-agent/pkg/agent/application/application.go index d721a8aa148..f87cad3a09c 100644 --- a/x-pack/elastic-agent/pkg/agent/application/application.go +++ b/x-pack/elastic-agent/pkg/agent/application/application.go @@ -31,7 +31,7 @@ type upgraderControl interface { } // New creates a new Agent and bootstrap the required subsystem. -func New(log *logger.Logger, pathConfigFile string, reexec reexecManager, uc upgraderControl) (Application, error) { +func New(log *logger.Logger, pathConfigFile string, reexec reexecManager, uc upgraderControl, agentInfo *info.AgentInfo) (Application, error) { // Load configuration from disk to understand in which mode of operation // we must start the elastic-agent, the mode of operation cannot be changed without restarting the // elastic-agent. @@ -44,7 +44,7 @@ func New(log *logger.Logger, pathConfigFile string, reexec reexecManager, uc upg return nil, err } - return createApplication(log, pathConfigFile, rawConfig, reexec, uc) + return createApplication(log, pathConfigFile, rawConfig, reexec, uc, agentInfo) } func createApplication( @@ -53,6 +53,7 @@ func createApplication( rawConfig *config.Config, reexec reexecManager, uc upgraderControl, + agentInfo *info.AgentInfo, ) (Application, error) { warn.LogNotGA(log) log.Info("Detecting execution mode") @@ -63,16 +64,16 @@ func createApplication( return nil, err } - if isStandalone(cfg.Fleet) { + if IsStandalone(cfg.Fleet) { log.Info("Agent is managed locally") - return newLocal(ctx, log, pathConfigFile, rawConfig, reexec, uc) + return newLocal(ctx, log, pathConfigFile, rawConfig, reexec, uc, agentInfo) } log.Info("Agent is managed by Fleet") - return newManaged(ctx, log, rawConfig, reexec) + return newManaged(ctx, log, rawConfig, reexec, agentInfo) } -// missing of fleet.enabled: true or fleet.{access_token,kibana} will place Elastic Agent into standalone mode. -func isStandalone(cfg *configuration.FleetAgentConfig) bool { +// IsStandalone decides based on missing of fleet.enabled: true or fleet.{access_token,kibana} will place Elastic Agent into standalone mode. +func IsStandalone(cfg *configuration.FleetAgentConfig) bool { return cfg == nil || !cfg.Enabled } diff --git a/x-pack/elastic-agent/pkg/agent/application/config_test.go b/x-pack/elastic-agent/pkg/agent/application/config_test.go index 4d4527a1e60..09acd68dd83 100644 --- a/x-pack/elastic-agent/pkg/agent/application/config_test.go +++ b/x-pack/elastic-agent/pkg/agent/application/config_test.go @@ -70,7 +70,7 @@ func testMgmtMode(t *testing.T) { err := c.Unpack(&m) require.NoError(t, err) assert.Equal(t, false, m.Fleet.Enabled) - assert.Equal(t, true, isStandalone(m.Fleet)) + assert.Equal(t, true, IsStandalone(m.Fleet)) }) @@ -80,7 +80,7 @@ func testMgmtMode(t *testing.T) { err := c.Unpack(&m) require.NoError(t, err) assert.Equal(t, true, m.Fleet.Enabled) - assert.Equal(t, false, isStandalone(m.Fleet)) + assert.Equal(t, false, IsStandalone(m.Fleet)) }) } diff --git a/x-pack/elastic-agent/pkg/agent/application/handler_action_settings.go b/x-pack/elastic-agent/pkg/agent/application/handler_action_settings.go new file mode 100644 index 00000000000..bb0e2def363 --- /dev/null +++ b/x-pack/elastic-agent/pkg/agent/application/handler_action_settings.go @@ -0,0 +1,52 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package application + +import ( + "context" + "fmt" + + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/fleetapi" +) + +// handlerSettings handles settings change coming from fleet and updates log level. +type handlerSettings struct { + log *logger.Logger + reexec reexecManager + agentInfo *info.AgentInfo +} + +// Handle handles SETTINGS action. +func (h *handlerSettings) Handle(ctx context.Context, a action, acker fleetAcker) error { + h.log.Debugf("handlerUpgrade: action '%+v' received", a) + action, ok := a.(*fleetapi.ActionSettings) + if !ok { + return fmt.Errorf("invalid type, expected ActionSettings and received %T", a) + } + + if !isSupportedLogLevel(action.LogLevel) { + return fmt.Errorf("invalid log level, expected debug|info|warning|error and received '%s'", action.LogLevel) + } + + if err := h.agentInfo.LogLevel(action.LogLevel); err != nil { + return errors.New("failed to update log level", err) + } + + if err := acker.Ack(ctx, a); err != nil { + h.log.Errorf("failed to acknowledge SETTINGS action with id '%s'", action.ActionID) + } else if err := acker.Commit(ctx); err != nil { + h.log.Errorf("failed to commit acker after acknowledging action with id '%s'", action.ActionID) + } + + h.reexec.ReExec() + return nil +} + +func isSupportedLogLevel(level string) bool { + return level == "error" || level == "debug" || level == "info" || level == "warning" +} diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go index a93483ca1cd..f18fa542a25 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_id.go @@ -26,8 +26,11 @@ const agentInfoKey = "agent" // defaultAgentActionStoreFile is the file that will contains the action that can be replayed after restart. const defaultAgentActionStoreFile = "action_store.yml" +const defaultLogLevel = "info" + type persistentAgentInfo struct { - ID string `json:"id" yaml:"id" config:"id"` + ID string `json:"id" yaml:"id" config:"id"` + LogLevel string `json:"logging.level,omitempty" yaml:"logging.level,omitempty" config:"logging.level,omitempty"` } type ioStore interface { @@ -45,6 +48,25 @@ func AgentActionStoreFile() string { return filepath.Join(paths.Home(), defaultAgentActionStoreFile) } +// updateLogLevel updates log level and persists it to disk. +func updateLogLevel(level string) error { + ai, err := loadAgentInfo(false, defaultLogLevel) + if err != nil { + return err + } + + if ai.LogLevel == level { + // no action needed + return nil + } + + agentConfigFile := AgentConfigFile() + s := storage.NewDiskStore(agentConfigFile) + + ai.LogLevel = level + return updateAgentInfo(s, ai) +} + func generateAgentID() (string, error) { uid, err := uuid.NewV4() if err != nil { @@ -54,11 +76,11 @@ func generateAgentID() (string, error) { return uid.String(), nil } -func loadAgentInfo(forceUpdate bool) (*persistentAgentInfo, error) { +func loadAgentInfo(forceUpdate bool, logLevel string) (*persistentAgentInfo, error) { agentConfigFile := AgentConfigFile() s := storage.NewDiskStore(agentConfigFile) - agentinfo, err := getInfoFromStore(s) + agentinfo, err := getInfoFromStore(s, logLevel) if err != nil { return nil, err } @@ -79,7 +101,7 @@ func loadAgentInfo(forceUpdate bool) (*persistentAgentInfo, error) { return agentinfo, nil } -func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { +func getInfoFromStore(s ioStore, logLevel string) (*persistentAgentInfo, error) { agentConfigFile := AgentConfigFile() reader, err := s.Load() if err != nil { @@ -104,7 +126,9 @@ func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { agentInfoSubMap, found := configMap[agentInfoKey] if !found { - return &persistentAgentInfo{}, nil + return &persistentAgentInfo{ + LogLevel: logLevel, + }, nil } cc, err := config.NewConfigFrom(agentInfoSubMap) @@ -112,7 +136,9 @@ func getInfoFromStore(s ioStore) (*persistentAgentInfo, error) { return nil, errors.New(err, "failed to create config from agent info submap") } - pid := &persistentAgentInfo{} + pid := &persistentAgentInfo{ + LogLevel: logLevel, + } if err := cc.Unpack(&pid); err != nil { return nil, errors.New(err, "failed to unpack stored config to map") } diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go index b0abbe19e64..827ae6300b9 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_info.go @@ -4,42 +4,51 @@ package info -import "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" +import ( + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" +) // AgentInfo is a collection of information about agent. type AgentInfo struct { - agentID string + agentID string + logLevel string } -// NewAgentInfo creates a new agent information. +// NewAgentInfoWithLog creates a new agent information. // In case when agent ID was already created it returns, // this created ID otherwise it generates // new unique identifier for agent. // If agent config file does not exist it gets created. -func NewAgentInfo() (*AgentInfo, error) { - agentInfo, err := loadAgentInfo(false) +// Initiates log level to predefined value. +func NewAgentInfoWithLog(level string) (*AgentInfo, error) { + agentInfo, err := loadAgentInfo(false, level) if err != nil { return nil, err } return &AgentInfo{ - agentID: agentInfo.ID, + agentID: agentInfo.ID, + logLevel: agentInfo.LogLevel, }, nil } -// ForceNewAgentInfo creates a new agent information. -// Generates new unique identifier for agent regardless -// of any existing ID. +// NewAgentInfo creates a new agent information. +// In case when agent ID was already created it returns, +// this created ID otherwise it generates +// new unique identifier for agent. // If agent config file does not exist it gets created. -func ForceNewAgentInfo() (*AgentInfo, error) { - agentInfo, err := loadAgentInfo(true) - if err != nil { - return nil, err +func NewAgentInfo() (*AgentInfo, error) { + return NewAgentInfoWithLog(defaultLogLevel) +} + +// LogLevel updates log level of agent. +func (i *AgentInfo) LogLevel(level string) error { + if err := updateLogLevel(level); err != nil { + return err } - return &AgentInfo{ - agentID: agentInfo.ID, - }, nil + i.logLevel = level + return nil } // AgentID returns an agent identifier. diff --git a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go index c5712646cfb..af35372ac2e 100644 --- a/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go +++ b/x-pack/elastic-agent/pkg/agent/application/info/agent_metadata.go @@ -40,6 +40,9 @@ type AgentECSMeta struct { BuildOriginal string `json:"build.original"` // Upgradeable is a flag specifying if it is possible for agent to be upgraded. Upgradeable bool `json:"upgradeable"` + // LogLevel describes currently set log level. + // Possible values: "debug"|"info"|"warning"|"error" + LogLevel string `json:"log_level"` } // SystemECSMeta is a collection of operating system metadata in ECS compliant object form. @@ -140,6 +143,7 @@ func (i *AgentInfo) ECSMetadata() (*ECSMeta, error) { // only upgradeable if running from Agent installer and running under the // control of the system supervisor (or built specifically with upgrading enabled) Upgradeable: release.Upgradeable() || (install.RunningInstalled() && install.RunningUnderSupervisor()), + LogLevel: i.logLevel, }, }, Host: &HostECSMeta{ diff --git a/x-pack/elastic-agent/pkg/agent/application/inspect_config_cmd.go b/x-pack/elastic-agent/pkg/agent/application/inspect_config_cmd.go index edf1ad8cdf2..f1bd2893bf0 100644 --- a/x-pack/elastic-agent/pkg/agent/application/inspect_config_cmd.go +++ b/x-pack/elastic-agent/pkg/agent/application/inspect_config_cmd.go @@ -46,7 +46,7 @@ func (c *InspectConfigCmd) inspectConfig() error { return err } - if isStandalone(cfg.Fleet) { + if IsStandalone(cfg.Fleet) { return printConfig(rawConfig) } diff --git a/x-pack/elastic-agent/pkg/agent/application/inspect_output_cmd.go b/x-pack/elastic-agent/pkg/agent/application/inspect_output_cmd.go index bb319ce1569..39c578344c4 100644 --- a/x-pack/elastic-agent/pkg/agent/application/inspect_output_cmd.go +++ b/x-pack/elastic-agent/pkg/agent/application/inspect_output_cmd.go @@ -66,7 +66,7 @@ func (c *InspectOutputCmd) inspectOutputs(agentInfo *info.AgentInfo) error { return err } - if isStandalone(cfg.Fleet) { + if IsStandalone(cfg.Fleet) { return listOutputsFromConfig(l, agentInfo, rawConfig) } @@ -119,7 +119,7 @@ func (c *InspectOutputCmd) inspectOutput(agentInfo *info.AgentInfo) error { return err } - if isStandalone(cfg.Fleet) { + if IsStandalone(cfg.Fleet) { return printOutputFromConfig(l, agentInfo, c.output, c.program, rawConfig) } diff --git a/x-pack/elastic-agent/pkg/agent/application/local_mode.go b/x-pack/elastic-agent/pkg/agent/application/local_mode.go index f0c4153f474..b1736485ceb 100644 --- a/x-pack/elastic-agent/pkg/agent/application/local_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/local_mode.go @@ -63,6 +63,7 @@ func newLocal( rawConfig *config.Config, reexec reexecManager, uc upgraderControl, + agentInfo *info.AgentInfo, ) (*Local, error) { cfg, err := configuration.NewFromConfig(rawConfig) if err != nil { @@ -75,10 +76,6 @@ func newLocal( return nil, err } } - agentInfo, err := info.NewAgentInfo() - if err != nil { - return nil, err - } logR := logreporter.NewReporter(log) diff --git a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go index fa31215f75d..aab53374c12 100644 --- a/x-pack/elastic-agent/pkg/agent/application/managed_mode.go +++ b/x-pack/elastic-agent/pkg/agent/application/managed_mode.go @@ -62,12 +62,8 @@ func newManaged( log *logger.Logger, rawConfig *config.Config, reexec reexecManager, + agentInfo *info.AgentInfo, ) (*Managed, error) { - agentInfo, err := info.NewAgentInfo() - if err != nil { - return nil, err - } - path := info.AgentConfigFile() store := storage.NewDiskStore(path) @@ -241,6 +237,15 @@ func newManaged( }, ) + actionDispatcher.MustRegister( + &fleetapi.ActionSettings{}, + &handlerSettings{ + log: log, + reexec: reexec, + agentInfo: agentInfo, + }, + ) + actionDispatcher.MustRegister( &fleetapi.ActionUnknown{}, &handlerUnknown{log: log}, diff --git a/x-pack/elastic-agent/pkg/agent/cmd/run.go b/x-pack/elastic-agent/pkg/agent/cmd/run.go index b014cd69084..89a2081ed26 100644 --- a/x-pack/elastic-agent/pkg/agent/cmd/run.go +++ b/x-pack/elastic-agent/pkg/agent/cmd/run.go @@ -14,16 +14,25 @@ import ( "github.com/spf13/cobra" + "github.com/elastic/beats/v7/libbeat/api" + "github.com/elastic/beats/v7/libbeat/cmd/instance/metrics" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/metric/system/host" + "github.com/elastic/beats/v7/libbeat/monitoring" + "github.com/elastic/beats/v7/libbeat/outputs" "github.com/elastic/beats/v7/libbeat/service" - "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/info" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/reexec" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/configuration" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/control/server" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/storage" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/cli" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/logger" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/monitoring/beats" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/release" ) @@ -75,6 +84,10 @@ func run(flags *globalFlags, streams *cli.IOStreams) error { // Windows: Mark se errors.M(errors.MetaKeyPath, pathConfigFile)) } + if err := getOverwrites(rawConfig); err != nil { + return errors.New(err, "could not read overwrites") + } + cfg, err := configuration.NewFromConfig(rawConfig) if err != nil { return errors.New(err, @@ -83,6 +96,14 @@ func run(flags *globalFlags, streams *cli.IOStreams) error { // Windows: Mark se errors.M(errors.MetaKeyPath, pathConfigFile)) } + agentInfo, err := info.NewAgentInfoWithLog(defaultLogLevel(cfg)) + if err != nil { + return errors.New(err, + "could not load agent info", + errors.TypeFilesystem, + errors.M(errors.MetaKeyPath, pathConfigFile)) + } + logger, err := logger.NewFromConfig("", cfg.Settings.LoggingConfig) if err != nil { return err @@ -106,11 +127,17 @@ func run(flags *globalFlags, streams *cli.IOStreams) error { // Windows: Mark se } defer control.Stop() - app, err := application.New(logger, pathConfigFile, rex, control) + app, err := application.New(logger, pathConfigFile, rex, control, agentInfo) if err != nil { return err } + serverStopFn, err := setupMetrics(agentInfo, logger, cfg.Settings.DownloadConfig.OS()) + if err != nil { + return err + } + defer serverStopFn() + if err := app.Start(); err != nil { return err } @@ -148,6 +175,7 @@ func run(flags *globalFlags, streams *cli.IOStreams) error { // Windows: Mark se logger.Info("Shutting down completed.") return err } + rex.ShutdownComplete() return err } @@ -164,3 +192,124 @@ func reexecPath() (string, error) { return potentialReexec, nil } + +func getOverwrites(rawConfig *config.Config) error { + path := info.AgentConfigFile() + + store := storage.NewDiskStore(path) + reader, err := store.Load() + if err != nil && errors.Is(err, os.ErrNotExist) { + // no fleet file ignore + return nil + } else if err != nil { + return errors.New(err, "could not initialize config store", + errors.TypeFilesystem, + errors.M(errors.MetaKeyPath, path)) + } + + config, err := config.NewConfigFrom(reader) + if err != nil { + return errors.New(err, + fmt.Sprintf("fail to read configuration %s for the elastic-agent", path), + errors.TypeFilesystem, + errors.M(errors.MetaKeyPath, path)) + } + + err = rawConfig.Merge(config) + if err != nil { + return errors.New(err, + fmt.Sprintf("fail to merge configuration with %s for the elastic-agent", path), + errors.TypeConfig, + errors.M(errors.MetaKeyPath, path)) + } + + return nil +} + +func defaultLogLevel(cfg *configuration.Configuration) string { + if application.IsStandalone(cfg.Fleet) { + // for standalone always take the one from config and don't override + return "" + } + + defaultLogLevel := logger.DefaultLoggingConfig().Level.String() + if configuredLevel := cfg.Settings.LoggingConfig.Level.String(); configuredLevel != "" && configuredLevel != defaultLogLevel { + // predefined log level + return configuredLevel + } + + return defaultLogLevel +} + +func setupMetrics(agentInfo *info.AgentInfo, logger *logger.Logger, operatingSystem string) (func() error, error) { + agentID := agentInfo.AgentID() + ecsMetadata, err := agentInfo.ECSMetadata() + if err != nil { + return nil, err + } + + reg := monitoring.Default.GetRegistry("libbeat") + if reg == nil { + reg = monitoring.Default.NewRegistry("libbeat") + metrics := reg.NewRegistry("output") + outputs.NewStats(metrics) + monitoring.NewString(metrics, "type").Set("") + } + + // use libbeat to setup metrics + if err := metrics.SetupMetrics(agentName); err != nil { + return nil, err + } + + infoRegistry := monitoring.GetNamespace("info").GetRegistry() + monitoring.NewString(infoRegistry, "version").Set(agentInfo.Version()) + monitoring.NewString(infoRegistry, "beat").Set(agentName) + monitoring.NewString(infoRegistry, "name").Set(agentName) + monitoring.NewString(infoRegistry, "hostname").Set(ecsMetadata.Host.Hostname) + monitoring.NewString(infoRegistry, "uuid").Set(agentID) + + // Add additional info to state registry. This is also reported to monitoring + stateRegistry := monitoring.GetNamespace("state").GetRegistry() + + serviceRegistry := stateRegistry.NewRegistry("service") + monitoring.NewString(serviceRegistry, "version").Set(agentInfo.Version()) + monitoring.NewString(serviceRegistry, "name").Set(agentName) + monitoring.NewString(serviceRegistry, "id").Set(agentID) + + mgmt := stateRegistry.NewRegistry("management") + monitoring.NewBool(mgmt, "enabled").Set(false) + + beatRegistry := stateRegistry.NewRegistry("beat") + monitoring.NewString(beatRegistry, "name").Set(agentName) + monitoring.NewFunc(stateRegistry, "host", host.ReportInfo, monitoring.Report) + + // TODO: when having output provide metrics, so far empty name + outputRegistry := stateRegistry.NewRegistry("output") + monitoring.NewString(outputRegistry, "name").Set("") + + queueRegistry := stateRegistry.NewRegistry("queue") + monitoring.NewString(queueRegistry, "name").Set("") + + moduleRegistry := stateRegistry.NewRegistry("module") + monitoring.NewInt(moduleRegistry, "count").Set(0) + + // start server for stats + endpointConfig := api.Config{ + Enabled: true, + Host: beats.AgentMonitoringEndpoint(operatingSystem), + } + + cfg, err := common.NewConfigFrom(endpointConfig) + if err != nil { + return nil, err + } + + s, err := api.NewWithDefaultRoutes(logger, cfg, monitoring.GetNamespace) + if err != nil { + return nil, errors.New(err, "could not start the HTTP server for the API") + } + s.Start() + + // return server stopper + return s.Stop, nil +} diff --git a/x-pack/elastic-agent/pkg/agent/operation/monitoring.go b/x-pack/elastic-agent/pkg/agent/operation/monitoring.go index 5d0bd889613..fc1458c4ec5 100644 --- a/x-pack/elastic-agent/pkg/agent/operation/monitoring.go +++ b/x-pack/elastic-agent/pkg/agent/operation/monitoring.go @@ -14,8 +14,10 @@ import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/application/paths" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/configrequest" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/errors" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/install" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/agent/program" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/app" + "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/monitoring/beats" ) const ( @@ -24,6 +26,7 @@ const ( logsProcessName = "filebeat" metricsProcessName = "metricbeat" artifactPrefix = "beats" + agentName = "elastic-agent" ) func (o *Operator) handleStartSidecar(s configrequest.Step) (result error) { @@ -324,6 +327,58 @@ func (o *Operator) getMonitoringMetricbeatConfig(output interface{}) (map[string }, }) } + + // setup cpu, memory and fd monitors for agents + modules = append(modules, map[string]interface{}{ + "module": "system", + "period": "10s", + "metricsets": []string{"process", "memory", "cpu"}, + "cpu.metrics": []string{"percentages", "normalized_percentages", "ticks"}, + "index": fmt.Sprintf("metrics-elastic_agent.%s-default", agentName), + "processes": []string{install.BinaryName}, + "processors": []map[string]interface{}{ + { + "add_fields": map[string]interface{}{ + "target": "data_stream", + "fields": map[string]interface{}{ + "type": "metrics", + "dataset": fmt.Sprintf("elastic_agent.%s", agentName), + "namespace": "default", + }, + }, + }, + { + "add_fields": map[string]interface{}{ + "target": "event", + "fields": map[string]interface{}{ + "dataset": fmt.Sprintf("elastic_agent.%s", agentName), + }, + }, + }, + { + "add_fields": map[string]interface{}{ + "target": "elastic_agent", + "fields": map[string]interface{}{ + "id": o.agentInfo.AgentID(), + "version": o.agentInfo.Version(), + "snapshot": o.agentInfo.Snapshot(), + }, + }, + }, + { + "drop_event": map[string]interface{}{ + "when": map[string]interface{}{ + "not": map[string]interface{}{ + "equals": map[string]interface{}{ + "process.name": install.BinaryName, + }, + }, + }, + }, + }, + }, + }) + result := map[string]interface{}{ "metricbeat": map[string]interface{}{ "modules": modules, @@ -367,6 +422,10 @@ func (o *Operator) getMetricbeatEndpoints() map[string][]string { } } + // add agent endpoint + agentEndpoint := beats.AgentPrefixedMonitoringEndpoint(o.config.DownloadConfig.OS()) + endpoints[agentName] = []string{agentEndpoint} + return endpoints } diff --git a/x-pack/elastic-agent/pkg/core/logger/logger.go b/x-pack/elastic-agent/pkg/core/logger/logger.go index a2886ccf28e..3f36e2b540a 100644 --- a/x-pack/elastic-agent/pkg/core/logger/logger.go +++ b/x-pack/elastic-agent/pkg/core/logger/logger.go @@ -54,7 +54,7 @@ func new(name string, cfg *Config) (*Logger, error) { if err != nil { return nil, err } - internal, err := makeInternalFileOutput() + internal, err := makeInternalFileOutput(cfg) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func toCommonConfig(cfg *Config) (*common.Config, error) { func DefaultLoggingConfig() *Config { cfg := logp.DefaultConfig(logp.DefaultEnvironment) cfg.Beat = agentName - cfg.Level = logp.DebugLevel + cfg.Level = logp.InfoLevel cfg.Files.Path = paths.Logs() cfg.Files.Name = fmt.Sprintf("%s.log", agentName) @@ -96,7 +96,7 @@ func DefaultLoggingConfig() *Config { // makeInternalFileOutput creates a zapcore.Core logger that cannot be changed with configuration. // // This is the logger that the spawned filebeat expects to read the log file from and ship to ES. -func makeInternalFileOutput() (zapcore.Core, error) { +func makeInternalFileOutput(cfg *Config) (zapcore.Core, error) { // defaultCfg is used to set the defaults for the file rotation of the internal logging // these settings cannot be changed by a user configuration defaultCfg := logp.DefaultConfig(logp.DefaultEnvironment) @@ -115,5 +115,5 @@ func makeInternalFileOutput() (zapcore.Core, error) { } encoder := zapcore.NewJSONEncoder(ecszap.ECSCompatibleEncoderConfig(logp.JSONEncoderConfig())) - return ecszap.WrapCore(zapcore.NewCore(encoder, rotator, zapcore.DebugLevel)), nil + return ecszap.WrapCore(zapcore.NewCore(encoder, rotator, cfg.Level.ZapLevel())), nil } diff --git a/x-pack/elastic-agent/pkg/core/monitoring/beats/monitoring.go b/x-pack/elastic-agent/pkg/core/monitoring/beats/monitoring.go index 203a101fd83..32b78cf3bce 100644 --- a/x-pack/elastic-agent/pkg/core/monitoring/beats/monitoring.go +++ b/x-pack/elastic-agent/pkg/core/monitoring/beats/monitoring.go @@ -21,8 +21,26 @@ const ( mbEndpointFileFormat = "unix:///tmp/elastic-agent/%s/%s/%s.sock" // args: pipeline name, application name mbEndpointFileFormatWin = `npipe:///%s-%s` + + // args: pipeline name, application name + agentMbEndpointFileFormat = "unix:///tmp/elastic-agent/elastic-agent.sock" + // args: pipeline name, application name + agentMbEndpointFileFormatWin = `npipe:///elastic-agent` ) +// AgentMonitoringEndpoint returns endpoint with exposed metrics for agent. +func AgentMonitoringEndpoint(operatingSystem string) string { + if operatingSystem == "windows" { + return agentMbEndpointFileFormatWin + } + return agentMbEndpointFileFormat +} + +// AgentMonitoringEndpoint returns endpoint with exposed metrics for agent. +func AgentPrefixedMonitoringEndpoint(operatingSystem string) string { + return httpPlusPrefix + AgentMonitoringEndpoint(operatingSystem) +} + func getMonitoringEndpoint(spec program.Spec, operatingSystem, pipelineID string) string { if endpoint, ok := spec.MetricEndpoints[operatingSystem]; ok { return endpoint diff --git a/x-pack/elastic-agent/pkg/fleetapi/action.go b/x-pack/elastic-agent/pkg/fleetapi/action.go index 2329546629c..211b9199f2f 100644 --- a/x-pack/elastic-agent/pkg/fleetapi/action.go +++ b/x-pack/elastic-agent/pkg/fleetapi/action.go @@ -19,6 +19,8 @@ const ( ActionTypeUnenroll = "UNENROLL" // ActionTypePolicyChange specifies policy change action. ActionTypePolicyChange = "POLICY_CHANGE" + // ActionTypeSettings specifies change of agent settings. + ActionTypeSettings = "SETTINGS" ) // Action base interface for all the implemented action from the fleet API. @@ -145,6 +147,34 @@ func (a *ActionUnenroll) ID() string { return a.ActionID } +// ActionSettings is a request to change agent settings. +type ActionSettings struct { + ActionID string + ActionType string + LogLevel string `json:"log_level"` +} + +func (a *ActionSettings) String() string { + var s strings.Builder + s.WriteString("action_id: ") + s.WriteString(a.ActionID) + s.WriteString(", type: ") + s.WriteString(a.ActionType) + s.WriteString(", log_level: ") + s.WriteString(a.LogLevel) + return s.String() +} + +// Type returns the type of the Action. +func (a *ActionSettings) Type() string { + return a.ActionType +} + +// ID returns the ID of the Action. +func (a *ActionSettings) ID() string { + return a.ActionID +} + // Actions is a list of Actions to executes and allow to unmarshal heterogenous action type. type Actions []Action @@ -195,6 +225,17 @@ func (a *Actions) UnmarshalJSON(data []byte) error { "fail to decode UPGRADE_ACTION action", errors.TypeConfig) } + case ActionTypeSettings: + action = &ActionSettings{ + ActionID: response.ActionID, + ActionType: response.ActionType, + } + + if err := json.Unmarshal(response.Data, action); err != nil { + return errors.New(err, + "fail to decode SETTINGS_ACTION action", + errors.TypeConfig) + } default: action = &ActionUnknown{ ActionID: response.ActionID,