Help us shape the future of Go! Take the 2026 Go Developer Survey

diagnostics

package
v0.2.52 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: GPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package diagnostics records a bounded, owner-confirmed support capture and builds one redacted ZIP. It does not persist captures, change playback, or accept paths, URLs, or commands from the caller.

Index

Constants

View Source
const Schema = "ether.support.v1"

Schema is the archive manifest version.

Variables

View Source
var (
	ErrNotFound     = errors.New("diagnostics: not found")
	ErrStaleDevice  = errors.New("diagnostics: stale device")
	ErrTooMany      = errors.New("diagnostics: too many captures")
	ErrNotRecording = errors.New("diagnostics: not recording")
)

Functions

func Alias

func Alias(scope, raw string) string

Alias is a stable pseudonym. The same raw id always maps to the same alias, and different ids do not share one.

func LogCorrelated

func LogCorrelated(rec LogRecord, q LogQuery) bool

LogCorrelated reports whether rec belongs to this capture by an exact correlation field inside the capture window. A shorter id is not a match for a longer one, and free text is not searched.

Types

type Candidate

type Candidate struct {
	Protocol   string
	Height     int
	VideoCodec string
	AudioCodec string
	Size       int64
	Score      float64
	Accepted   bool
	Rejected   bool
	Reason     string
	Name       string
}

Candidate is a bounded, URL-free source fact.

type CaptureView

type CaptureView struct {
	ID               string        `json:"id"`
	State            string        `json:"state"`
	DeviceConfirmed  bool          `json:"deviceConfirmed"`
	StartedAt        time.Time     `json:"startedAt"`
	Deadline         time.Time     `json:"deadline"`
	FinishedAt       *time.Time    `json:"finishedAt,omitempty"`
	Category         string        `json:"category"`
	Note             string        `json:"note,omitempty"`
	IncludeTitles    bool          `json:"includeTitles"`
	IncludeUsernames bool          `json:"includeUsernames"`
	IncludeAddresses bool          `json:"includeAddresses"`
	Device           *DeviceInfo   `json:"device,omitempty"`
	Discovery        []DeviceInfo  `json:"discovery,omitempty"`
	Observations     []Observation `json:"observations"`
	Hypotheses       []Hypothesis  `json:"hypotheses"`
	Coverage         []Gap         `json:"coverage"`
	Preview          *Preview      `json:"preview,omitempty"`
}

CaptureView is the owner-console state of one capture.

type ConnectivityFact

type ConnectivityFact struct {
	RemoteAccess          bool   `json:"remoteAccess"`
	SecureConnections     string `json:"secureConnections,omitempty"`
	CertificateConfigured bool   `json:"certificateConfigured"`
	PublicPort            int    `json:"publicPort,omitempty"`
}

ConnectivityFact has no public host, custom URL, or certificate path.

type Deps

type Deps struct {
	Now       func() time.Time
	Version   string
	Commit    string
	Platform  string
	Container string // "yes", "no", or "" when the caller could not tell
	Secrets   func() []string
	Settings  func() (Settings, error)
	// Sessions returns facts for deviceKey and a count of sessions that
	// belonged to someone else and were not copied into the slice.
	Sessions     func(deviceKey string) (matched []SessionFact, omitted int, err error)
	Hardware     func() (HardwareFact, error)
	Storage      func() (StorageFact, error)
	Connectivity func() (ConnectivityFact, error)
	// LogExtract returns structured records. Callers correlate on exact
	// fields inside [From, Until] and must not match substrings.
	LogExtract func(q LogQuery) (records []LogRecord, truncated bool, err error)
}

Deps are the only inputs the archive collectors may use.

type DeviceInfo

type DeviceInfo struct {
	ID            string    `json:"id"`
	Name          string    `json:"name,omitempty"`
	Model         string    `json:"model,omitempty"`
	Product       string    `json:"product,omitempty"`
	Version       string    `json:"version,omitempty"`
	Platform      string    `json:"platform,omitempty"`
	LastSeen      time.Time `json:"lastSeen"`
	SuggestedKind string    `json:"suggestedKind,omitempty"`
}

DeviceInfo is the owner-console card. ID is an opaque hash, not a client id.

type Event

type Event struct {
	Time time.Time
	// Until is the last arrival merged into this event. A coalesced span
	// is bounded by Time, not by the previous arrival. Zero means Time.
	Until            time.Time
	Kind             Kind
	Protocol         string
	DeviceKey        string
	SessionKey       string
	AttemptKey       string
	Method           string
	Route            string
	Status           int
	Duration         time.Duration
	Bytes            int64
	Range            string
	Repeat           int
	Product          string
	Platform         string
	Model            string
	DeviceName       string
	AppVersion       string
	QualityRequested string
	QualitySelected  string
	Profile          string
	Decision         string
	Reason           string
	Rejection        string
	ErrorExcerpt     string
	FFmpegExcerpt    string
	Title            string
	SourceName       string
	Username         string
	Peer             string
	// Uncorrelated is set when a correlation id was too long to retain.
	// The id is not stored, so it cannot be merged with another id.
	Uncorrelated bool
	Candidates   []Candidate
	Media        *MediaFacts
}

Event is one neutral observation. String fields are clipped before the event is stored.

type FinishIn

type FinishIn struct {
	IncludeTitles    bool `json:"includeTitles"`
	IncludeUsernames bool `json:"includeUsernames"`
	IncludeAddresses bool `json:"includeAddresses"`
	SetFlags         bool `json:"-"`
}

FinishIn replaces the inclusion flags used for the ZIP. The finish response previews the exact usernames and addresses that will be inside.

type Gap

type Gap struct {
	Collector string `json:"collector"`
	Status    string `json:"status"`
	Detail    string `json:"detail"`
}

Gap is something the package does not contain, and why.

type HardwareFact

type HardwareFact struct {
	Name      string `json:"name,omitempty"`
	FFmpeg    string `json:"ffmpeg,omitempty"`
	Available bool   `json:"available"`
}

HardwareFact has no device node paths.

type Hypothesis

type Hypothesis struct {
	Code string `json:"code"`
	Text string `json:"text"`
}

Hypothesis is an inference kept separate from observations.

type IdentityPreview

type IdentityPreview struct {
	UsernamesIncluded bool     `json:"usernamesIncluded"`
	AddressesIncluded bool     `json:"addressesIncluded"`
	Usernames         []string `json:"usernames,omitempty"`
	Addresses         []string `json:"addresses,omitempty"`
	Titles            []string `json:"titles,omitempty"`
	TitlesIncluded    bool     `json:"titlesIncluded"`
	Explanation       string   `json:"explanation"`
}

IdentityPreview states exactly which usernames and addresses the ZIP will contain. Values are present only when that inclusion flag is on.

type Kind

type Kind string

Kind is one observed fact. ClassifyRequest never returns a source rejection or an encoder result; those are recorded only by an explicit Observe from the component that actually decided.

const (
	KindDevice            Kind = "device.seen"
	KindPlaybackRequested Kind = "playback.requested"
	KindSourceChosen      Kind = "source.chosen"
	KindSourceRejected    Kind = "source.rejected"
	KindStreamRequested   Kind = "stream.requested"
	KindEncoderStarted    Kind = "encoder.started"
	KindEncoderFailed     Kind = "encoder.failed"
	KindMediaRequest      Kind = "media.request"
	KindMediaBytes        Kind = "media.bytes"
	KindTimelineReceived  Kind = "timeline.received"
	KindPlaybackReported  Kind = "playback.reported"
	KindPoll              Kind = "client.poll"
	KindTranscodeDecision Kind = "transcode.decision"
)

func ClassifyRequest

func ClassifyRequest(path string, status int, contentType string, bytesWritten int64) Kind

ClassifyRequest maps a request to an observation.

A poll, including HTTP 404, is only KindPoll. A playlist request is only KindStreamRequested. KindMediaBytes requires a successful media response, not merely bytes written: a 404 or 500 body is KindMediaRequest. /:/timeline is KindTimelineReceived until the handler records KindPlaybackReported itself. This function never returns KindPlaybackReported, KindSourceRejected, KindSourceChosen, KindEncoderStarted, or KindEncoderFailed.

type LogQuery

type LogQuery struct {
	From        time.Time
	Until       time.Time
	DeviceKey   string
	SessionKeys []string
	AttemptKeys []string
	MaxLines    int
}

LogQuery selects log records for one confirmed capture.

type LogRecord

type LogRecord struct {
	Time    time.Time
	Level   string
	Message string
	Attrs   map[string]string
}

LogRecord is one structured log row. Message is free text and is not exported.

func ParseLogLine

func ParseLogLine(line string) (LogRecord, bool)

ParseLogLine reads a slog-style line into structured fields. It returns false when the line has no key=value fields.

type MediaFacts

type MediaFacts struct {
	Container   string `json:"container,omitempty"`
	VideoCodec  string `json:"videoCodec,omitempty"`
	AudioCodec  string `json:"audioCodec,omitempty"`
	Height      int    `json:"height,omitempty"`
	Width       int    `json:"width,omitempty"`
	BitrateKbps int    `json:"bitrateKbps,omitempty"`
	Channels    string `json:"channels,omitempty"`
}

MediaFacts is probe information safe to export. It has no URLs.

type Observation

type Observation struct {
	At     time.Time `json:"at"`
	Kind   Kind      `json:"kind"`
	Count  int       `json:"count,omitempty"`
	Status int       `json:"status,omitempty"`
	Route  string    `json:"route,omitempty"`
	Bytes  int64     `json:"bytes,omitempty"`
	Device string    `json:"device,omitempty"`
}

Observation is one fact shown to the owner. It is not a diagnosis.

type Preview

type Preview struct {
	Files     []string        `json:"files"`
	Omitted   []string        `json:"omitted,omitempty"`
	Identity  IdentityPreview `json:"identity"`
	Note      string          `json:"note,omitempty"`
	Redaction string          `json:"redaction"`
}

Preview is the inclusion list shown before download. Files is the sealed archive inventory. It is empty until Finish freezes the ZIP, and then it matches that ZIP's members in order.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

func New

func New(deps Deps) *Recorder

New records observations in memory. A nil clock uses time.Now.

func (*Recorder) Archive

func (r *Recorder) Archive(token, id string) ([]byte, error)

Archive returns the ZIP for the capture that created it. Any other credential gets the same not-found result as an unknown id.

func (*Recorder) Devices

func (r *Recorder) Devices() []DeviceInfo

Devices returns device cards seen within deviceRecent. Ids are opaque.

func (*Recorder) Discard

func (r *Recorder) Discard(token, id string) error

func (*Recorder) Expire

func (r *Recorder) Expire()

Expire drops finished captures past their retention and finishes recordings past the deadline so a disconnected browser cannot leave one open.

func (*Recorder) Finish

func (r *Recorder) Finish(token, id string, in FinishIn) (CaptureView, error)

Finish closes the capture and builds the ZIP. The returned view lists the exact usernames and addresses that were written, when those flags are on.

func (*Recorder) Get

func (r *Recorder) Get(token, id string) (CaptureView, error)

func (*Recorder) Observe

func (r *Recorder) Observe(e Event)

Observe retains one bounded event. Ordinary device sightings update the device table and do not take a ring slot.

func (*Recorder) SelectDevice

func (r *Recorder) SelectDevice(token, id, deviceID string) (CaptureView, error)

SelectDevice confirms a discovered or recent device. A stale id leaves the capture unconfirmed.

func (*Recorder) Start

func (r *Recorder) Start(token string, in StartIn) (CaptureView, error)

type SessionFact

type SessionFact struct {
	DeviceKey string
	State     string
	Decision  string
	Quality   string
	Title     string
	Username  string
	Peer      string
	Bytes     int64
}

SessionFact is one playback session before projection. DeviceKey selects correlation and is not exported. Username, title, and peer are omitted from the session file; usernames and addresses are exported only from the identity preview when the owner opts in.

type Settings

type Settings struct {
	LogLevel              string `json:"logLevel,omitempty"`
	FriendlyName          string `json:"friendlyName,omitempty"`
	QualityDefault        string `json:"qualityDefault,omitempty"`
	QualityRemote         string `json:"qualityRemote,omitempty"`
	ProfileName           string `json:"profileName,omitempty"`
	ProfileMaxHeight      int    `json:"profileMaxHeight,omitempty"`
	ProfileAllowHDR       bool   `json:"profileAllowHdr,omitempty"`
	HWAccel               string `json:"hwAccel,omitempty"`
	MaxTranscodes         int    `json:"maxTranscodes,omitempty"`
	RemoteMaxBitrateKbps  int    `json:"remoteMaxBitrateKbps,omitempty"`
	SecureConnections     string `json:"secureConnections,omitempty"`
	RemoteAccess          bool   `json:"remoteAccess,omitempty"`
	CertificateConfigured bool   `json:"certificateConfigured,omitempty"`
	CacheBudgetBytes      int64  `json:"cacheBudgetBytes,omitempty"`
}

Settings is an explicit safe projection. Callers must not put secrets, filesystem paths, or URLs in it.

type StartIn

type StartIn struct {
	DeviceID         string `json:"deviceId"`
	Category         string `json:"category"`
	Note             string `json:"note"`
	IncludeTitles    bool   `json:"includeTitles"`
	IncludeUsernames bool   `json:"includeUsernames"`
	IncludeAddresses bool   `json:"includeAddresses"`
}

StartIn opens a capture. DeviceID empty means the device is not known yet. A device id that is not in the recent table is stale and does not open a capture. Notes are kept, truncated, and treated as untrusted data.

type StorageFact

type StorageFact struct {
	CacheBudgetBytes int64 `json:"cacheBudgetBytes,omitempty"`
	DiskFreeBytes    int64 `json:"diskFreeBytes,omitempty"`
	DiskFreeKnown    bool  `json:"diskFreeKnown"`
}

StorageFact has no cache directory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL