Documentation
¶
Overview ¶
Package crashtracker monitors the application process for crashes and sends structured crash reports to Datadog Error Tracking.
It uses the monitor-process pattern: on Start, the application re-execs itself as a lightweight monitor child (identified by the DD_CRASHTRACKING_IS_MONITOR_PROCESS environment variable). The monitor child inherits a pipe fd registered via runtime/debug.SetCrashOutput; when the application crashes the Go runtime writes the crash dump to that pipe and the monitor child parses and uploads a structured report to the Error Tracking intake.
Requires runtime/debug.SetCrashOutput, added in Go 1.23; see this repository's go.mod for the minimum Go version this module actually builds with.
Lifecycle ¶
Call Start as early as possible in main, before any goroutines are created:
func main() {
if err := crashtracker.Start(); err != nil {
log.Printf("crashtracker.Start: %v", err)
}
// ... application code
}
There is no corresponding Stop. Process exit alone closes the crash pipe, which is all the cleanup the monitor needs: it reads EOF and exits without filing a report. Do not add a deferred unregister step — deferred functions run during panic unwinding, before the runtime writes the crash dump, so a defer here would disable reporting for the most common crash: an unrecovered panic.
Start is idempotent: subsequent calls after the first are no-ops. A companion Orchestrion integration (not part of this package) can inject a Start call as the first statement of main using DD_* environment configuration; where that integration is built in, it wins the race to be the first Start call, so a later programmatic Start call with options in main is a no-op and those options are silently dropped — not applied, not merged, and not reported as ignored. Do not rely on programmatic options to control startup in that build; use the DD_* environment configuration the integration reads instead.
Configuration ¶
The monitor process inherits all environment variables except GOMEMLIMIT and GOGC (the monitor sets its own memory ceiling instead of inheriting the application's). Options passed to Start (WithService, WithEnv, WithVersion, WithAPIKey, WithAgentURL, WithSite) are resolved in the application process and then forwarded to the monitor child across the process boundary, so they take effect end to end. WithHTTPClient is the one exception: an *http.Client cannot cross a process boundary, so it only affects direct calls to the package's internal upload path and has no effect via Start.
Goroutine stack completeness ¶
By default Go uses GOTRACEBACK=single, which records only the crashing goroutine in the crash dump. Set GOTRACEBACK=all in the process environment to include all goroutines in the crash report's error.threads field.
Containers and PID 1 ¶
The monitor is a child of the application process, in the same PID namespace rather than a separate one. If the application is PID 1 of that namespace — the common case for a Go binary run directly as a container's ENTRYPOINT with no init process in front of it — the kernel terminates every other process in the namespace with an unblockable SIGKILL as soon as PID 1 fully exits (pid_namespaces(7)). That includes the monitor. Receiving the crash dump itself is not the risk: the runtime's write to the pipe happens synchronously before the application finishes exiting, with the monitor already started and reading. Uploading the parsed report is a network round trip, and it can still be in flight when the namespace teardown lands a moment later, silently losing the report. Run the application behind an init process (tini, dumb-init, or an orchestrator's own, e.g. Kubernetes' shareProcessNamespace) so it is not PID 1 itself — already common container practice for signal handling and zombie reaping, and required here for the same structural reason.
Init order note ¶
The monitor child is intercepted from package init, which is the earliest hook available to a pure Go implementation. Go leaves cross-package init order unspecified beyond dependency constraints, and in practice this means any package linked into the binary — not just main's direct imports, but every transitive dependency — can run its init to full completion in the monitor role before crashtracker's own init detects that role and exits. This is not something reordering your own imports can influence: the ordering is decided by the toolchain across the whole import graph, not by import statement order in your source. Keep expensive or externally visible init work out of any package that might be imported when crashtracking is enabled, and call Start as the first statement of main for manual integrations.
Index ¶
- func Start(opts ...Option) error
- type Error
- type Frame
- type OSInfo
- type Option
- func WithAPIKey(apiKey string) Option
- func WithAgentURL(rawURL string) Option
- func WithEnabled(enabled bool) Option
- func WithEnv(env string) Option
- func WithHTTPClient(c *http.Client) Option
- func WithService(service string) Option
- func WithSite(site string) Option
- func WithVersion(version string) Option
- type Report
- type SigInfo
- type StackTrace
- type Thread
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Start ¶
Start initialises the crashtracker. It must be called as early as possible in main().
Start spawns a monitor child process, registers the crash pipe via runtime/debug.SetCrashOutput, and returns so the application continues normally. There is no corresponding Stop: process exit alone closes the pipe, which is all the cleanup the monitor needs (it reads EOF and exits without a report). An explicit unregister-and-close step is unnecessary and actively harmful if deferred, since deferred functions run during panic unwinding — before the runtime writes the crash dump — which would disable reporting for the most common crash: an unrecovered panic. See the package doc for the full example.
Start is idempotent: subsequent calls after the first are no-ops.
Types ¶
type Error ¶
type Error struct {
Type string `json:"type,omitempty"`
Message string `json:"message,omitempty"`
Stack *StackTrace `json:"stack,omitempty"`
Threads []Thread `json:"threads,omitempty"`
ThreadName string `json:"thread_name,omitempty"`
IsCrash bool `json:"is_crash"`
SourceType string `json:"source_type,omitempty"`
}
Error holds error details in the errorsintake model.
type Frame ¶
type Frame struct {
Function string `json:"function,omitempty"`
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
}
Frame is a single stack frame.
type OSInfo ¶
type OSInfo struct {
Architecture string `json:"architecture"`
Bitness string `json:"bitness"`
OSType string `json:"os_type"`
Version string `json:"version"`
}
OSInfo holds OS/platform details required by the Crashtracking error.source_type path. All four fields are always serialized: libdatadog's own OsInfo struct has no optional fields here (os_info.rs), so none of these should be omitempty.
type Option ¶
type Option func(*config)
Option is a functional option for configuring the crashtracker.
func WithAPIKey ¶
WithAPIKey sets the Datadog API key for agentless upload. The key reaches the monitor child process through its environment (like every other option; see buildChildEnv), so choosing WithAPIKey over DD_API_KEY does not keep the key out of a process environment — it only keeps it out of the application's own.
func WithAgentURL ¶
WithAgentURL configures the Datadog Agent URL for report upload.
func WithEnabled ¶
WithEnabled explicitly enables or disables the crashtracker, overriding the DD_CRASHTRACKING_ENABLED environment gate. When disabled, Start does not spawn the monitor process and returns nil.
func WithHTTPClient ¶
WithHTTPClient sets a custom HTTP client for report upload.
func WithService ¶
WithService sets the service name tag on crash reports.
func WithVersion ¶
WithVersion sets the version tag on crash reports.
type Report ¶
type Report struct {
Timestamp int64 `json:"timestamp"` // unix ms when the monitor built the report (no crash time is available in the dump)
DDSource string `json:"ddsource"` // "crashtracker"
DDTags string `json:"ddtags"` // service,env,version,language_name:go,data_schema_version:...
Error Error `json:"error"`
OSInfo OSInfo `json:"os_info"`
SigInfo *SigInfo `json:"sig_info,omitempty"`
TraceID string `json:"trace_id,omitempty"`
}
Report is the errorsintake payload sent to Datadog Error Tracking on a crash.
type SigInfo ¶
type SigInfo struct {
SiAddr string `json:"si_addr,omitempty"`
SiCode int `json:"si_code,omitempty"`
// SiCodeHuman is declared to match libdatadog's schema, but parseSignal
// does not currently populate it: the code-to-name mapping (SEGV_MAPERR,
// BUS_ADRALN, FPE_INTDIV, ...) is both signal- and platform-specific,
// needing the same kind of per-GOOS table as signalNumbers. Always empty
// today; see TestParseSignalDoesNotPopulateSiCodeHuman.
SiCodeHuman string `json:"si_code_human_readable,omitempty"`
SiSigno int `json:"si_signo,omitempty"`
SiSignoHuman string `json:"si_signo_human_readable,omitempty"`
}
SigInfo holds UNIX signal details for signal-triggered crashes.
type StackTrace ¶
type StackTrace struct {
Format string `json:"format"`
Frames []Frame `json:"frames"`
Incomplete bool `json:"incomplete,omitempty"`
}
StackTrace is the Crashtracking-format structured stack (error.stack object).