Files
insights-go/error_tracking_stack_trace.go
T
43ebe1ce78 feat: Introduce error tracking with slog integration (#120)
* docs: Describe the new error tracing functionality within the README.md

* fix: Added the new error capturing logic within the examples

* chore: Added the fixture for the enqueuing of exception test

* fix: Introduce Enqueue client to reduce the scope of what callees can do with the client and improve modularity / testability

* fix: Wire up the exception flow within the posthog client

* test: Wire up the exception logic in the existing posthog client test to adhere with the other structures there

* test: Introduce a simple test utils file where we can store (testfunctions that are used across several different tests

* test: Introduce a single location where we can store mocks / stubs to keep the mental burden of the main test files to a reasonable level and avoid cluttering those tests

* feat: Added the models used for the error tracking API endpoint, adhered to the style that was present in the other enqueue-able objects

* tests: Added tests for the newly introduced error tracking models to cover the validation logic

* chore: Add functional options to configure the slog logger

* feat: Added a slog wrapper which will mirror exceptions to the PostHog cloud

* test: Added tests for the slog logger wrapper

* feat: Added stack trace retrievers to enrich the exception in the UI

* test: Added a test to cover the newly added stack trace logic

* fix: Safeguarding for cases where Func is nil

* feat: Use raw frames instead of resolved frames now that the API supports it

---------

Co-authored-by: Akatchi <[email protected]>
Co-authored-by: Oliver Browne <[email protected]>
2025-09-04 11:24:30 +03:00

80 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package posthog
import (
"fmt"
"path/filepath"
"runtime"
"strings"
)
// InAppDecider reports whether a stack frame should be considered “in-app”
// (i.e., part of the calling application’s own code) as opposed to external
// dependencies, standard library, or tooling internals. The UI can use this
// signal to visually emphasize frames that are likely actionable.
type InAppDecider func(frame runtime.Frame) bool
// SimpleInAppDecider a naive implementation of InAppDecider which checks the file paths.
// It's good enough for 90% of the cases.
func SimpleInAppDecider(frame runtime.Frame) bool {
f := frame.File
sep := string(filepath.Separator)
if strings.Contains(f, "pkg/mod/golang.org/toolchain") ||
strings.Contains(f, fmt.Sprintf("%[1]svendor%[1]s", sep)) ||
strings.Contains(f, fmt.Sprintf("%[1]spkg%[1]smod%[1]s", sep)) {
return false
}
return true
}
// StackTraceExtractor produces a stack trace for the current goroutine to
// enrich captured exceptions with call-site context.
type StackTraceExtractor interface {
// GetStackTrace returns a PostHog-compatible stack trace.
//
// The skip parameter controls how many leading frames to omit before
// recording. Use it to drop extractor/logging internals and start at the
// application call site. For example, a skip of 3–5 is typically enough to
// hide wrapper layers when called from a slog handler.
GetStackTrace(skip int) *ExceptionStacktrace
}
// DefaultStackTraceExtractor is provided by default as a sane / simple implementation
// of a StackTraceExtractor. It should be enough for most use cases, however, you're free to
// create your own implementation if you require more flexibility.
type DefaultStackTraceExtractor struct {
InAppDecider InAppDecider
}
func (d DefaultStackTraceExtractor) GetStackTrace(skip int) *ExceptionStacktrace {
pcs := make([]uintptr, 64)
stackCallCount := runtime.Callers(skip, pcs)
frames := runtime.CallersFrames(pcs[:stackCallCount])
traces := make([]StackFrame, 0, stackCallCount)
for {
frame, hasMore := frames.Next()
if frame == *new(runtime.Frame) {
break
}
traces = append(traces, StackFrame{
Filename: frame.File,
LineNo: frame.Line,
Function: frame.Function,
InApp: d.InAppDecider(frame),
Synthetic: false,
Platform: "go",
})
if !hasMore {
break
}
}
return &ExceptionStacktrace{
Type: "raw",
Frames: traces,
}
}