| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package telemetry |
| |
| import ( |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| |
| "golang.org/x/telemetry/counter" |
| "golang.org/x/telemetry/internal/crashmonitor" |
| ) |
| |
| // Config controls the behavior of [Start]. |
| type Config struct { |
| // ReportCrashes, if set, will enable crash reporting. |
| // ReportCrashes uses the [debug.SetCrashOutput] mechanism, which is a |
| // process-wide resource. |
| // Do not make other calls to that function within your application. |
| // ReportCrashes is a non-functional unless the program is built with go1.23+. |
| ReportCrashes bool |
| |
| // Upload causes this program to periodically upload approved counters |
| // from the local telemetry database to telemetry.go.dev. |
| // |
| // This option has no effect unless the user has given consent |
| // to enable data collection, for example by running |
| // cmd/gotelemetry or affirming the gopls dialog. |
| // |
| // (This feature is expected to be used only by gopls. |
| // Longer term, the go command may become the sole program |
| // responsible for uploading.) |
| Upload bool |
| |
| // Log messages generated by crash monitoring or uploading will be |
| // written to this optional writer. |
| Logger io.Writer |
| } |
| |
| // Start initializes telemetry using the specified configuration. |
| // |
| // Start opens the local telemetry database so that counter increment |
| // operations are durably recorded in the local file system. |
| // |
| // If [Config.Upload] is set, and the user has opted in to telemetry |
| // uploading, this process may attempt to upload approved counters |
| // to telemetry.go.dev. [But: this option doesn't appear to exist yet.] |
| // |
| // If [Config.ReportCrashes] is set, Start re-executes the current |
| // executable as a child process, in a special mode in which it acts |
| // as a crash monitor for the parent process (the application). |
| // In that mode, the call to Start will never return, so Start must |
| // be called immediately within main, even before such things as |
| // inspecting the command line. The application should avoid expensive |
| // steps or external side effects in init functions, as they will |
| // be executed twice (parent and child). |
| func Start(config Config) { |
| counter.Open() |
| if !config.ReportCrashes || !crashmonitor.Supported() { |
| // TODO(matloob): Once support for uploading is added to start, |
| // we'll just start the uploader instead of returning here. |
| return |
| } |
| if os.Getenv(telemetryChildVar) != "" { |
| child(config.Logger) |
| panic("unreachable") |
| } |
| |
| parent(config.Logger) |
| } |
| |
| const telemetryChildVar = "X_TELEMETRY_CHILD" |
| |
| func parent(logw io.Writer) { |
| logger := log.New(logw, "", 0) |
| // This process is the application (parent). |
| // Fork+exec the telemetry child. |
| exe, err := os.Executable() |
| if err != nil { |
| logger.Fatal(err) |
| } |
| cmd := exec.Command(exe, "** telemetry **") // this unused arg is just for ps(1) |
| cmd.Env = append(os.Environ(), telemetryChildVar+"=1") |
| cmd.Stderr = os.Stderr |
| cmd.Stdout = os.Stderr |
| pipe, err := cmd.StdinPipe() |
| if err != nil { |
| logger.Fatalf("StdinPipe: %v", err) |
| } |
| |
| crashmonitor.Parent(pipe.(*os.File)) // (this conversion is safe) |
| |
| if err := cmd.Start(); err != nil { |
| logger.Fatalf("can't start telemetry child process: %v", err) |
| } |
| } |
| |
| func child(logw io.Writer) { |
| // TODO(matloob): add support for uploading. |
| crashmonitor.Child(logw) |
| } |