open-telemetry / opentelemetry-go

OpenTelemetry Go API and SDK
https://opentelemetry.io/docs/languages/go
Apache License 2.0
5.22k stars 1.05k forks source link

Have simpler SDK initialization API #1484

Open tigrannajaryan opened 3 years ago

tigrannajaryan commented 3 years ago

The calls needed to setup and enable the emitting of telemetry by the SDK are currently quite complicated.

We should have a much simpler API for basic use-cases.

Here is what needs to be done currently to enable telemetry (copied from example code):

func initProvider() func() {
    ctx := context.Background()

    // If the OpenTelemetry Collector is running on a local cluster (minikube or
    // microk8s), it should be accessible through the NodePort service at the
    // `localhost:30080` endpoint. Otherwise, replace `localhost` with the
    // endpoint of your cluster. If you run the app inside k8s, then you can
    // probably connect directly to the service through dns
    driver := otlpgrpc.NewDriver(
        otlpgrpc.WithInsecure(),
        otlpgrpc.WithEndpoint("localhost:30080"),
        otlpgrpc.WithDialOption(grpc.WithBlock()), // useful for testing
    )
    exp, err := otlp.NewExporter(ctx, driver)
    handleErr(err, "failed to create exporter")

    res, err := resource.New(ctx,
        resource.WithAttributes(
            // the service name used to display traces in backends
            semconv.ServiceNameKey.String("test-service"),
        ),
    )
    handleErr(err, "failed to create resource")

    bsp := sdktrace.NewBatchSpanProcessor(exp)
    tracerProvider := sdktrace.NewTracerProvider(
        sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
        sdktrace.WithResource(res),
        sdktrace.WithSpanProcessor(bsp),
    )

    cont := controller.New(
        processor.New(
            simple.NewWithExactDistribution(),
            exp,
        ),
        controller.WithPusher(exp),
        controller.WithCollectPeriod(2*time.Second),
    )

    // set global propagator to tracecontext (the default is no-op).
    otel.SetTextMapPropagator(propagation.TraceContext{})
    otel.SetTracerProvider(tracerProvider)
    otel.SetMeterProvider(cont.MeterProvider())
    handleErr(cont.Start(context.Background()), "failed to start controller")

    return func() {
        // Shutdown will flush any remaining spans.
        handleErr(tracerProvider.Shutdown(ctx), "failed to shutdown TracerProvider")

        // Push any last metric events to the exporter.
        handleErr(cont.Stop(context.Background()), "failed to stop controller")
    }
}

func main() {
    shutdown := initProvider()
    defer shutdown()

        // application code goes here...
}

func handleErr(err error, message string) {
    if err != nil {
        log.Fatalf("%s: %v", message, err)
    }
}

It will be hard for new users of OpenTelemetry to write this initialization code from scratch. They will most likely copy/paste it and modify as needed. However, there is little that needs customization in this code and little decision making that is mandatory for end user to do, which would justify forcing the user to go through this exercise.

I suggest that we come up with a simple "enable" API that has sane defaults for most users.

Here is a possible simple SDK initialization API that we could have.

Minimum version:

// This enables tracing and metrics, by default exporting via insecure OTLP/gRPC
// to localhost:4317, with batcher, default metric collection period.
// Note that OTEL_* environment variables can override these defaults.
shutdown, err := otel.Enable(otel.WithServiceName("test-service"))
handleErr(err, "cannot enable telemetry")
defer shutdown()

Use customized exporter:

driver := otlpgrpc.NewDriver(
    otlpgrpc.WithInsecure(),
    otlpgrpc.WithEndpoint("localhost:30080"),
    otlpgrpc.WithDialOption(grpc.WithBlock()), // useful for testing
)
exp, err := otlp.NewExporter(ctx, driver)
handleErr(err, "failed to create exporter")

// When WithExporter is used OTEL_EXPORTER_* environment variables are ignored.
shutdownFunc, err := otel.Enable(
    otel.WithServiceName("test-service"), 
    otel.WithExporter(exp),
)
handleErr(err, "cannot enable telemetry")
defer shutdownFunc()

Use customized trace provider:

driver := otlpgrpc.NewDriver(
    otlpgrpc.WithInsecure(),
    otlpgrpc.WithEndpoint("localhost:30080"),
    otlpgrpc.WithDialOption(grpc.WithBlock()), // useful for testing
)
exp, err := otlp.NewExporter(ctx, driver)
handleErr(err, "failed to create exporter")

res, err := resource.New(ctx,
    resource.WithAttributes(
        // the service name used to display traces in backends
        semconv.ServiceNameKey.String("test-service"),
    ),
)
handleErr(err, "failed to create resource")

bsp := sdktrace.NewBatchSpanProcessor(exp)
tracerProvider := sdktrace.NewTracerProvider(
    sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}),
    sdktrace.WithResource(res),
    sdktrace.WithSpanProcessor(bsp),
)

shutdownFunc, err := otel.Enable(
    otel.WithTraceProvider(tracerProvider),
    // Note #1: it is an error to use WithExporter or WithServiceName if
    // WithTraceProvider is used.
    // Note #2: metrics are not enable in this case, have to explicitly
    // use WithMeterProvider for that.
    // Note #3: OTEL_* environment variables which control exporting, batching
    // and otherwise configure trace provider or the resource are ignored.
)
handleErr(err, "cannot enable telemetry")
defer shutdownFunc()

etc, etc, with further customization possible up to the full version equivalent to the detailed initialization code that we require today.

Note: the above proposal of simpler API is for illustration only. I will happy with any other alternate that is similarly simple.

tigrannajaryan commented 3 years ago

cc @bogdandrutu @MrAlias

nickzelei commented 7 months ago

Would like to see this as well. I'm looking to use otel in Go and it's quite confusing what is actually required, especially when looking at the compliance matrix: https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md#environment-variables

It seems like Go could simply look at the supported SDK environment variables and completely instrument itself based on what it finds in the environment. This would heavily simplify what is actually required within the Go program itself.