Phase 4 of #8011. Depends on #8017.
Summary
Introduce a small in-tree internal/azderrors package that captures a single call-site frame at error construction/wrap time, and emit it as additive span attributes (error.origin.func, error.origin.file, error.origin.line).
This directly answers the user-facing "where in azd did this come from?" question without adopting any external library. Single-frame capture via runtime.Caller(1) is ~100ns per call and zero allocations beyond the error struct itself.
A future Phase 7 issue (#8078) considers a full return-path / stacktrace solution if single-frame coverage proves insufficient.
API surface
package azderrors
type Error struct {
cls Classification // from internal/tracing/errclassify
cause error
msg string
pc [1]uintptr // single-frame call-site
}
func (e *Error) Error() string // full message
func (e *Error) Unwrap() error // cause
func (e *Error) Classify() Classification // for the shared classifier
func (e *Error) Frame() runtime.Frame // resolved on demand
func New(cls Classification, format string, args ...any) error
func Wrap(err error, cls Classification, format string, args ...any) error
func Errorf(cls Classification, format string, args ...any) error
func WithCode(err error, code string) error
Each constructor calls runtime.Callers(2, e.pc[:]) so e.Frame() returns the caller of New/Wrap/Errorf. Frames are resolved lazily when the classifier reads them.
Compatibility with stdlib errors is full: errors.Is, errors.As, errors.AsType[*azderrors.Error], errors.Unwrap all behave normally.
Telemetry attributes
When the chain contains an *azderrors.Error with a captured frame, emit:
| Key |
Type |
Source |
Example |
error.origin.func |
string |
runtime.Frame.Function |
github.com/azure/azure-dev/cli/azd/pkg/project.(*Manager).Load |
error.origin.file |
string |
module-relative path |
pkg/project/manager.go |
error.origin.line |
int |
runtime.Frame.Line |
142 |
Walk the chain outermost-first; emit the first *azderrors.Error frame found.
Resolve runtime.Frame.File to a module-relative path via runtime/debug.ReadBuildInfo so dev-build telemetry is consistent with release builds (which already strip absolute paths via -trimpath). Fall back to filepath.Base(frame.File) if module info is unavailable.
Scope
Tests
- Unit tests for
New/Wrap/Errorf frame capture (assert correct file/line).
- Unit tests for the chain walker that picks up the first
*azderrors.Error frame.
- AppInsights serialization tests for the three new attributes.
Phase 4 of #8011. Depends on #8017.
Summary
Introduce a small in-tree
internal/azderrorspackage that captures a single call-site frame at error construction/wrap time, and emit it as additive span attributes (error.origin.func,error.origin.file,error.origin.line).This directly answers the user-facing "where in azd did this come from?" question without adopting any external library. Single-frame capture via
runtime.Caller(1)is ~100ns per call and zero allocations beyond the error struct itself.A future Phase 7 issue (#8078) considers a full return-path / stacktrace solution if single-frame coverage proves insufficient.
API surface
Each constructor calls
runtime.Callers(2, e.pc[:])soe.Frame()returns the caller ofNew/Wrap/Errorf. Frames are resolved lazily when the classifier reads them.Compatibility with stdlib errors is full:
errors.Is,errors.As,errors.AsType[*azderrors.Error],errors.Unwrapall behave normally.Telemetry attributes
When the chain contains an
*azderrors.Errorwith a captured frame, emit:error.origin.funcruntime.Frame.Functiongithub.com/azure/azure-dev/cli/azd/pkg/project.(*Manager).Loaderror.origin.filepkg/project/manager.goerror.origin.lineruntime.Frame.Line142Walk the chain outermost-first; emit the first
*azderrors.Errorframe found.Resolve
runtime.Frame.Fileto a module-relative path viaruntime/debug.ReadBuildInfoso dev-build telemetry is consistent with release builds (which already strip absolute paths via-trimpath). Fall back tofilepath.Base(frame.File)if module info is unavailable.Scope
azderrorsbased on Kusto data #8079 (Phase 5), to be picked up after Adderror.chain.typesspan attribute #8015 ships and Kusto data is available.cli/azd/extensions/) could potentially adopt the same primitives later, but don't actively wire anything across the gRPC boundary in this phase.Tests
New/Wrap/Errorfframe capture (assert correct file/line).*azderrors.Errorframe.