blob: beab88626b6fa0727e0a22b5fac308346d03bfef [file] [log] [blame]
// Copyright 2023 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 upload
import (
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
"runtime/debug"
"strings"
"time"
"golang.org/x/telemetry/internal/telemetry"
)
var logger *log.Logger
func init() {
logger = log.New(io.Discard, "", 0)
}
// keep track of what SetLogOutput has seen
var seenlogwriters []io.Writer
// SetLogOutput sets the default logger's output destination.
func SetLogOutput(logging io.Writer) {
if logging == nil {
return
}
logger.SetOutput(logging) // the common case
seenlogwriters = append(seenlogwriters, logging)
if len(seenlogwriters) > 1 {
// The client asked for logging, and there is also a debug dir
logger.SetOutput(io.MultiWriter(seenlogwriters...))
}
}
// LogIfDebug arranges to write a log file in the directory
// dirname, if it exists. If dirname is the empty string,
// the function tries the directory it.Localdir/debug.
func LogIfDebug(dirname string) error {
dname := filepath.Join(telemetry.Default.LocalDir(), "debug")
if dirname != "" {
dname = dirname
}
fd, err := os.Stat(dname)
if err != nil {
return err
}
if fd == nil || !fd.IsDir() {
// debug doesn't exist or isn't a directory
return nil
}
info, ok := debug.ReadBuildInfo()
if !ok {
return fmt.Errorf("no build info")
}
year, month, day := time.Now().UTC().Date()
goVers := info.GoVersion
// E.g., goVers:"go1.22-20240109-RC01 cl/597041403 +dcbe772469 X:loopvar"
words := strings.Fields(goVers)
goVers = words[0]
progPkgPath := info.Path
if progPkgPath == "" {
progPkgPath = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
}
prog := path.Base(progPkgPath)
progVers := info.Main.Version
fname := filepath.Join(dname, fmt.Sprintf("%s-%s-%s-%4d%02d%02d-%d.log",
prog, progVers, goVers, year, month, day, os.Getpid()))
fname = strings.ReplaceAll(fname, " ", "")
if _, err := os.Stat(fname); err == nil {
// This process previously called upload.Run
return nil
}
logfd, err := os.Create(fname)
if err != nil {
return err
}
SetLogOutput(logfd)
return nil
}
// Uploader carries parameters needed for upload.
type Uploader struct {
// Config is used to select counters to upload.
Config *telemetry.UploadConfig
// ConfigVersion is the version of the config.
ConfigVersion string
// Dir is the telemetry directory to process.
Dir telemetry.Dir
UploadServerURL string
StartTime time.Time
cache parsedCache
}
// NewUploader creates a default uploader.
//
// If telemetryDir is set, it is used as the telemetry data directory to
// process, instead of the default directory.
//
// If config is set, it is used as the upload config, rather than the
// downloaded upload configuration.
func NewUploader(telemetryDir string, config *telemetry.UploadConfig) *Uploader {
var dir telemetry.Dir
if telemetryDir != "" {
dir = telemetry.NewDir(telemetryDir)
} else {
dir = telemetry.Default
}
// TODO(rfindley): get the config here, rather than during the upload process.
return &Uploader{
Config: config,
ConfigVersion: "custom",
Dir: dir,
UploadServerURL: "https://telemetry.go.dev/upload",
StartTime: time.Now().UTC(),
}
}
// Run generates and uploads reports
func (u *Uploader) Run() {
if telemetry.DisabledOnPlatform {
return
}
todo := u.findWork()
ready, err := u.reports(&todo)
if err != nil {
logger.Printf("reports: %v", err)
}
for _, f := range ready {
u.uploadReport(f)
}
}