blob: 0a059330099dbf2e990cd4a61c1b0f9f72faa076 [file] [log] [blame]
// 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)
}