diff --git a/.chloggen/partial_error.yaml b/.chloggen/partial_error.yaml new file mode 100644 index 00000000000..84c0f6dc1d9 --- /dev/null +++ b/.chloggen/partial_error.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: consumer/consumererror/xconsumererror + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add new Partial error, allowing consumers to express a permanent error with a failed item count. + +# One or more tracking issues or pull requests related to the change +issues: [13423] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [api] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 80aea4f3cd3..838159785e6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,6 +25,17 @@ cmd/mdatagen/internal/samplefactoryreceiver/ @open-telemetry/collector-approvers cmd/mdatagen/internal/sampleprocessor/ @open-telemetry/collector-approvers cmd/mdatagen/internal/samplereceiver/ @open-telemetry/collector-approvers @dmitryax cmd/mdatagen/internal/samplescraper/ @open-telemetry/collector-approvers @dmitryax +config/configauth/ @open-telemetry/collector-approvers +config/configcompression/ @open-telemetry/collector-approvers +config/configgrpc/ @open-telemetry/collector-approvers +config/confighttp/ @open-telemetry/collector-approvers +config/configmiddleware/ @open-telemetry/collector-approvers +config/confignet/ @open-telemetry/collector-approvers +config/configopaque/ @open-telemetry/collector-approvers +config/configoptional/ @open-telemetry/collector-approvers +config/configretry/ @open-telemetry/collector-approvers +config/configtelemetry/ @open-telemetry/collector-approvers +config/configtls/ @open-telemetry/collector-approvers confmap/ @open-telemetry/collector-approvers @mx-psi @evan-bradley confmap/provider/envprovider/ @open-telemetry/collector-approvers confmap/provider/fileprovider/ @open-telemetry/collector-approvers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index bba1d2ed876..71b01562ee7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -25,6 +25,17 @@ body: - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper + - config/configauth + - config/configcompression + - config/configgrpc + - config/confighttp + - config/configmiddleware + - config/confignet + - config/configopaque + - config/configoptional + - config/configretry + - config/configtelemetry + - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 2b10eda576f..608f7a9d240 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -19,6 +19,17 @@ body: - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper + - config/configauth + - config/configcompression + - config/configgrpc + - config/confighttp + - config/configmiddleware + - config/confignet + - config/configopaque + - config/configoptional + - config/configretry + - config/configtelemetry + - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index 8ec6a71b6e7..b41db90ae2f 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -18,6 +18,17 @@ body: - cmd/mdatagen/internal/sampleprocessor - cmd/mdatagen/internal/samplereceiver - cmd/mdatagen/internal/samplescraper + - config/configauth + - config/configcompression + - config/configgrpc + - config/confighttp + - config/configmiddleware + - config/confignet + - config/configopaque + - config/configoptional + - config/configretry + - config/configtelemetry + - config/configtls - confmap - confmap/provider/envprovider - confmap/provider/fileprovider diff --git a/consumer/consumererror/xconsumererror/go.mod b/consumer/consumererror/xconsumererror/go.mod index c05bfbcaa79..4abbbb50269 100644 --- a/consumer/consumererror/xconsumererror/go.mod +++ b/consumer/consumererror/xconsumererror/go.mod @@ -19,6 +19,10 @@ require ( go.opentelemetry.io/collector/featuregate v1.45.0 // indirect go.opentelemetry.io/collector/pdata v1.45.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sys v0.34.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/consumer/consumererror/xconsumererror/go.sum b/consumer/consumererror/xconsumererror/go.sum index 903bf673575..5f7394205ee 100644 --- a/consumer/consumererror/xconsumererror/go.sum +++ b/consumer/consumererror/xconsumererror/go.sum @@ -1,6 +1,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -34,6 +38,16 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/consumer/consumererror/xconsumererror/partial.go b/consumer/consumererror/xconsumererror/partial.go new file mode 100644 index 00000000000..2cbe84c34ac --- /dev/null +++ b/consumer/consumererror/xconsumererror/partial.go @@ -0,0 +1,56 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xconsumererror // import "go.opentelemetry.io/collector/consumer/consumererror/xconsumererror" + +import ( + "errors" + + "go.opentelemetry.io/collector/consumer/consumererror" +) + +type partialError struct { + inner error + failed int +} + +var _ error = partialError{} + +func (pe partialError) Error() string { + return pe.inner.Error() +} + +func (pe partialError) Unwrap() error { + return pe.inner +} + +// NewPartial creates a new partial error. This is used to report errors +// where only a subset of the total items failed to be written, but it +// is not possible to tell which particular items failed. An example of this +// would be a backend that can report partial successes, but only communicate +// the number of failed items without specifying which specific items failed. +// +// A partial error wraps a PermanentError; it can be treated as any other permanent +// error with no changes, meaning that consumers can transition to producing this +// error when appropriate without breaking any parts of the pipeline that check for +// permanent errors. +// +// In a scenario where the exact items that failed are known and can be retried, +// it's recommended to use the respective signal error ([consumererror.Logs], +// [consumererror.Metrics], or [consumererror.Traces]). +func NewPartial(err error, failed int) error { + return consumererror.NewPermanent(partialError{ + inner: err, + failed: failed, + }) +} + +// IsPartial checks if an error was wrapped with the NewPartial function, +// or if it contains one such error in its Unwrap() tree. The results are +// the count of failed items if the error is a partial error, and a boolean +// result of whether the error was a partial or not. +func IsPartial(err error) (int, bool) { + var pe partialError + ok := errors.As(err, &pe) + return pe.failed, ok +} diff --git a/consumer/consumererror/xconsumererror/partial_test.go b/consumer/consumererror/xconsumererror/partial_test.go new file mode 100644 index 00000000000..5abc6050423 --- /dev/null +++ b/consumer/consumererror/xconsumererror/partial_test.go @@ -0,0 +1,65 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xconsumererror_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/consumer/consumererror" + "go.opentelemetry.io/collector/consumer/consumererror/xconsumererror" +) + +func TestPartial(t *testing.T) { + internalErr := errors.New("some points failed") + err := xconsumererror.NewPartial(internalErr, 5) + // The partial error must be wrapped and be able to be + // treated as a plain permanent error. + require.True(t, consumererror.IsPermanent(err)) + // The error should be unwrappable to verify the internal + // error if necessary. + require.ErrorIs(t, err, internalErr) + // It must be possible to retrieve the failed items from + // the partial error. + failed, ok := xconsumererror.IsPartial(err) + require.True(t, ok) + require.Equal(t, 5, failed) +} + +func ExampleNewPartial() { + // Produce a partial error. + failedItems := 5 + consumptionErr := errors.New("some points failed to be written") + err := xconsumererror.NewPartial(consumptionErr, failedItems) + fmt.Println(err) + + // Output: Permanent error: some points failed to be written +} + +func ExampleIsPartial() { + // Produce a partial error. + partialErr := xconsumererror.NewPartial(errors.New("some points failed to be written"), 10) + + // IsPartial will return the failed item count, and a boolean + // to indicate whether it is a partial error or not. + if count, ok := xconsumererror.IsPartial(partialErr); ok { + fmt.Println(count) + } + + // Output: 10 +} + +func ExampleIsPartial_second() { + // Produce a partial error. + partialErr := xconsumererror.NewPartial(errors.New("some points failed to be written"), 10) + + // Partial errors wrap a Permanent error, so it can simply be treated + // as one if the number of failed items isn't important. + fmt.Println(consumererror.IsPermanent(partialErr)) + + // Output: true +}