Files
insights-go/error_tracking.go
T
Eli ReismanandGitHub 8a6e9ae51e feat: refactor internal event submission pipeline and test suites (#151)
* intitial refactor:

- Add Context-based graceful shutdown and drain phase for enqueued requests
- Use buffered channels vs. mutex for concurrency mgmt.
- Use atomic.Pointer vs. locks for FF mgmt
- Use Callback ifc vs. lossy internal tracking for unit test result validation
- Add new APIs mirroring current that accept caller's Context to control
  operation timeouts

* expose batch submission timeout with default equiv to old hardcoded timeout

* restructure event submission and batching pipeline

* overhaul tests; implement new stress tests and address coverage gaps

* parallelize tests where safe to do so (3x CI time savings!)

* introduce faster pure Go JSON seralization library

* more refactors: unit tests & benchmarks

* DRY up shutdown req drain and prepareForSend serializer

* use go-json in CLI utils too; remove redundant helpers

* move error type to proper pkg

* DRY up loop() and fix deadlock in old test case

* normalize CI and Makefile test and benchmark target behavviors

* more realistic feature flags benchmark

* replace leaky time.After pattern with idiomatic safer Timers

* internal queue size minimums decoupled from batch and worker configs

* remove dead comments

* fix test bug

* update default http.Transport cfg to avoid conn pool as bottleneck; update batch enqueue limiting strategy and tests/benches

* adjust stress test defaults to match prod

* update flaky tests and rm stale file

* make graceful shutdown timeout opt-in for now

* fix tests that reverted to hold helper utils during rebase

* fix flaky test due to overhead from -race detector now default in test flags

* add more graceful shutdown test cases
2026-01-13 12:22:44 -08:00

158 lines
4.3 KiB
Go

package posthog
import (
"time"
)
var _ Message = (*Exception)(nil)
type Exception struct {
// This field is exported for serialization purposes and shouldn't be set by
// the application, its value is always overwritten by the library.
Type string
DistinctId string
Timestamp time.Time
DisableGeoIP bool
// Typed properties that end up in the API "properties" object:
ExceptionList []ExceptionItem
ExceptionFingerprint *string
}
type ExceptionItem struct {
// Type will be rendered as title in the UI
Type string `json:"type"`
// Value will be rendered as description in the UI
Value string `json:"value"`
Mechanism *ExceptionMechanism `json:"mechanism,omitempty"`
// Stacktrace can conveniently be generated through the use of StackTraceExtractor
Stacktrace *ExceptionStacktrace `json:"stacktrace,omitempty"`
}
type ExceptionMechanism struct {
Handled *bool `json:"handled,omitempty"`
Synthetic *bool `json:"synthetic,omitempty"`
}
type ExceptionStacktrace struct {
Type string `json:"type"`
Frames []StackFrame `json:"frames"`
}
// StackFrame represents a single "Frame" within a stack trace.
// Documentation about the available fields can be found here:
// https://github.com/PostHog/posthog/blob/39b9326320c23acbdc6e96a8beb41b30d3c99099/rust/cymbal/src/langs/go.rs#L7
type StackFrame struct {
Filename string `json:"filename"`
LineNo int `json:"lineno"`
Function string `json:"function"`
InApp bool `json:"in_app"`
Synthetic bool `json:"synthetic"`
Platform string `json:"platform"`
}
type ExceptionInApi struct {
Type string `json:"type"`
Library string `json:"library"`
LibraryVersion string `json:"library_version"`
Timestamp time.Time `json:"timestamp"`
Event string `json:"event"`
Properties ExceptionInApiProperties `json:"properties"`
}
type ExceptionInApiProperties struct {
Lib string `json:"$lib"`
LibVersion string `json:"$lib_version"`
DistinctId string `json:"distinct_id"`
DisableGeoIP bool `json:"$geoip_disable,omitempty"`
ExceptionList []ExceptionItem `json:"$exception_list"`
ExceptionFingerprint *string `json:"$exception_fingerprint,omitempty"`
}
func (msg Exception) internal() { panic(unimplementedError) }
func (msg Exception) Validate() error {
if len(msg.DistinctId) == 0 {
return FieldError{
Type: "posthog.Exception",
Name: "DistinctId",
Value: msg.DistinctId,
}
}
if len(msg.ExceptionList) == 0 {
return FieldError{
Type: "posthog.Exception",
Name: "ExceptionList",
Value: []ExceptionItem{},
}
}
for _, item := range msg.ExceptionList {
if err := item.Validate(); err != nil {
return err
}
}
return nil
}
func (msg ExceptionItem) Validate() error {
if msg.Type == "" {
return FieldError{
Type: "posthog.Exception",
Name: "Type",
Value: msg.Type,
}
}
if msg.Value == "" {
return FieldError{
Type: "posthog.Exception",
Name: "Value",
Value: msg.Value,
}
}
return nil
}
func (msg Exception) APIfy() APIMessage {
libVersion := getVersion()
return ExceptionInApi{
Type: msg.Type, // set to "exception" by Enqueue switch
Event: "$exception",
Library: SDKName,
LibraryVersion: libVersion,
Timestamp: msg.Timestamp,
Properties: ExceptionInApiProperties{
Lib: SDKName,
LibVersion: libVersion,
DistinctId: msg.DistinctId,
DisableGeoIP: msg.DisableGeoIP,
ExceptionList: msg.ExceptionList,
ExceptionFingerprint: msg.ExceptionFingerprint,
},
}
}
// NewDefaultException is a convenience function to build an Exception object (usable for `client.Enqueue`)
// with sane defaults. If you want more control, please manually build the Exception object.
func NewDefaultException(
timestamp time.Time,
distinctID, title, description string,
) Exception {
defaultStackTrace := DefaultStackTraceExtractor{InAppDecider: SimpleInAppDecider}
return Exception{
DistinctId: distinctID,
Timestamp: timestamp,
ExceptionList: []ExceptionItem{
{
Type: title,
Value: description,
Stacktrace: defaultStackTrace.GetStackTrace(3),
},
},
}
}