mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
* debrand: signoz/SigNoz/SIGNOZ -> o11y/O11y/O11Y across the tree Rename all SigNoz *branding* to O11y in file contents, file/dir names, package names, env vars, config keys, comments, docs, SDK code, and the ClickHouse schema identifiers the querier reads. Collector dependency: - Repoint github.com/hanzoai/signoz-otel-collector -> github.com/hanzoai/otel-collector and bump v0.144.6 -> v0.144.7 (go.mod + go.sum reconciled via go mod tidy). Internal package paths also debranded upstream: signozschemamigrator -> o11yschemamigrator, signozlogspipelineprocessor -> o11ylogspipelineprocessor. - Fix: v0.144.7 dropped otelconst.DistributedFieldKeysTable from the collector's public constants; pin FieldKeysTable = "distributed_field_keys" locally in pkg/telemetrymetadata/tables.go. Package/dir renames (package decl + all importers): pkg/apiserver/signozapiserver -> o11yapiserver, plus signozalertmanager, signozauthzapi, signozglobal, signozquerier, signozruler, and pkg/signoz -> pkg/o11y. Schema (read plane): signoz_traces/metrics/logs/metadata/analytics/meter and signoz_index_v3/index_v2/error_index_v2/spans (+ distributed_*) -> o11y_*. Data-preserving cutover migration added: deploy/clickhouse/migrations/0001_rename_signoz_to_o11y.sql (metadata-only RENAME DATABASE/TABLE, no drop/recreate). Preserved (attribution / external / wire contracts): - LICENSE + NOTICE verbatim (incl. "software from SigNoz", "Copyright ... SigNoz Inc."). - Upstream github.com/SigNoz/* repo URLs + @SigNoz/* CODEOWNERS teams. - Billing resource-attribute regex "signoz.workspace.*" (external wire contract). go build ./pkg/... ./cmd/... = 0; gofmt clean; renamed+schema pkg tests pass. * o11y: bump collector to v0.144.8 (o11y_* physical schema writer) + lockstep deploy plan Collector v0.144.8's schema-migrator + exporters now CREATE/WRITE the same o11y_* physical databases/tables the querier reads and the RENAME migration (0001_rename_signoz_to_o11y.sql) targets. Cross-checked byte-identical: writer DB set == migration target DB set (6); writer prefixed-table set == migration target table set (8); o11y querier reads and cloud reads are subsets of that set. Zero table-name mismatches between writer, readers, and migration. Collector go.mod unchanged between v0.144.7 and v0.144.8 (identical go.mod hash) — pure source rename, no module-graph change. Adds deploy/clickhouse/DEPLOY_signoz_to_o11y_cutover.md: the lockstep order (collector v0.144.8 -> RENAME migration -> o11y+cloud readers) with a fresh/scratch-ClickHouse pre-flight (create via migrator, emit trace+metric, query back) and rollback. Ships together or telemetry blackholes. go build ./pkg/... ./cmd/community = 0.
202 lines
7.1 KiB
Go
202 lines
7.1 KiB
Go
package errors
|
|
|
|
import (
|
|
"context"
|
|
"errors" //nolint:depguard
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
typ := typ{"test-error"}
|
|
err := New(typ, MustNewCode("code"), "test error info")
|
|
assert.NotNil(t, err)
|
|
}
|
|
|
|
func TestNewf(t *testing.T) {
|
|
typ := typ{"test-error"}
|
|
err := Newf(typ, MustNewCode("test_code"), "test error info with %s", "string")
|
|
assert.NotNil(t, err)
|
|
assert.Equal(t, "test error info with string", err.Error())
|
|
}
|
|
|
|
func TestWrapf(t *testing.T) {
|
|
typ := typ{"test-error"}
|
|
err := Wrapf(errors.New("original error"), typ, MustNewCode("test_code"), "info for err %d", 2)
|
|
assert.NotNil(t, err)
|
|
}
|
|
|
|
func TestError(t *testing.T) {
|
|
typ := typ{"test-error"}
|
|
err1 := New(typ, MustNewCode("test_code"), "info for err1")
|
|
assert.Equal(t, "info for err1", err1.Error())
|
|
|
|
err2 := Wrapf(err1, typ, MustNewCode("test_code"), "info for err2")
|
|
assert.Equal(t, "info for err1", err2.Error())
|
|
}
|
|
|
|
func TestUnwrapb(t *testing.T) {
|
|
typ := typ{"test-error"}
|
|
oerr := errors.New("original error")
|
|
berr := Wrapf(oerr, typ, MustNewCode("test_code"), "this is a base err").WithUrl("https://docs").WithAdditional("additional err")
|
|
|
|
atyp, acode, amessage, aerr, au, aa := Unwrapb(berr)
|
|
assert.Equal(t, typ, atyp)
|
|
assert.Equal(t, "test_code", acode.String())
|
|
assert.Equal(t, "this is a base err", amessage)
|
|
assert.Equal(t, oerr, aerr)
|
|
assert.Equal(t, "https://docs", au)
|
|
assert.Equal(t, []additional{{message: "additional err"}}, aa)
|
|
|
|
atyp, _, _, _, _, _ = Unwrapb(oerr)
|
|
assert.Equal(t, TypeInternal, atyp)
|
|
}
|
|
|
|
func TestAttr(t *testing.T) {
|
|
err := New(TypeInternal, MustNewCode("test_code"), "test error")
|
|
attr := Attr(err)
|
|
assert.Equal(t, "exception", attr.Key)
|
|
assert.Equal(t, err, attr.Value.Any())
|
|
}
|
|
|
|
func TestWithSuggestions(t *testing.T) {
|
|
err := New(TypeInternal, MustNewCode("test_code"), "test error").WithSuggestions("try this")
|
|
assert.Equal(t, []string{"try this"}, suggestionsOf(err))
|
|
|
|
// WithSuggestions replaces the existing list.
|
|
err = err.WithSuggestions("try this instead")
|
|
assert.Equal(t, []string{"try this instead"}, suggestionsOf(err))
|
|
|
|
// Variadic form replaces with multiple entries.
|
|
err = err.WithSuggestions("first", "second")
|
|
assert.Equal(t, []string{"first", "second"}, suggestionsOf(err))
|
|
}
|
|
|
|
func TestWithSuggestiveAdditional(t *testing.T) {
|
|
// WithSuggestiveAdditional attaches suggestions to a specific detail (in the
|
|
// errors array), distinct from the error-wide WithSuggestions.
|
|
err := NewInvalidInputf(MustNewCode("bad_field"), "unknown field %q", "filed").
|
|
WithSuggestiveAdditional("field `filed` not found", "did you mean: `field`")
|
|
|
|
j := AsJSON(err)
|
|
assert.Equal(t, []responseerroradditional{
|
|
{Message: "field `filed` not found", Suggestions: []string{"did you mean: `field`"}},
|
|
}, j.Errors)
|
|
assert.Empty(t, j.Suggestions, "detail-scoped suggestions must not leak into the error-wide list")
|
|
}
|
|
|
|
func TestWithRetryAfter(t *testing.T) {
|
|
err := New(TypeInternal, MustNewCode("test_code"), "test error").WithRetryAfter(5 * time.Microsecond)
|
|
r := retryOf(err)
|
|
|
|
assert.Equal(t, 5, int(r.delay.Microseconds()))
|
|
}
|
|
|
|
func TestAsJSONBaseError(t *testing.T) {
|
|
err := New(TypeInvalidInput, MustNewCode("bad_input"), "field foo is bad").
|
|
WithUrl("https://docs/bad_input").
|
|
WithAdditional("hint1", "hint2").
|
|
WithSuggestions("try this")
|
|
|
|
j := AsJSON(err)
|
|
|
|
assert.Equal(t, "invalid-input", j.Type)
|
|
assert.Equal(t, "bad_input", j.Code)
|
|
assert.Equal(t, "field foo is bad", j.Message)
|
|
assert.Equal(t, "https://docs/bad_input", j.Url)
|
|
// A detail with no suggestions carries an empty (non-nil) slice — the
|
|
// suggestions field is non-nullable, so it marshals to [] rather than null.
|
|
assert.Equal(t, []responseerroradditional{
|
|
{Message: "hint1", Suggestions: []string{}},
|
|
{Message: "hint2", Suggestions: []string{}},
|
|
}, j.Errors)
|
|
|
|
// InvalidInput auto-applies the after_fix policy via NewInvalidInputf — but
|
|
// New (bare constructor) does not. The retry block should reflect that.
|
|
assert.Nil(t, j.Retry, "bare New(...) should not populate a retry block")
|
|
|
|
assert.Equal(t, []string{"try this"}, j.Suggestions)
|
|
}
|
|
|
|
func TestAsJSONWrappedErrorPreservesHints(t *testing.T) {
|
|
// An inner base carries the user-facing hints (e.g. produced inside an
|
|
// UnmarshalJSON), then gets re-wrapped (e.g. WrapInvalidInputf). suggestionsOf
|
|
// must walk the cause chain so the hints still surface.
|
|
inner := NewInvalidInputf(MustNewCode("bad_kind"), "unknown panel kind %q", "boom").
|
|
WithSuggestions("valid references: a, b, c")
|
|
|
|
wrapped := WrapInvalidInputf(inner, MustNewCode("outer"), "%s", inner.Error())
|
|
|
|
j := AsJSON(wrapped)
|
|
assert.Equal(t, []string{"valid references: a, b, c"}, j.Suggestions,
|
|
"suggestions on an inner base must survive wrapping")
|
|
}
|
|
|
|
func TestAsJSONRetryBlock(t *testing.T) {
|
|
t.Run("RetryAfterIncludesDuration", func(t *testing.T) {
|
|
err := NewTimeoutf(MustNewCode("slow"), "slow").WithRetryAfter(5 * time.Second)
|
|
j := AsJSON(err)
|
|
require.NotNil(t, j.Retry)
|
|
assert.Equal(t, 5*time.Second, j.Retry.Delay)
|
|
})
|
|
|
|
t.Run("NonAfterPolicyOmitsDurationField", func(t *testing.T) {
|
|
// NewInvalidInputf auto-applies retryAfterFix via the constructor helper.
|
|
err := NewInvalidInputf(MustNewCode("bad"), "bad")
|
|
j := AsJSON(err)
|
|
require.Nil(t, j.Retry, "retry must be empty")
|
|
})
|
|
|
|
t.Run("BareErrorOmitsRetryBlock", func(t *testing.T) {
|
|
err := New(TypeInternal, MustNewCode("boom"), "boom")
|
|
j := AsJSON(err)
|
|
assert.Nil(t, j.Retry, "bare New(...) without WithRetry* must omit retry")
|
|
})
|
|
|
|
t.Run("NonBaseErrorOmitsRetryBlock", func(t *testing.T) {
|
|
// Stdlib errors carry no retry metadata; AsJSON omits the retry block.
|
|
j := AsJSON(errors.New("plain stdlib error"))
|
|
assert.Nil(t, j.Retry, "non-base errors must omit the retry block")
|
|
})
|
|
}
|
|
|
|
func TestAsJSONEmptyWhenNoneSet(t *testing.T) {
|
|
// errors and suggestions are non-nullable in the OpenAPI spec, so AsJSON
|
|
// leaves them as empty (non-nil) slices when the error carries none — they
|
|
// marshal to [] rather than null.
|
|
j := AsJSON(New(TypeInternal, MustNewCode("boom"), "boom"))
|
|
|
|
assert.NotNil(t, j.Suggestions)
|
|
assert.Empty(t, j.Suggestions)
|
|
assert.NotNil(t, j.Errors)
|
|
assert.Empty(t, j.Errors)
|
|
}
|
|
|
|
func TestWithStacktrace(t *testing.T) {
|
|
err := New(TypeInternal, MustNewCode("test_code"), "panic").WithStacktrace("custom stack trace")
|
|
|
|
assert.Equal(t, "custom stack trace", err.Stacktrace())
|
|
assert.Equal(t, "panic", err.Error())
|
|
|
|
typ, code, message, _, _, _ := Unwrapb(err)
|
|
assert.Equal(t, TypeInternal, typ)
|
|
assert.Equal(t, "test_code", code.String())
|
|
assert.Equal(t, "panic", message)
|
|
}
|
|
|
|
// Wrapped context sentinels must remain detectable via errors.Is so callers
|
|
// that branch on context.Canceled / context.DeadlineExceeded keep working
|
|
// after the error passes through one of the o11y Wrap* helpers.
|
|
func TestWrapPreservesContextSentinels(t *testing.T) {
|
|
canceled := WrapCanceledf(context.Canceled, MustNewCode("canceled"), "op canceled")
|
|
assert.True(t, Is(canceled, context.Canceled))
|
|
assert.False(t, Is(canceled, context.DeadlineExceeded))
|
|
|
|
deadline := WrapTimeoutf(context.DeadlineExceeded, MustNewCode("timeout"), "op timed out")
|
|
assert.True(t, Is(deadline, context.DeadlineExceeded))
|
|
assert.False(t, Is(deadline, context.Canceled))
|
|
}
|