From fb413abe9c94573a738235abc4dcf93ec8f6db18 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 30 Oct 2024 23:49:04 -0400 Subject: [PATCH 01/26] Implement an initial version of the support bundle in Alloy --- go.mod | 2 + go.sum | 2 + internal/alloycli/cmd_run.go | 26 ++- internal/runtime/alloy.go | 13 ++ internal/runtime/logging/logger.go | 53 +++++-- internal/service/http/http.go | 88 ++++++++++- internal/service/http/supportbundle.go | 210 +++++++++++++++++++++++++ 7 files changed, 376 insertions(+), 18 deletions(-) create mode 100644 internal/service/http/supportbundle.go diff --git a/go.mod b/go.mod index 5fbf0d7c5c..719b2b81c5 100644 --- a/go.mod +++ b/go.mod @@ -819,6 +819,8 @@ require ( require github.com/moby/sys/userns v0.1.0 // indirect +require github.com/mackerelio/go-osstat v0.2.5 + // NOTE: replace directives below must always be *temporary*. // // Adding a replace directive to change a module to a fork of a module will diff --git a/go.sum b/go.sum index 88917091f4..d3955cc9d0 100644 --- a/go.sum +++ b/go.sum @@ -1688,6 +1688,8 @@ github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMn github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-validate v0.0.0-20180911180927-64fcb82c878e/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2GXh+o= +github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/internal/alloycli/cmd_run.go b/internal/alloycli/cmd_run.go index 33f9798eb7..98e77da036 100644 --- a/internal/alloycli/cmd_run.go +++ b/internal/alloycli/cmd_run.go @@ -23,6 +23,7 @@ import ( "github.com/grafana/ckit/peer" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" + "github.com/spf13/pflag" "go.opentelemetry.io/otel" "golang.org/x/exp/maps" @@ -64,6 +65,7 @@ func runCommand() *cobra.Command { clusterAdvInterfaces: advertise.DefaultInterfaces, clusterMaxJoinPeers: 5, clusterRejoinInterval: 60 * time.Second, + disableSupportBundle: false, } cmd := &cobra.Command{ @@ -100,7 +102,7 @@ depending on the nature of the reload error. SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - return r.Run(args[0]) + return r.Run(cmd, args[0]) }, } @@ -111,6 +113,8 @@ depending on the nature of the reload error. cmd.Flags().StringVar(&r.uiPrefix, "server.http.ui-path-prefix", r.uiPrefix, "Prefix to serve the HTTP UI at") cmd.Flags(). BoolVar(&r.enablePprof, "server.http.enable-pprof", r.enablePprof, "Enable /debug/pprof profiling endpoints.") + cmd.Flags(). + BoolVar(&r.disableSupportBundle, "server.http.disable-support-bundle", r.disableSupportBundle, "Disable /-/support support bundle retrieval.") // Cluster flags cmd.Flags(). @@ -184,9 +188,10 @@ type alloyRun struct { configBypassConversionErrors bool configExtraArgs string enableCommunityComps bool + disableSupportBundle bool } -func (fr *alloyRun) Run(configPath string) error { +func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { var wg sync.WaitGroup defer wg.Wait() @@ -275,6 +280,15 @@ func (fr *alloyRun) Run(configPath string) error { return err } + var runtimeConfig []byte + if !fr.disableSupportBundle { + b := strings.Builder{} + cmd.Flags().VisitAll(func(f *pflag.Flag) { + b.WriteString(fmt.Sprintf("%s=%s\n", f.Name, f.Value.String())) + }) + runtimeConfig = []byte(b.String()) + } + httpService := httpservice.New(httpservice.Options{ Logger: log.With(l, "service", "http"), Tracer: t, @@ -283,9 +297,11 @@ func (fr *alloyRun) Run(configPath string) error { ReadyFunc: func() bool { return ready() }, ReloadFunc: func() (*alloy_runtime.Source, error) { return reload() }, - HTTPListenAddr: fr.httpListenAddr, - MemoryListenAddr: fr.inMemoryAddr, - EnablePProf: fr.enablePprof, + HTTPListenAddr: fr.httpListenAddr, + MemoryListenAddr: fr.inMemoryAddr, + EnablePProf: fr.enablePprof, + DisableSupportBundle: fr.disableSupportBundle, + RuntimeConfig: runtimeConfig, }) remoteCfgService, err := remotecfgservice.New(remotecfgservice.Options{ diff --git a/internal/runtime/alloy.go b/internal/runtime/alloy.go index 613fc204da..ddd6b34604 100644 --- a/internal/runtime/alloy.go +++ b/internal/runtime/alloy.go @@ -48,6 +48,7 @@ package runtime import ( "context" "fmt" + "io" "sync" "time" @@ -315,6 +316,18 @@ func (f *Runtime) LoadSource(source *Source, args map[string]any, configPath str }) } +// AddTemporaryLogger provides the ability to add a temp logger to the controller. +// This is used for the support bundle to capture logs from the controller. +func (f *Runtime) AddTemporaryLogger(w io.Writer) { + f.log.SetTemporaryWriter(w) +} + +// RemoveTemporaryLogger provides the ability to remove a temp logger added to the controller. +// This is used for the support bundle after capturing logs from the controller. +func (f *Runtime) RemoveTemporaryLogger() { + f.log.RemoveTemporaryWriter() +} + // Same as above but with a customComponentRegistry that provides custom component definitions. func (f *Runtime) loadSource(source *Source, args map[string]any, customComponentRegistry *controller.CustomComponentRegistry) error { return f.applyLoaderConfig(controller.ApplyOptions{ diff --git a/internal/runtime/logging/logger.go b/internal/runtime/logging/logger.go index 5bc2402772..1955c3f8e7 100644 --- a/internal/runtime/logging/logger.go +++ b/internal/runtime/logging/logger.go @@ -63,7 +63,6 @@ func NewDeferred(w io.Writer) (*Logger, error) { format formatVar writer writerVar ) - l := &Logger{ inner: w, @@ -104,11 +103,10 @@ func (l *Logger) Update(o Options) error { l.level.Set(slogLevel(o.Level).Level()) l.format.Set(o.Format) - newWriter := l.inner + l.writer.innerWriter = l.inner if len(o.WriteTo) > 0 { - newWriter = io.MultiWriter(l.inner, &lokiWriter{o.WriteTo}) + l.writer.lokiWriter = &lokiWriter{o.WriteTo} } - l.writer.Set(newWriter) // Build all our deferred handlers if l.deferredSlog != nil { @@ -133,6 +131,14 @@ func (l *Logger) Update(o Options) error { return nil } +func (l *Logger) SetTemporaryWriter(w io.Writer) { + l.writer.SetTemporaryWriter(w) +} + +func (l *Logger) RemoveTemporaryWriter() { + l.writer.RemoveTemporaryWriter() +} + // Log implements log.Logger. func (l *Logger) Log(kvps ...interface{}) error { // Buffer logs before confirming log format is configured in `logging` block @@ -215,24 +221,51 @@ func (f *formatVar) Set(format Format) { type writerVar struct { mut sync.RWMutex - w io.Writer + + lokiWriter *lokiWriter + innerWriter io.Writer + tmpWriter io.Writer +} + +func (w *writerVar) SetTemporaryWriter(writer io.Writer) { + w.mut.Lock() + defer w.mut.Unlock() + w.tmpWriter = writer } -func (w *writerVar) Set(inner io.Writer) { +func (w *writerVar) RemoveTemporaryWriter() { w.mut.Lock() defer w.mut.Unlock() - w.w = inner + w.tmpWriter = nil } -func (w *writerVar) Write(p []byte) (n int, err error) { +func (w *writerVar) Write(p []byte) (int, error) { w.mut.RLock() defer w.mut.RUnlock() - if w.w == nil { + if w.innerWriter == nil { return 0, fmt.Errorf("no writer available") } - return w.w.Write(p) + // The following is effectively an io.Multiwriter, but without updating + // the Multiwriter each time tmpWriter is added or removed. + if _, err := w.innerWriter.Write(p); err != nil { + return 0, err + } + + if w.lokiWriter != nil { + if _, err := w.lokiWriter.Write(p); err != nil { + return 0, err + } + } + + if w.tmpWriter != nil { + if _, err := w.tmpWriter.Write(p); err != nil { + return 0, err + } + } + + return len(p), nil } type bufferedItem struct { diff --git a/internal/service/http/http.go b/internal/service/http/http.go index ff070b330b..031578ed20 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -2,6 +2,7 @@ package http import ( + "bytes" "context" "crypto/tls" "fmt" @@ -11,8 +12,10 @@ import ( "os" "path" "sort" + "strconv" "strings" "sync" + "time" "github.com/go-kit/log" "github.com/gorilla/mux" @@ -47,9 +50,11 @@ type Options struct { ReadyFunc func() bool ReloadFunc func() (*alloy_runtime.Source, error) - HTTPListenAddr string // Address to listen for HTTP traffic on. - MemoryListenAddr string // Address to accept in-memory traffic on. - EnablePProf bool // Whether pprof endpoints should be exposed. + HTTPListenAddr string // Address to listen for HTTP traffic on. + MemoryListenAddr string // Address to accept in-memory traffic on. + EnablePProf bool // Whether pprof endpoints should be exposed. + DisableSupportBundle bool // Whether support bundle endpoint should be disabled. + RuntimeConfig []byte // Alloy runtime config to send with support bundle } // Arguments holds runtime settings for the HTTP service. @@ -66,6 +71,8 @@ type Service struct { winMut sync.Mutex win *server.WinCertStoreHandler + host service.Host + // publicLis and tcpLis are used to lazily enable TLS, since TLS is // optionally configurable at runtime. // @@ -140,6 +147,8 @@ func (s *Service) Definition() service.Definition { // Run starts the HTTP service. It will run until the provided context is // canceled or there is a fatal error. func (s *Service) Run(ctx context.Context, host service.Host) error { + s.host = host + var wg sync.WaitGroup defer wg.Wait() @@ -211,6 +220,9 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { }).Methods(http.MethodGet, http.MethodPost) } + // Wire in support bundle generator + r.HandleFunc("/-/support", s.supportHandler).Methods("GET") + // Wire custom service handlers for services which depend on the http // service. // @@ -243,6 +255,76 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { return nil } +func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { + s.winMut.Lock() + cfg := s.opts + s.winMut.Unlock() + + runtime, ok := s.host.(*alloy_runtime.Runtime) + if !ok { + level.Error(s.log).Log("msg", "failed to get runtime for support bundle", "err", "host is not a runtime") + } + + if cfg.DisableSupportBundle { + rw.WriteHeader(http.StatusForbidden) + _, _ = rw.Write([]byte("403 - support bundle generation is disabled; it can be re-enabled by removing the -disable-support-bundle flag")) + return + } + + duration := getServerWriteTimeout(r) + if r.URL.Query().Has("duration") { + d, err := strconv.Atoi(r.URL.Query().Get("duration")) + if err != nil { + http.Error(rw, fmt.Sprintf("duration value (in seconds) should be a positive integer: %s", err), http.StatusBadRequest) + return + } + if d < 1 { + http.Error(rw, "duration value (in seconds) should be larger than 1", http.StatusBadRequest) + return + } + if float64(d) > duration.Seconds() { + http.Error(rw, "duration value exceeds the server's write timeout", http.StatusBadRequest) + return + } + duration = time.Duration(d) * time.Second + } + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + s.winMut.Lock() + var ( + httpSrvAddress = cfg.HTTPListenAddr + ) + s.winMut.Unlock() + + var logsBuffer bytes.Buffer + if runtime != nil { + syncBuff := log.NewSyncWriter(&logsBuffer) + runtime.AddTemporaryLogger(syncBuff) + defer func() { + runtime.RemoveTemporaryLogger() + }() + } + + bundle, err := ExportSupportBundle(ctx, cfg.RuntimeConfig, httpSrvAddress, s.Data().(Data).DialFunc) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + if err := ServeSupportBundle(rw, bundle, &logsBuffer); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } +} + +func getServerWriteTimeout(r *http.Request) time.Duration { + srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server) + if ok && srv.WriteTimeout != 0 { + return srv.WriteTimeout + } + return 30 * time.Second +} + // getServiceRoutes returns a sorted list of service routes for services which // depend on the HTTP service. // diff --git a/internal/service/http/supportbundle.go b/internal/service/http/supportbundle.go new file mode 100644 index 0000000000..a95afef876 --- /dev/null +++ b/internal/service/http/supportbundle.go @@ -0,0 +1,210 @@ +package http + +import ( + "archive/zip" + "bytes" + "context" + "fmt" + "io" + "net/http" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "sync" + "time" + + "github.com/grafana/alloy/internal/build" + "github.com/grafana/alloy/internal/static/server" + "github.com/mackerelio/go-osstat/uptime" + "gopkg.in/yaml.v3" +) + +// Bundle collects all the data that is exposed as a support bundle. +type Bundle struct { + meta []byte + alloyMetrics []byte + components []byte + peers []byte + runtimeConfig []byte + heapBuf *bytes.Buffer + goroutineBuf *bytes.Buffer + blockBuf *bytes.Buffer + mutexBuf *bytes.Buffer + cpuBuf *bytes.Buffer +} + +// Metadata contains general runtime information about the current Alloy environment. +type Metadata struct { + BuildVersion string `yaml:"build_version"` + OS string `yaml:"os"` + Architecture string `yaml:"architecture"` + Uptime float64 `yaml:"uptime"` +} + +// Used to enforce single-flight requests to Export +var mut sync.Mutex + +// ExportSupportBundle gathers the information required for the support bundle. +func ExportSupportBundle(ctx context.Context, runtimeConfig []byte, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) { + mut.Lock() + defer mut.Unlock() + // The block profiler is disabled by default. Temporarily enable recording + // of all blocking events. Also, temporarily record all mutex contentions, + // and defer restoring of earlier mutex profiling fraction. + runtime.SetBlockProfileRate(1) + old := runtime.SetMutexProfileFraction(1) + defer func() { + runtime.SetBlockProfileRate(0) + runtime.SetMutexProfileFraction(old) + }() + + // Gather runtime metadata. + ut, err := uptime.Get() + if err != nil { + return nil, err + } + m := Metadata{ + BuildVersion: build.Version, + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + Uptime: ut.Seconds(), + } + meta, err := yaml.Marshal(m) + if err != nil { + return nil, fmt.Errorf("failed to marshal support bundle metadata: %s", err) + } + + var httpClient http.Client + httpClient.Transport = &http.Transport{DialContext: dialContext} + // Gather Alloy's own metrics. + alloyMetrics, err := retrieveAPIEndpoint(httpClient, srvAddress, "metrics") + if err != nil { + return nil, fmt.Errorf("failed to get internal Alloy metrics: %s", err) + } + // Gather running component configuration + components, err := retrieveAPIEndpoint(httpClient, srvAddress, "api/v0/web/components") + if err != nil { + return nil, fmt.Errorf("failed to get component details: %s", err) + } + // Gather cluster peers information + peers, err := retrieveAPIEndpoint(httpClient, srvAddress, "api/v0/web/peers") + if err != nil { + return nil, fmt.Errorf("failed to get peer details: %s", err) + } + + // Export pprof data. + var ( + cpuBuf bytes.Buffer + heapBuf bytes.Buffer + goroutineBuf bytes.Buffer + blockBuf bytes.Buffer + mutexBuf bytes.Buffer + ) + err = pprof.StartCPUProfile(&cpuBuf) + if err != nil { + return nil, err + } + deadline, _ := ctx.Deadline() + // Sleep for the remaining of the context deadline, but leave some time for + // the rest of the bundle to be exported successfully. + time.Sleep(time.Until(deadline) - 200*time.Millisecond) + pprof.StopCPUProfile() + + p := pprof.Lookup("heap") + if err := p.WriteTo(&heapBuf, 0); err != nil { + return nil, err + } + p = pprof.Lookup("goroutine") + if err := p.WriteTo(&goroutineBuf, 0); err != nil { + return nil, err + } + p = pprof.Lookup("block") + if err := p.WriteTo(&blockBuf, 0); err != nil { + return nil, err + } + p = pprof.Lookup("mutex") + if err := p.WriteTo(&mutexBuf, 0); err != nil { + return nil, err + } + + // Finally, bundle everything up to be served, either as a zip from + // memory, or exported to a directory. + bundle := &Bundle{ + meta: meta, + alloyMetrics: alloyMetrics, + components: components, + peers: peers, + runtimeConfig: runtimeConfig, + heapBuf: &heapBuf, + goroutineBuf: &goroutineBuf, + blockBuf: &blockBuf, + mutexBuf: &mutexBuf, + cpuBuf: &cpuBuf, + } + + return bundle, nil +} + +func retrieveAPIEndpoint(httpClient http.Client, srvAddress, endpoint string) ([]byte, error) { + url := fmt.Sprintf("http://%s/%s", srvAddress, endpoint) + resp, err := httpClient.Get(url) + if err != nil { + return nil, err + } + res, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return res, nil +} + +// ServeSupportBundle the collected data and logs as a zip file over the given +// http.ResponseWriter. +func ServeSupportBundle(rw http.ResponseWriter, b *Bundle, logsBuf *bytes.Buffer) error { + zw := zip.NewWriter(rw) + rw.Header().Set("Content-Type", "application/zip") + rw.Header().Set("Content-Disposition", "attachment; filename=\"alloy-support-bundle.zip\"") + + zipStructure := map[string][]byte{ + "alloy-metadata.yaml": b.meta, + "alloy-components.json": b.components, + "alloy-peers.json": b.peers, + "alloy-metrics.txt": b.alloyMetrics, + "alloy-runtime-config.txt": b.runtimeConfig, + "alloy-logs.txt": logsBuf.Bytes(), + "pprof/cpu.pprof": b.cpuBuf.Bytes(), + "pprof/heap.pprof": b.heapBuf.Bytes(), + "pprof/goroutine.pprof": b.goroutineBuf.Bytes(), + "pprof/mutex.pprof": b.mutexBuf.Bytes(), + "pprof/block.pprof": b.blockBuf.Bytes(), + } + + for fn, b := range zipStructure { + if b != nil { + path := append([]string{"alloy-support-bundle"}, strings.Split(fn, "/")...) + if err := writeByteSlice(zw, b, path...); err != nil { + return err + } + } + } + + err := zw.Close() + if err != nil { + return fmt.Errorf("failed to flush the zip writer: %v", err) + } + return nil +} + +func writeByteSlice(zw *zip.Writer, b []byte, fn ...string) error { + f, err := zw.Create(filepath.Join(fn...)) + if err != nil { + return err + } + _, err = f.Write(b) + if err != nil { + return err + } + return nil +} From be6e95b432157e2cf07928782d183f2019d0b211 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 31 Oct 2024 00:20:20 -0400 Subject: [PATCH 02/26] Add documentation for support bundle --- docs/sources/reference/cli/run.md | 2 + docs/sources/troubleshoot/support_bundle.md | 46 +++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 docs/sources/troubleshoot/support_bundle.md diff --git a/docs/sources/reference/cli/run.md b/docs/sources/reference/cli/run.md index f246bb894e..c154696260 100644 --- a/docs/sources/reference/cli/run.md +++ b/docs/sources/reference/cli/run.md @@ -42,6 +42,7 @@ The following flags are supported: * `--server.http.ui-path-prefix`: Base path where the UI is exposed (default `/`). * `--storage.path`: Base directory where components can store data (default `data-alloy/`). * `--disable-reporting`: Disable [data collection][] (default `false`). +* `--disable-support-bundle`: Disable [support bundle][] endpoint (default `false`). * `--cluster.enabled`: Start {{< param "PRODUCT_NAME" >}} in clustered mode (default `false`). * `--cluster.node-name`: The name to use for this node (defaults to the environment's hostname). * `--cluster.join-addresses`: Comma-separated list of addresses to join the cluster at (default `""`). Mutually exclusive with `--cluster.discover-peers`. @@ -178,6 +179,7 @@ Refer to [alloy convert][] for more details on how `extra-args` work. [go-discover]: https://github.com/hashicorp/go-discover [in-memory HTTP traffic]: ../../../get-started/component_controller/#in-memory-traffic [data collection]: ../../../data-collection/ +[support bundle]: ../../../troubleshoot/support_bundle [components]: ../../get-started/components/ [component controller]: ../../../get-started/component_controller/ [UI]: ../../../troubleshoot/debug/#clustering-page diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md new file mode 100644 index 0000000000..a2fc05ce86 --- /dev/null +++ b/docs/sources/troubleshoot/support_bundle.md @@ -0,0 +1,46 @@ +--- +canonical: https://grafana.com/docs/alloy/latest/troubleshoot/support_bundle/ +description: Learn how to generate a support bundle +title: Generate a Support Bundle +menuTitle: Generate Support Bundle +weight: 300 +--- + +# Generate Support Bundle + +The `/-/support?duration=N` endpoint returns a 'support bundle', a zip file that contains information +about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a baseline of information when trying +to debug an issue. + +{{< admonition type="note" >}} +By default this endpoint is enabled, but may be disabled using the `--disable-support-bundle` runtime flag. +{{< /admonition >}} + +The duration parameter is optional, must be less than or equal to the +configured HTTP server write timeout, and if not provided, defaults to it. +The endpoint is only exposed to {{< param "PRODUCT_NAME" >}}'s HTTP server listen address, which +defaults to `localhost:12345`. + +The support bundle contains all information in plain text, so that it can be +inspected before sharing, to verify that no sensitive information has leaked. + +In addition, you can inspect the [supportbundle implementation](https://github.com/grafana/alloy/tree/internal/service/http/supportbundle.go) +to verify the code that is being used to generate these bundles. + +A support bundle contains the following data: +* `alloy-components.json` contains information about the [components][components] running on this {{< param "PRODUCT_NAME" >}} instance, generated by the +`/api/v0/web/components` endpoint. +* `alloy-logs.txt` contains the logs during the bundle generation. +* `alloy-metadata.yaml` contains the {{< param "PRODUCT_NAME" >}} build version and the installation's operating system, architecture, and uptime. +* `alloy-metrics.txt` contains a snapshot of {{< param "PRODUCT_NAME" >}}'s internal metrics. +* `alloy-peers.json` contains information about the identified cluster peers of this {{< param "PRODUCT_NAME" >}} instance, generated by the +`/api/v0/web/peers` endpoint. +* `alloy-runtime-config.txt` contains the values of the runtime flags available in {{< param "PRODUCT_NAME" >}}. +* The `pprof/` directory contains Go runtime profiling data (CPU, heap, goroutine, mutex, block profiles) as exported by the pprof package. See the [profile][profile] + documentation for more details on how to use this information. + +[profile]: ../profile +[components]: ../../get-started/components/ + + +[alloy-repo]: https://github.com/grafana/alloy/issues \ No newline at end of file From 479291e3881e3bd2b38937b3ca5bedec4415954a Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 31 Oct 2024 00:21:31 -0400 Subject: [PATCH 03/26] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ee417f07f..4b55f93a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Main (unreleased) ### Features +- Add support bundle generation via the API endpoint /-/support (@dehaansa) + - Add the function `path_join` to the stdlib. (@wildum) - Add `pyroscope.receive_http` component to receive and forward Pyroscope profiles (@marcsanmi) From 8576be3d37617ad73a9bb59cf6012680a057f270 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Mon, 4 Nov 2024 09:34:43 -0500 Subject: [PATCH 04/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index a2fc05ce86..0bb77275ca 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -25,7 +25,7 @@ The support bundle contains all information in plain text, so that it can be inspected before sharing, to verify that no sensitive information has leaked. In addition, you can inspect the [supportbundle implementation](https://github.com/grafana/alloy/tree/internal/service/http/supportbundle.go) -to verify the code that is being used to generate these bundles. +to verify the code used to generate these bundles. A support bundle contains the following data: * `alloy-components.json` contains information about the [components][components] running on this {{< param "PRODUCT_NAME" >}} instance, generated by the From 402b47763fc244228adb73bea3253d8a0a61979b Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Mon, 4 Nov 2024 09:34:49 -0500 Subject: [PATCH 05/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 0bb77275ca..6c5357d301 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -18,7 +18,7 @@ By default this endpoint is enabled, but may be disabled using the `--disable-su The duration parameter is optional, must be less than or equal to the configured HTTP server write timeout, and if not provided, defaults to it. -The endpoint is only exposed to {{< param "PRODUCT_NAME" >}}'s HTTP server listen address, which +The endpoint is only exposed to the {{< param "PRODUCT_NAME" >}} HTTP server listen address, which defaults to `localhost:12345`. The support bundle contains all information in plain text, so that it can be From f22f663b53a95fcbf4f6766b9f629b72dd5e5253 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Mon, 4 Nov 2024 09:34:56 -0500 Subject: [PATCH 06/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 6c5357d301..7720ac5ca5 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -13,7 +13,7 @@ about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a base to debug an issue. {{< admonition type="note" >}} -By default this endpoint is enabled, but may be disabled using the `--disable-support-bundle` runtime flag. +This endpoint is enabled by default, but may be disabled using the `--disable-support-bundle` runtime flag. {{< /admonition >}} The duration parameter is optional, must be less than or equal to the From b95a24835d946a63b2069a546f1073462662ef08 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Mon, 4 Nov 2024 09:35:02 -0500 Subject: [PATCH 07/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 7720ac5ca5..4e127bb3e7 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -21,8 +21,8 @@ configured HTTP server write timeout, and if not provided, defaults to it. The endpoint is only exposed to the {{< param "PRODUCT_NAME" >}} HTTP server listen address, which defaults to `localhost:12345`. -The support bundle contains all information in plain text, so that it can be -inspected before sharing, to verify that no sensitive information has leaked. +The support bundle contains all information in plain text, so you can +inspect it before sharing to verify that no sensitive information has leaked. In addition, you can inspect the [supportbundle implementation](https://github.com/grafana/alloy/tree/internal/service/http/supportbundle.go) to verify the code used to generate these bundles. From c04efb3aa3568646de43cd2b1fdfec104ffae673 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 5 Nov 2024 14:24:08 -0500 Subject: [PATCH 08/26] Initial PR feedback --- docs/sources/troubleshoot/support_bundle.md | 4 +- .../prometheus/write/queue/e2e_test.go | 4 +- internal/service/http/http.go | 10 +-- internal/service/http/supportbundle.go | 64 +++++++++---------- 4 files changed, 37 insertions(+), 45 deletions(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index a2fc05ce86..dc296d26c2 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -35,12 +35,10 @@ A support bundle contains the following data: * `alloy-metrics.txt` contains a snapshot of {{< param "PRODUCT_NAME" >}}'s internal metrics. * `alloy-peers.json` contains information about the identified cluster peers of this {{< param "PRODUCT_NAME" >}} instance, generated by the `/api/v0/web/peers` endpoint. -* `alloy-runtime-config.txt` contains the values of the runtime flags available in {{< param "PRODUCT_NAME" >}}. +* `alloy-runtime-flags.txt` contains the values of the runtime flags available in {{< param "PRODUCT_NAME" >}}. * The `pprof/` directory contains Go runtime profiling data (CPU, heap, goroutine, mutex, block profiles) as exported by the pprof package. See the [profile][profile] documentation for more details on how to use this information. [profile]: ../profile [components]: ../../get-started/components/ - - [alloy-repo]: https://github.com/grafana/alloy/issues \ No newline at end of file diff --git a/internal/component/prometheus/write/queue/e2e_test.go b/internal/component/prometheus/write/queue/e2e_test.go index 5593a6cbdc..f0137e94ea 100644 --- a/internal/component/prometheus/write/queue/e2e_test.go +++ b/internal/component/prometheus/write/queue/e2e_test.go @@ -126,8 +126,8 @@ func TestE2E(t *testing.T) { } const ( - iterations = 10 - items = 100 + iterations = 1 + items = 1000 ) func runTest(t *testing.T, add func(index int, appendable storage.Appender) (float64, labels.Labels), test func(samples *safeSlice[prompb.TimeSeries]), metaTest func(meta *safeSlice[prompb.MetricMetadata])) { diff --git a/internal/service/http/http.go b/internal/service/http/http.go index 031578ed20..ab6b9ef2b9 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -54,7 +54,7 @@ type Options struct { MemoryListenAddr string // Address to accept in-memory traffic on. EnablePProf bool // Whether pprof endpoints should be exposed. DisableSupportBundle bool // Whether support bundle endpoint should be disabled. - RuntimeConfig []byte // Alloy runtime config to send with support bundle + RuntimeFlags []byte // Alloy runtime flags to send with support bundle } // Arguments holds runtime settings for the HTTP service. @@ -291,12 +291,6 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(context.Background(), duration) defer cancel() - s.winMut.Lock() - var ( - httpSrvAddress = cfg.HTTPListenAddr - ) - s.winMut.Unlock() - var logsBuffer bytes.Buffer if runtime != nil { syncBuff := log.NewSyncWriter(&logsBuffer) @@ -306,7 +300,7 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { }() } - bundle, err := ExportSupportBundle(ctx, cfg.RuntimeConfig, httpSrvAddress, s.Data().(Data).DialFunc) + bundle, err := ExportSupportBundle(ctx, cfg.RuntimeFlags, cfg.HTTPListenAddr, s.Data().(Data).DialFunc) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return diff --git a/internal/service/http/supportbundle.go b/internal/service/http/supportbundle.go index a95afef876..e82166ff94 100644 --- a/internal/service/http/supportbundle.go +++ b/internal/service/http/supportbundle.go @@ -22,16 +22,16 @@ import ( // Bundle collects all the data that is exposed as a support bundle. type Bundle struct { - meta []byte - alloyMetrics []byte - components []byte - peers []byte - runtimeConfig []byte - heapBuf *bytes.Buffer - goroutineBuf *bytes.Buffer - blockBuf *bytes.Buffer - mutexBuf *bytes.Buffer - cpuBuf *bytes.Buffer + meta []byte + alloyMetrics []byte + components []byte + peers []byte + runtimeFlags []byte + heapBuf *bytes.Buffer + goroutineBuf *bytes.Buffer + blockBuf *bytes.Buffer + mutexBuf *bytes.Buffer + cpuBuf *bytes.Buffer } // Metadata contains general runtime information about the current Alloy environment. @@ -46,7 +46,7 @@ type Metadata struct { var mut sync.Mutex // ExportSupportBundle gathers the information required for the support bundle. -func ExportSupportBundle(ctx context.Context, runtimeConfig []byte, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) { +func ExportSupportBundle(ctx context.Context, runtimeFlags []byte, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) { mut.Lock() defer mut.Unlock() // The block profiler is disabled by default. Temporarily enable recording @@ -131,16 +131,16 @@ func ExportSupportBundle(ctx context.Context, runtimeConfig []byte, srvAddress s // Finally, bundle everything up to be served, either as a zip from // memory, or exported to a directory. bundle := &Bundle{ - meta: meta, - alloyMetrics: alloyMetrics, - components: components, - peers: peers, - runtimeConfig: runtimeConfig, - heapBuf: &heapBuf, - goroutineBuf: &goroutineBuf, - blockBuf: &blockBuf, - mutexBuf: &mutexBuf, - cpuBuf: &cpuBuf, + meta: meta, + alloyMetrics: alloyMetrics, + components: components, + peers: peers, + runtimeFlags: runtimeFlags, + heapBuf: &heapBuf, + goroutineBuf: &goroutineBuf, + blockBuf: &blockBuf, + mutexBuf: &mutexBuf, + cpuBuf: &cpuBuf, } return bundle, nil @@ -168,17 +168,17 @@ func ServeSupportBundle(rw http.ResponseWriter, b *Bundle, logsBuf *bytes.Buffer rw.Header().Set("Content-Disposition", "attachment; filename=\"alloy-support-bundle.zip\"") zipStructure := map[string][]byte{ - "alloy-metadata.yaml": b.meta, - "alloy-components.json": b.components, - "alloy-peers.json": b.peers, - "alloy-metrics.txt": b.alloyMetrics, - "alloy-runtime-config.txt": b.runtimeConfig, - "alloy-logs.txt": logsBuf.Bytes(), - "pprof/cpu.pprof": b.cpuBuf.Bytes(), - "pprof/heap.pprof": b.heapBuf.Bytes(), - "pprof/goroutine.pprof": b.goroutineBuf.Bytes(), - "pprof/mutex.pprof": b.mutexBuf.Bytes(), - "pprof/block.pprof": b.blockBuf.Bytes(), + "alloy-metadata.yaml": b.meta, + "alloy-components.json": b.components, + "alloy-peers.json": b.peers, + "alloy-metrics.txt": b.alloyMetrics, + "alloy-runtime-flags.txt": b.runtimeFlags, + "alloy-logs.txt": logsBuf.Bytes(), + "pprof/cpu.pprof": b.cpuBuf.Bytes(), + "pprof/heap.pprof": b.heapBuf.Bytes(), + "pprof/goroutine.pprof": b.goroutineBuf.Bytes(), + "pprof/mutex.pprof": b.mutexBuf.Bytes(), + "pprof/block.pprof": b.blockBuf.Bytes(), } for fn, b := range zipStructure { From e4d8debc0503a12c40f0397c5535f908d1397fdc Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 5 Nov 2024 16:23:28 -0500 Subject: [PATCH 09/26] Rewrite http service to use logging library internal to alloy --- internal/alloycli/cmd_run.go | 8 ++--- internal/runtime/alloy.go | 13 -------- internal/runtime/logging/logger.go | 6 ++++ internal/service/http/http.go | 48 ++++++++++++++++-------------- internal/service/http/http_test.go | 2 +- 5 files changed, 36 insertions(+), 41 deletions(-) diff --git a/internal/alloycli/cmd_run.go b/internal/alloycli/cmd_run.go index 98e77da036..704ef303f3 100644 --- a/internal/alloycli/cmd_run.go +++ b/internal/alloycli/cmd_run.go @@ -280,17 +280,17 @@ func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { return err } - var runtimeConfig []byte + var runtimeFlags []byte if !fr.disableSupportBundle { b := strings.Builder{} cmd.Flags().VisitAll(func(f *pflag.Flag) { b.WriteString(fmt.Sprintf("%s=%s\n", f.Name, f.Value.String())) }) - runtimeConfig = []byte(b.String()) + runtimeFlags = []byte(b.String()) } httpService := httpservice.New(httpservice.Options{ - Logger: log.With(l, "service", "http"), + Logger: l, Tracer: t, Gatherer: prometheus.DefaultGatherer, @@ -301,7 +301,7 @@ func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { MemoryListenAddr: fr.inMemoryAddr, EnablePProf: fr.enablePprof, DisableSupportBundle: fr.disableSupportBundle, - RuntimeConfig: runtimeConfig, + RuntimeFlags: runtimeFlags, }) remoteCfgService, err := remotecfgservice.New(remotecfgservice.Options{ diff --git a/internal/runtime/alloy.go b/internal/runtime/alloy.go index ddd6b34604..613fc204da 100644 --- a/internal/runtime/alloy.go +++ b/internal/runtime/alloy.go @@ -48,7 +48,6 @@ package runtime import ( "context" "fmt" - "io" "sync" "time" @@ -316,18 +315,6 @@ func (f *Runtime) LoadSource(source *Source, args map[string]any, configPath str }) } -// AddTemporaryLogger provides the ability to add a temp logger to the controller. -// This is used for the support bundle to capture logs from the controller. -func (f *Runtime) AddTemporaryLogger(w io.Writer) { - f.log.SetTemporaryWriter(w) -} - -// RemoveTemporaryLogger provides the ability to remove a temp logger added to the controller. -// This is used for the support bundle after capturing logs from the controller. -func (f *Runtime) RemoveTemporaryLogger() { - f.log.RemoveTemporaryWriter() -} - // Same as above but with a customComponentRegistry that provides custom component definitions. func (f *Runtime) loadSource(source *Source, args map[string]any, customComponentRegistry *controller.CustomComponentRegistry) error { return f.applyLoaderConfig(controller.ApplyOptions{ diff --git a/internal/runtime/logging/logger.go b/internal/runtime/logging/logger.go index 1955c3f8e7..3161c66798 100644 --- a/internal/runtime/logging/logger.go +++ b/internal/runtime/logging/logger.go @@ -55,6 +55,12 @@ func New(w io.Writer, o Options) (*Logger, error) { return l, nil } +// New creates a New nop logger +func NewNop() *Logger { + l, _ := NewDeferred(io.Discard) + return l +} + // NewDeferred creates a new logger with the default log level and format. // The logger is not updated during initialization. func NewDeferred(w io.Writer) (*Logger, error) { diff --git a/internal/service/http/http.go b/internal/service/http/http.go index ab6b9ef2b9..5d0f6a9c67 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/alloy/internal/component" "github.com/grafana/alloy/internal/featuregate" alloy_runtime "github.com/grafana/alloy/internal/runtime" + "github.com/grafana/alloy/internal/runtime/logging" "github.com/grafana/alloy/internal/runtime/logging/level" "github.com/grafana/alloy/internal/service" "github.com/grafana/alloy/internal/service/remotecfg" @@ -43,7 +44,7 @@ const ServiceName = "http" // Options are used to configure the HTTP service. Options are constant for the // lifetime of the HTTP service. type Options struct { - Logger log.Logger // Where to send logs. + Logger *logging.Logger // Where to send logs. Tracer trace.TracerProvider // Where to send traces. Gatherer prometheus.Gatherer // Where to collect metrics from. @@ -63,7 +64,7 @@ type Arguments struct { } type Service struct { - log log.Logger + log *logging.Logger tracer trace.TracerProvider gatherer prometheus.Gatherer opts Options @@ -102,7 +103,7 @@ func New(opts Options) *Service { ) if l == nil { - l = log.NewNopLogger() + l = logging.NewNop() } if t == nil { t = noop.NewTracerProvider() @@ -165,7 +166,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { netLis, err := net.Listen("tcp", s.opts.HTTPListenAddr) if err != nil { // There is no recovering from failing to listen on the port. - level.Error(s.log).Log("msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) + internalLog(level.Error(s.log), "msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) os.Exit(1) } if err := s.tcpLis.SetInner(netLis); err != nil { @@ -206,16 +207,16 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { if s.opts.ReloadFunc != nil { r.HandleFunc("/-/reload", func(w http.ResponseWriter, _ *http.Request) { - level.Info(s.log).Log("msg", "reload requested via /-/reload endpoint") + internalLog(level.Info(s.log), "msg", "reload requested via /-/reload endpoint") _, err := s.opts.ReloadFunc() if err != nil { - level.Error(s.log).Log("msg", "failed to reload config", "err", err.Error()) + internalLog(level.Error(s.log), "msg", "failed to reload config", "err", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } - level.Info(s.log).Log("msg", "config reloaded") + internalLog(level.Info(s.log), "msg", "config reloaded") _, _ = fmt.Fprintln(w, "config reloaded") }).Methods(http.MethodGet, http.MethodPost) } @@ -234,7 +235,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { srv := &http.Server{Handler: h2c.NewHandler(r, &http2.Server{})} - level.Info(s.log).Log("msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) + internalLog(level.Info(s.log), "msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) listeners := []net.Listener{s.publicLis, s.memLis} for _, lis := range listeners { @@ -244,7 +245,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { defer cancel() if err := srv.Serve(lis); err != nil { - level.Info(s.log).Log("msg", "http server closed", "addr", lis.Addr(), "err", err) + internalLog(level.Info(s.log), "msg", "http server closed", "addr", lis.Addr(), "err", err) } }(lis) } @@ -260,11 +261,6 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { cfg := s.opts s.winMut.Unlock() - runtime, ok := s.host.(*alloy_runtime.Runtime) - if !ok { - level.Error(s.log).Log("msg", "failed to get runtime for support bundle", "err", "host is not a runtime") - } - if cfg.DisableSupportBundle { rw.WriteHeader(http.StatusForbidden) _, _ = rw.Write([]byte("403 - support bundle generation is disabled; it can be re-enabled by removing the -disable-support-bundle flag")) @@ -292,13 +288,11 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { defer cancel() var logsBuffer bytes.Buffer - if runtime != nil { - syncBuff := log.NewSyncWriter(&logsBuffer) - runtime.AddTemporaryLogger(syncBuff) - defer func() { - runtime.RemoveTemporaryLogger() - }() - } + syncBuff := log.NewSyncWriter(&logsBuffer) + s.log.SetTemporaryWriter(syncBuff) + defer func() { + s.log.RemoveTemporaryWriter() + }() bundle, err := ExportSupportBundle(ctx, cfg.RuntimeFlags, cfg.HTTPListenAddr, s.Data().(Data).DialFunc) if err != nil { @@ -411,14 +405,14 @@ func (s *Service) Update(newConfig any) error { } newTLSListener := tls.NewListener(s.tcpLis, tlsConfig) - level.Info(s.log).Log("msg", "applying TLS config to HTTP server") + internalLog(level.Info(s.log), "msg", "applying TLS config to HTTP server") if err := s.publicLis.SetInner(newTLSListener); err != nil { return err } } else { // Ensure that the outer lazy listener is sending requests directly to the // network, instead of any previous instance of a TLS listener. - level.Info(s.log).Log("msg", "applying non-TLS config to HTTP server") + internalLog(level.Info(s.log), "msg", "applying non-TLS config to HTTP server") if err := s.publicLis.SetInner(s.tcpLis); err != nil { return err } @@ -448,6 +442,14 @@ func (s *Service) Data() any { } } +// The http service uses an internal logger function instead of log.With +// as it needs to maintain a reference to the internal logging library +// for appending a temporary logger for support bundle generation +func internalLog(l log.Logger, keyVals ...interface{}) error { + newKv := append(keyVals, "service", "http") + return l.Log(newKv) +} + // Data includes information associated with the HTTP service. type Data struct { // Address that the HTTP service is configured to listen on. diff --git a/internal/service/http/http_test.go b/internal/service/http/http_test.go index bfc212471d..2481fcd6cb 100644 --- a/internal/service/http/http_test.go +++ b/internal/service/http/http_test.go @@ -169,7 +169,7 @@ func newTestEnvironment(t *testing.T) (*testEnvironment, error) { } svc := New(Options{ - Logger: util.TestLogger(t), + Logger: util.TestAlloyLogger(t), Tracer: noop.NewTracerProvider(), Gatherer: prometheus.NewRegistry(), From 657bd0444d74a319fc48f49462b7e28c3d6bd1f6 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 5 Nov 2024 16:26:52 -0500 Subject: [PATCH 10/26] Revert accidental commit of e2e test changes --- internal/component/prometheus/write/queue/e2e_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/component/prometheus/write/queue/e2e_test.go b/internal/component/prometheus/write/queue/e2e_test.go index f0137e94ea..5593a6cbdc 100644 --- a/internal/component/prometheus/write/queue/e2e_test.go +++ b/internal/component/prometheus/write/queue/e2e_test.go @@ -126,8 +126,8 @@ func TestE2E(t *testing.T) { } const ( - iterations = 1 - items = 1000 + iterations = 10 + items = 100 ) func runTest(t *testing.T, add func(index int, appendable storage.Appender) (float64, labels.Labels), test func(samples *safeSlice[prompb.TimeSeries]), metaTest func(meta *safeSlice[prompb.MetricMetadata])) { From 7307d1684eb6f4097ce5a51ef67a278185cf3a60 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 5 Nov 2024 16:27:44 -0500 Subject: [PATCH 11/26] Fix comment on exported function --- internal/runtime/logging/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/runtime/logging/logger.go b/internal/runtime/logging/logger.go index 3161c66798..843aec4c77 100644 --- a/internal/runtime/logging/logger.go +++ b/internal/runtime/logging/logger.go @@ -55,7 +55,7 @@ func New(w io.Writer, o Options) (*Logger, error) { return l, nil } -// New creates a New nop logger +// NewNop returns a logger that does nothing func NewNop() *Logger { l, _ := NewDeferred(io.Discard) return l From 3740c2712464df855ed5ea24e8c34634c215bc76 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Tue, 5 Nov 2024 16:31:04 -0500 Subject: [PATCH 12/26] Clean up added host variable that is no longer used --- internal/service/http/http.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/service/http/http.go b/internal/service/http/http.go index 5d0f6a9c67..14a0e08109 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -72,8 +72,6 @@ type Service struct { winMut sync.Mutex win *server.WinCertStoreHandler - host service.Host - // publicLis and tcpLis are used to lazily enable TLS, since TLS is // optionally configurable at runtime. // @@ -148,8 +146,6 @@ func (s *Service) Definition() service.Definition { // Run starts the HTTP service. It will run until the provided context is // canceled or there is a fatal error. func (s *Service) Run(ctx context.Context, host service.Host) error { - s.host = host - var wg sync.WaitGroup defer wg.Wait() From ff6412f9c8df9fca599973ca7211fcdb974cefab Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Wed, 6 Nov 2024 10:23:12 -0500 Subject: [PATCH 13/26] Refactor usage of logger in http service --- internal/service/http/http.go | 44 ++++++++++++++++------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/internal/service/http/http.go b/internal/service/http/http.go index 14a0e08109..e91211c088 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -64,10 +64,13 @@ type Arguments struct { } type Service struct { - log *logging.Logger - tracer trace.TracerProvider - gatherer prometheus.Gatherer - opts Options + log *logging.Logger + // internalLog allows us to leverage log.With for logging and leverage the + // logging struct for setting a temporary logger for support bundle usage + internalLog log.Logger + tracer trace.TracerProvider + gatherer prometheus.Gatherer + opts Options winMut sync.Mutex win *server.WinCertStoreHandler @@ -119,10 +122,11 @@ func New(opts Options) *Service { _ = publicLis.SetInner(tcpLis) return &Service{ - log: l, - tracer: t, - gatherer: r, - opts: opts, + log: l, + internalLog: log.With(l, "service", "http"), + tracer: t, + gatherer: r, + opts: opts, publicLis: publicLis, tcpLis: tcpLis, @@ -162,7 +166,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { netLis, err := net.Listen("tcp", s.opts.HTTPListenAddr) if err != nil { // There is no recovering from failing to listen on the port. - internalLog(level.Error(s.log), "msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) + level.Error(s.internalLog).Log("msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) os.Exit(1) } if err := s.tcpLis.SetInner(netLis); err != nil { @@ -203,16 +207,16 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { if s.opts.ReloadFunc != nil { r.HandleFunc("/-/reload", func(w http.ResponseWriter, _ *http.Request) { - internalLog(level.Info(s.log), "msg", "reload requested via /-/reload endpoint") + level.Info(s.internalLog).Log("msg", "reload requested via /-/reload endpoint") _, err := s.opts.ReloadFunc() if err != nil { - internalLog(level.Error(s.log), "msg", "failed to reload config", "err", err.Error()) + level.Error(s.internalLog).Log("msg", "failed to reload config", "err", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } - internalLog(level.Info(s.log), "msg", "config reloaded") + level.Info(s.internalLog).Log("msg", "config reloaded") _, _ = fmt.Fprintln(w, "config reloaded") }).Methods(http.MethodGet, http.MethodPost) } @@ -231,7 +235,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { srv := &http.Server{Handler: h2c.NewHandler(r, &http2.Server{})} - internalLog(level.Info(s.log), "msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) + level.Info(s.internalLog).Log("msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) listeners := []net.Listener{s.publicLis, s.memLis} for _, lis := range listeners { @@ -241,7 +245,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { defer cancel() if err := srv.Serve(lis); err != nil { - internalLog(level.Info(s.log), "msg", "http server closed", "addr", lis.Addr(), "err", err) + level.Info(s.internalLog).Log("msg", "http server closed", "addr", lis.Addr(), "err", err) } }(lis) } @@ -401,14 +405,14 @@ func (s *Service) Update(newConfig any) error { } newTLSListener := tls.NewListener(s.tcpLis, tlsConfig) - internalLog(level.Info(s.log), "msg", "applying TLS config to HTTP server") + level.Info(s.internalLog).Log("msg", "applying TLS config to HTTP server") if err := s.publicLis.SetInner(newTLSListener); err != nil { return err } } else { // Ensure that the outer lazy listener is sending requests directly to the // network, instead of any previous instance of a TLS listener. - internalLog(level.Info(s.log), "msg", "applying non-TLS config to HTTP server") + level.Info(s.internalLog).Log("msg", "applying non-TLS config to HTTP server") if err := s.publicLis.SetInner(s.tcpLis); err != nil { return err } @@ -438,14 +442,6 @@ func (s *Service) Data() any { } } -// The http service uses an internal logger function instead of log.With -// as it needs to maintain a reference to the internal logging library -// for appending a temporary logger for support bundle generation -func internalLog(l log.Logger, keyVals ...interface{}) error { - newKv := append(keyVals, "service", "http") - return l.Log(newKv) -} - // Data includes information associated with the HTTP service. type Data struct { // Address that the HTTP service is configured to listen on. From e8a30d5fc8caf9ad17dccc5e8b7f26794f1d8017 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 11:18:38 -0500 Subject: [PATCH 14/26] Update internal/service/http/http.go Co-authored-by: Piotr <17101802+thampiotr@users.noreply.github.com> --- internal/service/http/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/http/http.go b/internal/service/http/http.go index e91211c088..d7e0ba1699 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -263,7 +263,7 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { if cfg.DisableSupportBundle { rw.WriteHeader(http.StatusForbidden) - _, _ = rw.Write([]byte("403 - support bundle generation is disabled; it can be re-enabled by removing the -disable-support-bundle flag")) + _, _ = rw.Write([]byte("support bundle generation is disabled; it can be re-enabled by removing the --disable-support-bundle flag")) return } From a1cd13af6b8b218cb233af3ec0b6822ef6610cad Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 14:14:11 -0500 Subject: [PATCH 15/26] implement PR feedback --- internal/alloycli/cmd_run.go | 18 ++++---- internal/service/http/http.go | 64 +++++++++++++------------- internal/service/http/supportbundle.go | 17 +++---- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/internal/alloycli/cmd_run.go b/internal/alloycli/cmd_run.go index 704ef303f3..a78aa4ad9b 100644 --- a/internal/alloycli/cmd_run.go +++ b/internal/alloycli/cmd_run.go @@ -280,13 +280,11 @@ func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { return err } - var runtimeFlags []byte + runtimeFlags := []string{} if !fr.disableSupportBundle { - b := strings.Builder{} cmd.Flags().VisitAll(func(f *pflag.Flag) { - b.WriteString(fmt.Sprintf("%s=%s\n", f.Name, f.Value.String())) + runtimeFlags = append(runtimeFlags, fmt.Sprintf("%s=%s", f.Name, f.Value.String())) }) - runtimeFlags = []byte(b.String()) } httpService := httpservice.New(httpservice.Options{ @@ -297,11 +295,13 @@ func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { ReadyFunc: func() bool { return ready() }, ReloadFunc: func() (*alloy_runtime.Source, error) { return reload() }, - HTTPListenAddr: fr.httpListenAddr, - MemoryListenAddr: fr.inMemoryAddr, - EnablePProf: fr.enablePprof, - DisableSupportBundle: fr.disableSupportBundle, - RuntimeFlags: runtimeFlags, + HTTPListenAddr: fr.httpListenAddr, + MemoryListenAddr: fr.inMemoryAddr, + EnablePProf: fr.enablePprof, + BundleContext: httpservice.SupportBundleContext{ + RuntimeFlags: runtimeFlags, + DisableSupportBundle: fr.disableSupportBundle, + }, }) remoteCfgService, err := remotecfgservice.New(remotecfgservice.Options{ diff --git a/internal/service/http/http.go b/internal/service/http/http.go index d7e0ba1699..fa0f975ee9 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -51,11 +51,10 @@ type Options struct { ReadyFunc func() bool ReloadFunc func() (*alloy_runtime.Source, error) - HTTPListenAddr string // Address to listen for HTTP traffic on. - MemoryListenAddr string // Address to accept in-memory traffic on. - EnablePProf bool // Whether pprof endpoints should be exposed. - DisableSupportBundle bool // Whether support bundle endpoint should be disabled. - RuntimeFlags []byte // Alloy runtime flags to send with support bundle + HTTPListenAddr string // Address to listen for HTTP traffic on. + MemoryListenAddr string // Address to accept in-memory traffic on. + EnablePProf bool // Whether pprof endpoints should be exposed. + BundleContext SupportBundleContext // Context for delivering a support bundle } // Arguments holds runtime settings for the HTTP service. @@ -64,17 +63,20 @@ type Arguments struct { } type Service struct { - log *logging.Logger - // internalLog allows us to leverage log.With for logging and leverage the - // logging struct for setting a temporary logger for support bundle usage - internalLog log.Logger - tracer trace.TracerProvider - gatherer prometheus.Gatherer - opts Options + // globalLogger allows us to leverage the logging struct for setting a temporary + // logger for support bundle usage and still leverage log.With for logging in the service + globalLogger *logging.Logger + log log.Logger + tracer trace.TracerProvider + gatherer prometheus.Gatherer + opts Options winMut sync.Mutex win *server.WinCertStoreHandler + // Used to enforce single-flight requests to supportHandler + supportBundleMut sync.Mutex + // publicLis and tcpLis are used to lazily enable TLS, since TLS is // optionally configurable at runtime. // @@ -122,11 +124,11 @@ func New(opts Options) *Service { _ = publicLis.SetInner(tcpLis) return &Service{ - log: l, - internalLog: log.With(l, "service", "http"), - tracer: t, - gatherer: r, - opts: opts, + globalLogger: l, + log: log.With(l, "service", "http"), + tracer: t, + gatherer: r, + opts: opts, publicLis: publicLis, tcpLis: tcpLis, @@ -166,7 +168,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { netLis, err := net.Listen("tcp", s.opts.HTTPListenAddr) if err != nil { // There is no recovering from failing to listen on the port. - level.Error(s.internalLog).Log("msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) + level.Error(s.log).Log("msg", fmt.Sprintf("failed to listen on %s", s.opts.HTTPListenAddr), "err", err) os.Exit(1) } if err := s.tcpLis.SetInner(netLis); err != nil { @@ -207,16 +209,16 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { if s.opts.ReloadFunc != nil { r.HandleFunc("/-/reload", func(w http.ResponseWriter, _ *http.Request) { - level.Info(s.internalLog).Log("msg", "reload requested via /-/reload endpoint") + level.Info(s.log).Log("msg", "reload requested via /-/reload endpoint") _, err := s.opts.ReloadFunc() if err != nil { - level.Error(s.internalLog).Log("msg", "failed to reload config", "err", err.Error()) + level.Error(s.log).Log("msg", "failed to reload config", "err", err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } - level.Info(s.internalLog).Log("msg", "config reloaded") + level.Info(s.log).Log("msg", "config reloaded") _, _ = fmt.Fprintln(w, "config reloaded") }).Methods(http.MethodGet, http.MethodPost) } @@ -235,7 +237,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { srv := &http.Server{Handler: h2c.NewHandler(r, &http2.Server{})} - level.Info(s.internalLog).Log("msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) + level.Info(s.log).Log("msg", "now listening for http traffic", "addr", s.opts.HTTPListenAddr) listeners := []net.Listener{s.publicLis, s.memLis} for _, lis := range listeners { @@ -245,7 +247,7 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { defer cancel() if err := srv.Serve(lis); err != nil { - level.Info(s.internalLog).Log("msg", "http server closed", "addr", lis.Addr(), "err", err) + level.Info(s.log).Log("msg", "http server closed", "addr", lis.Addr(), "err", err) } }(lis) } @@ -257,11 +259,11 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { } func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { - s.winMut.Lock() + s.supportBundleMut.Lock() + defer s.supportBundleMut.Unlock() cfg := s.opts - s.winMut.Unlock() - if cfg.DisableSupportBundle { + if cfg.BundleContext.DisableSupportBundle { rw.WriteHeader(http.StatusForbidden) _, _ = rw.Write([]byte("support bundle generation is disabled; it can be re-enabled by removing the --disable-support-bundle flag")) return @@ -289,12 +291,12 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { var logsBuffer bytes.Buffer syncBuff := log.NewSyncWriter(&logsBuffer) - s.log.SetTemporaryWriter(syncBuff) + s.globalLogger.SetTemporaryWriter(syncBuff) defer func() { - s.log.RemoveTemporaryWriter() + s.globalLogger.RemoveTemporaryWriter() }() - bundle, err := ExportSupportBundle(ctx, cfg.RuntimeFlags, cfg.HTTPListenAddr, s.Data().(Data).DialFunc) + bundle, err := ExportSupportBundle(ctx, cfg.BundleContext.RuntimeFlags, cfg.HTTPListenAddr, s.Data().(Data).DialFunc) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return @@ -405,14 +407,14 @@ func (s *Service) Update(newConfig any) error { } newTLSListener := tls.NewListener(s.tcpLis, tlsConfig) - level.Info(s.internalLog).Log("msg", "applying TLS config to HTTP server") + level.Info(s.log).Log("msg", "applying TLS config to HTTP server") if err := s.publicLis.SetInner(newTLSListener); err != nil { return err } } else { // Ensure that the outer lazy listener is sending requests directly to the // network, instead of any previous instance of a TLS listener. - level.Info(s.internalLog).Log("msg", "applying non-TLS config to HTTP server") + level.Info(s.log).Log("msg", "applying non-TLS config to HTTP server") if err := s.publicLis.SetInner(s.tcpLis); err != nil { return err } diff --git a/internal/service/http/supportbundle.go b/internal/service/http/supportbundle.go index e82166ff94..3c75c35150 100644 --- a/internal/service/http/supportbundle.go +++ b/internal/service/http/supportbundle.go @@ -11,7 +11,6 @@ import ( "runtime" "runtime/pprof" "strings" - "sync" "time" "github.com/grafana/alloy/internal/build" @@ -20,6 +19,13 @@ import ( "gopkg.in/yaml.v3" ) +// SupportBundleContext groups the relevant context that is used in the HTTP +// service config for the support bundle +type SupportBundleContext struct { + DisableSupportBundle bool // Whether support bundle endpoint should be disabled. + RuntimeFlags []string // Alloy runtime flags to send with support bundle +} + // Bundle collects all the data that is exposed as a support bundle. type Bundle struct { meta []byte @@ -42,13 +48,8 @@ type Metadata struct { Uptime float64 `yaml:"uptime"` } -// Used to enforce single-flight requests to Export -var mut sync.Mutex - // ExportSupportBundle gathers the information required for the support bundle. -func ExportSupportBundle(ctx context.Context, runtimeFlags []byte, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) { - mut.Lock() - defer mut.Unlock() +func ExportSupportBundle(ctx context.Context, runtimeFlags []string, srvAddress string, dialContext server.DialContextFunc) (*Bundle, error) { // The block profiler is disabled by default. Temporarily enable recording // of all blocking events. Also, temporarily record all mutex contentions, // and defer restoring of earlier mutex profiling fraction. @@ -135,7 +136,7 @@ func ExportSupportBundle(ctx context.Context, runtimeFlags []byte, srvAddress st alloyMetrics: alloyMetrics, components: components, peers: peers, - runtimeFlags: runtimeFlags, + runtimeFlags: []byte(strings.Join(runtimeFlags, "\n")), heapBuf: &heapBuf, goroutineBuf: &goroutineBuf, blockBuf: &blockBuf, From f4fc9211844a4ca789d9400a83944ee55a6bda7e Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 14:26:28 -0500 Subject: [PATCH 16/26] Hide support bundle behind public preview stability level --- internal/alloycli/cmd_run.go | 1 + internal/service/http/http.go | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/internal/alloycli/cmd_run.go b/internal/alloycli/cmd_run.go index a78aa4ad9b..306a030c55 100644 --- a/internal/alloycli/cmd_run.go +++ b/internal/alloycli/cmd_run.go @@ -298,6 +298,7 @@ func (fr *alloyRun) Run(cmd *cobra.Command, configPath string) error { HTTPListenAddr: fr.httpListenAddr, MemoryListenAddr: fr.inMemoryAddr, EnablePProf: fr.enablePprof, + MinStability: fr.minStability, BundleContext: httpservice.SupportBundleContext{ RuntimeFlags: runtimeFlags, DisableSupportBundle: fr.disableSupportBundle, diff --git a/internal/service/http/http.go b/internal/service/http/http.go index fa0f975ee9..590802b9b4 100644 --- a/internal/service/http/http.go +++ b/internal/service/http/http.go @@ -51,10 +51,11 @@ type Options struct { ReadyFunc func() bool ReloadFunc func() (*alloy_runtime.Source, error) - HTTPListenAddr string // Address to listen for HTTP traffic on. - MemoryListenAddr string // Address to accept in-memory traffic on. - EnablePProf bool // Whether pprof endpoints should be exposed. - BundleContext SupportBundleContext // Context for delivering a support bundle + HTTPListenAddr string // Address to listen for HTTP traffic on. + MemoryListenAddr string // Address to accept in-memory traffic on. + EnablePProf bool // Whether pprof endpoints should be exposed. + MinStability featuregate.Stability // Minimum stability level to utilize for feature gates + BundleContext SupportBundleContext // Context for delivering a support bundle } // Arguments holds runtime settings for the HTTP service. @@ -261,9 +262,16 @@ func (s *Service) Run(ctx context.Context, host service.Host) error { func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { s.supportBundleMut.Lock() defer s.supportBundleMut.Unlock() - cfg := s.opts - if cfg.BundleContext.DisableSupportBundle { + // TODO(dehaansa) remove this check once the support bundle is generally available + if !s.opts.MinStability.Permits(featuregate.StabilityPublicPreview) { + rw.WriteHeader(http.StatusForbidden) + _, _ = rw.Write([]byte("support bundle generation is only available in public preview. Use" + + " --stability.level command-line flag to enable public-preview features")) + return + } + + if s.opts.BundleContext.DisableSupportBundle { rw.WriteHeader(http.StatusForbidden) _, _ = rw.Write([]byte("support bundle generation is disabled; it can be re-enabled by removing the --disable-support-bundle flag")) return @@ -296,7 +304,7 @@ func (s *Service) supportHandler(rw http.ResponseWriter, r *http.Request) { s.globalLogger.RemoveTemporaryWriter() }() - bundle, err := ExportSupportBundle(ctx, cfg.BundleContext.RuntimeFlags, cfg.HTTPListenAddr, s.Data().(Data).DialFunc) + bundle, err := ExportSupportBundle(ctx, s.opts.BundleContext.RuntimeFlags, s.opts.HTTPListenAddr, s.Data().(Data).DialFunc) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return From 3d5f9af005d783ddf0fc4d73402ec7cfa93ceb30 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 14:27:38 -0500 Subject: [PATCH 17/26] Update docs based on feedback --- docs/sources/troubleshoot/support_bundle.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 156c6c0ca0..b782d1b327 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -6,8 +6,12 @@ menuTitle: Generate Support Bundle weight: 300 --- +Public preview + # Generate Support Bundle +{{< docs/shared lookup="stability/public_preview.md" source="alloy" version="" >}} + The `/-/support?duration=N` endpoint returns a 'support bundle', a zip file that contains information about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a baseline of information when trying to debug an issue. From 88c4224b19de54875246b05ccf2e23ad0feff7c5 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:09:14 -0500 Subject: [PATCH 18/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index b782d1b327..44d23c2976 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -12,7 +12,7 @@ weight: 300 {{< docs/shared lookup="stability/public_preview.md" source="alloy" version="" >}} -The `/-/support?duration=N` endpoint returns a 'support bundle', a zip file that contains information +The `/-/support?duration=N` endpoint returns a support bundle, a zip file that contains information about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a baseline of information when trying to debug an issue. From f23481ff6f5aa9c0b03ac29c7803d31859771c53 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:09:25 -0500 Subject: [PATCH 19/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 44d23c2976..6458aeb499 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -8,7 +8,7 @@ weight: 300 Public preview -# Generate Support Bundle +# Generate a support bundle {{< docs/shared lookup="stability/public_preview.md" source="alloy" version="" >}} From 1755e9db815707c0d1d2ce8d3ff72705308fa31b Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:10:30 -0500 Subject: [PATCH 20/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 6458aeb499..087e59def5 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -2,7 +2,7 @@ canonical: https://grafana.com/docs/alloy/latest/troubleshoot/support_bundle/ description: Learn how to generate a support bundle title: Generate a Support Bundle -menuTitle: Generate Support Bundle +menuTitle: Generate a support bundle weight: 300 --- From f10ac8e2e1438204036b24c03e69700a01ddc4b6 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:10:41 -0500 Subject: [PATCH 21/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 087e59def5..0943104188 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -36,7 +36,7 @@ A support bundle contains the following data: `/api/v0/web/components` endpoint. * `alloy-logs.txt` contains the logs during the bundle generation. * `alloy-metadata.yaml` contains the {{< param "PRODUCT_NAME" >}} build version and the installation's operating system, architecture, and uptime. -* `alloy-metrics.txt` contains a snapshot of {{< param "PRODUCT_NAME" >}}'s internal metrics. +* `alloy-metrics.txt` contains a snapshot of the internal metrics for {{< param "PRODUCT_NAME" >}}. * `alloy-peers.json` contains information about the identified cluster peers of this {{< param "PRODUCT_NAME" >}} instance, generated by the `/api/v0/web/peers` endpoint. * `alloy-runtime-flags.txt` contains the values of the runtime flags available in {{< param "PRODUCT_NAME" >}}. From 034265c37bae7e58ca228b25371e56dc21a8cf0a Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:10:52 -0500 Subject: [PATCH 22/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 0943104188..079032175e 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -1,7 +1,7 @@ --- canonical: https://grafana.com/docs/alloy/latest/troubleshoot/support_bundle/ description: Learn how to generate a support bundle -title: Generate a Support Bundle +title: Generate a support bundle menuTitle: Generate a support bundle weight: 300 --- From f5fd0d07592359084015fe78a7dc2d1b625d9c59 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:11:01 -0500 Subject: [PATCH 23/26] Update docs/sources/troubleshoot/support_bundle.md Co-authored-by: Clayton Cornell <131809008+clayton-cornell@users.noreply.github.com> --- docs/sources/troubleshoot/support_bundle.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 079032175e..139f2fb8e2 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -40,8 +40,8 @@ A support bundle contains the following data: * `alloy-peers.json` contains information about the identified cluster peers of this {{< param "PRODUCT_NAME" >}} instance, generated by the `/api/v0/web/peers` endpoint. * `alloy-runtime-flags.txt` contains the values of the runtime flags available in {{< param "PRODUCT_NAME" >}}. -* The `pprof/` directory contains Go runtime profiling data (CPU, heap, goroutine, mutex, block profiles) as exported by the pprof package. See the [profile][profile] - documentation for more details on how to use this information. +* The `pprof/` directory contains Go runtime profiling data (CPU, heap, goroutine, mutex, block profiles) as exported by the pprof package. +Refer to the [profile][profile] documentation for more details on how to use this information. [profile]: ../profile [components]: ../../get-started/components/ From a8e01da10d77e12933e8b472ec04ea5d03f59e51 Mon Sep 17 00:00:00 2001 From: Sam DeHaan Date: Thu, 7 Nov 2024 15:35:21 -0500 Subject: [PATCH 24/26] More PR feedback in docs --- docs/sources/troubleshoot/support_bundle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index 139f2fb8e2..a7005ccace 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -10,7 +10,7 @@ weight: 300 # Generate a support bundle -{{< docs/shared lookup="stability/public_preview.md" source="alloy" version="" >}} +{{< docs/public-preview product="Generate support bundle" >}} The `/-/support?duration=N` endpoint returns a support bundle, a zip file that contains information about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a baseline of information when trying From 62b2736fea9ffebd32ec958f21189d46890d7506 Mon Sep 17 00:00:00 2001 From: Piotr <17101802+thampiotr@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:18:33 +0000 Subject: [PATCH 25/26] Fix race condition in logger --- internal/runtime/logging/logger.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/runtime/logging/logger.go b/internal/runtime/logging/logger.go index 843aec4c77..4e1b5bd5a3 100644 --- a/internal/runtime/logging/logger.go +++ b/internal/runtime/logging/logger.go @@ -109,9 +109,9 @@ func (l *Logger) Update(o Options) error { l.level.Set(slogLevel(o.Level).Level()) l.format.Set(o.Format) - l.writer.innerWriter = l.inner + l.writer.SetInnerWriter(l.inner) if len(o.WriteTo) > 0 { - l.writer.lokiWriter = &lokiWriter{o.WriteTo} + l.writer.SetLokiWriter(&lokiWriter{o.WriteTo}) } // Build all our deferred handlers @@ -245,6 +245,18 @@ func (w *writerVar) RemoveTemporaryWriter() { w.tmpWriter = nil } +func (w *writerVar) SetInnerWriter(writer io.Writer) { + w.mut.Lock() + defer w.mut.Unlock() + w.innerWriter = writer +} + +func (w *writerVar) SetLokiWriter(writer *lokiWriter) { + w.mut.Lock() + defer w.mut.Unlock() + w.lokiWriter = writer +} + func (w *writerVar) Write(p []byte) (int, error) { w.mut.RLock() defer w.mut.RUnlock() From 3599ef9bb7b758fdc690329a6dfa433501498d22 Mon Sep 17 00:00:00 2001 From: Piotr <17101802+thampiotr@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:26:54 +0000 Subject: [PATCH 26/26] Add a note about backward-compatibility exception --- docs/sources/troubleshoot/support_bundle.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/troubleshoot/support_bundle.md b/docs/sources/troubleshoot/support_bundle.md index a7005ccace..2bb870bc5b 100644 --- a/docs/sources/troubleshoot/support_bundle.md +++ b/docs/sources/troubleshoot/support_bundle.md @@ -16,6 +16,8 @@ The `/-/support?duration=N` endpoint returns a support bundle, a zip file that c about a running {{< param "PRODUCT_NAME" >}} instance, and can be used as a baseline of information when trying to debug an issue. +This feature is not covered by our [backward-compatibility][backward-compatibility] guarantees. + {{< admonition type="note" >}} This endpoint is enabled by default, but may be disabled using the `--disable-support-bundle` runtime flag. {{< /admonition >}} @@ -45,4 +47,5 @@ Refer to the [profile][profile] documentation for more details on how to use thi [profile]: ../profile [components]: ../../get-started/components/ -[alloy-repo]: https://github.com/grafana/alloy/issues \ No newline at end of file +[alloy-repo]: https://github.com/grafana/alloy/issues +[backward-compatibility]: ../../introduction/backward-compatibility \ No newline at end of file