-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Use a V2 API to evolve interfaces #3920
Comments
First roadblock in this adventure. We can't use the same method names in the V2 of the API and have them be directly compatible with V1. The problem is if we modify an interface, for example
The function on the type that implements tracer can only implement Span V1 or Span V2. This is true even if SpanV2 is a superset of SpanV1. Example code: https://go.dev/play/p/wj9GfKoW3B0 This means for the SDK types to directly be usable as both a V1 and a V2, the V2 interfaces will have to use a different name. For example, An alternative solution would be to not have direct implementation, but include a method that allows you to get a V1 tracer from V2. The V2 Tracer might look like:
The goal would then be to have the returned v1.Tracer be a wrapper around the implemented tracer that has the V1 methods. A potential drawback is this might end up with a lot of copying from V1 datatypes to v2. |
Issue with global provider functionsStarting with the sample instrumented application: package main
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
type Application struct {
tracer trace.Tracer
}
func NewApplication() Application {
const name = "Application"
return Application{tracer: otel.Tracer(name)}
}
func main() {
_ = NewApplication()
} Upgrading to package main
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace/v2"
)
type Application struct {
tracer trace.Tracer
}
func NewApplication() Application {
const name = "Application"
return Application{tracer: otel.Tracer(name)}
}
func main() {
_ = NewApplication()
} Causes:
|
Issue with instrumentation options accepting impelementationsStarting with the sample application: package main
import (
sdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
type Application struct {
tracer trace.Tracer
}
func NewApplication(t trace.TracerProvider) Application {
const name = "Application"
return Application{tracer: t.Tracer(name)}
}
func main() {
_ = NewApplication(sdk.NewTracerProvider())
} And upgrading to package main
import (
sdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace/v2"
)
type Application struct {
tracer trace.Tracer
}
func NewApplication(t trace.TracerProvider) Application {
const name = "Application"
return Application{tracer: t.Tracer(name)}
}
func main() {
_ = NewApplication(sdk.NewTracerProvider())
} Causes the following error:
|
From SIG meeting: this looks like a non-starter. We cannot use this approach because of this. |
If we want to solve the global question by having each API manage their own global impelementation, there is an unresolved issue where an instrumentation library imports the v2 global and ask for a provider but the operator has only registered a v1 global. The global packages could be made to return a wrapped version of the v1 provider, but the unimplemented methods would either panic or be no-ops. Both behaviors being something the instrumentation author did not intend. |
Similarly, but instead of extending the interfaces, the func TracerProviderV1(tp TracerProvider) trace.TracerProvider {
return v1TracerProvider{tp}
}
type v1TracerProvider struct {
TracerProvider
}
func (tp v1TracerProvider) Tracer(name string, options ...trace.TracerOption) trace.Tracer {
return v1Tracer{tp.TracerProvider.Tracer(name, v2TracerOptions(options)...)}
}
func v2TracerOptions(opt []trace.TracerOption) []TracerOption {
out := make([]TracerOption, 0, len(opt))
// TODO: implement.
return out
}
type v1Tracer struct {
Tracer
}
func (t v1Tracer) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
ctx, s := t.Tracer.Start(ctx, spanName, v2SpanStartOptions(opts)...)
return ctx, v1Span{s}
}
func v2SpanStartOptions(opt []trace.SpanStartOption) []SpanStartOption {
out := make([]SpanStartOption, 0, len(opt))
// TODO: implement.
return out
}
type v1Span struct {
Span
}
func (s v1Span) End(options ...trace.SpanEndOption)
func (s v1Span) AddEvent(name string, options ...trace.EventOption)
func (s v1Span) IsRecording() bool
func (s v1Span) RecordError(err error, options ...trace.EventOption)
func (s v1Span) SpanContext() trace.SpanContext
func (s v1Span) SetStatus(code codes.Code, description string)
func (s v1Span) SetName(name string)
func (s v1Span) SetAttributes(kv ...attribute.KeyValue)
func (s v1Span) TracerProvider() trace.TracerProvider This would mean the SDK would not simultaneously implement both API versions, but the v2 implementing version could still be used for the v1 with this conversion. This still doesn't solve the issue with a v2 API being provided a v1 only implementing SDK. |
I don't think you have to solve the problem of a v2 API being handed a v1 implementation? In OTel we require you to upgrade your SDK. |
I don't think that's going to work. User's code that start with a v1 implementing SDK used with v1 API instrumentation would compile. They would upgrade to the latest version of the v1 API and the SDK would update to implementing the v2 API. This means by upgrading their SDK it would no longer support their instrumentation because it doesn't implement the v1 API anymore. They would need to go through some modification of their code to effectively "downgrade" their SDK to support an older version of the OTel API. This does not comply with the OTel mission statement:
|
We could also offer a v2 creation function in func NewTracerProviderV2(/*...*/) tracev2.TracerProvider { /*...*/ } The returned The original This has the following downsides:
|
The span context storage in a Currently the active span is stored in a context: opentelemetry-go/trace/context.go Lines 23 to 26 in 2e54fbb
If a v2 API branched, it would have its own This means a trace from instrumentation that uses the v2 API to instrumentation that uses the v1 API and vice versa is going to have incompatibilities. |
This approach means that we would effectively be splitting the SDK and hiding the details within the same package. This doesn't truly address the original requirement that one SDK would implement the v1 and v2 APIs. It means one SDK package, with two hidden implementation, could possibly1 be used to implement both APIs with the help of conversion functions Footnotes
|
Sorry I've been in the airport most of today. Can we sync on Monday and summarize where each of us stands on the different approaches? See if we can come to an agreement on which approach we want to use. |
The plan (from the SIG meeting today) is to summarize our decision and close this proposal as "declined". |
This is a potential solution to #3919
Cutting a new version is a potential solution to adding methods to interfaces. This issue is a collection of requirements for the PoC, and what questions this PoC should answer.
Requirements:
Questions:
cc @katiehockman
The text was updated successfully, but these errors were encountered: