# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
"""

from os import environ
from typing import Sequence, Tuple

from pkg_resources import iter_entry_points

from opentelemetry import trace
from opentelemetry.environment_variables import (
    OTEL_PYTHON_ID_GENERATOR,
    OTEL_TRACES_EXPORTER,
)
from opentelemetry.instrumentation.configurator import BaseConfigurator
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_SPAN = "otlp_proto_grpc_span"

_RANDOM_ID_GENERATOR = "random"
_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR


def _get_id_generator() -> str:
    return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR)


def _get_exporter_names() -> Sequence[str]:
    trace_exporters = environ.get(OTEL_TRACES_EXPORTER)

    exporters = set()

    if trace_exporters and trace_exporters.lower().strip() != "none":
        exporters.update(
            {
                trace_exporter.strip()
                for trace_exporter in trace_exporters.split(",")
            }
        )

    if _EXPORTER_OTLP in exporters:
        exporters.remove(_EXPORTER_OTLP)
        exporters.add(_EXPORTER_OTLP_SPAN)

    return list(exporters)


def _init_tracing(
    exporters: Sequence[SpanExporter], id_generator: IdGenerator
):
    # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
    # from the env variable else defaults to "unknown_service"
    provider = TracerProvider(
        id_generator=id_generator(),
    )
    trace.set_tracer_provider(provider)

    for _, exporter_class in exporters.items():
        exporter_args = {}
        provider.add_span_processor(
            BatchSpanProcessor(exporter_class(**exporter_args))
        )


def _import_tracer_provider_config_components(
    selected_components, entry_point_name
) -> Sequence[Tuple[str, object]]:
    component_entry_points = {
        ep.name: ep for ep in iter_entry_points(entry_point_name)
    }
    component_impls = []
    for selected_component in selected_components:
        entry_point = component_entry_points.get(selected_component, None)
        if not entry_point:
            raise RuntimeError(
                f"Requested component '{selected_component}' not found in entry points for '{entry_point_name}'"
            )

        component_impl = entry_point.load()
        component_impls.append((selected_component, component_impl))

    return component_impls


def _import_exporters(
    exporter_names: Sequence[str],
) -> Sequence[SpanExporter]:
    trace_exporters = {}

    for (
        exporter_name,
        exporter_impl,
    ) in _import_tracer_provider_config_components(
        exporter_names, "opentelemetry_exporter"
    ):
        if issubclass(exporter_impl, SpanExporter):
            trace_exporters[exporter_name] = exporter_impl
        else:
            raise RuntimeError(f"{exporter_name} is not a trace exporter")
    return trace_exporters


def _import_id_generator(id_generator_name: str) -> IdGenerator:
    # pylint: disable=unbalanced-tuple-unpacking
    [
        (id_generator_name, id_generator_impl)
    ] = _import_tracer_provider_config_components(
        [id_generator_name.strip()], "opentelemetry_id_generator"
    )

    if issubclass(id_generator_impl, IdGenerator):
        return id_generator_impl

    raise RuntimeError(f"{id_generator_name} is not an IdGenerator")


def _initialize_components():
    exporter_names = _get_exporter_names()
    trace_exporters = _import_exporters(exporter_names)
    id_generator_name = _get_id_generator()
    id_generator = _import_id_generator(id_generator_name)
    _init_tracing(trace_exporters, id_generator)


class _OTelSDKConfigurator(BaseConfigurator):
    """A basic Configurator by OTel Python for initalizing OTel SDK components

    Initializes several crucial OTel SDK components (i.e. TracerProvider,
    MeterProvider, Processors...) according to a default implementation. Other
    Configurators can subclass and slightly alter this initialization.

    NOTE: This class should not be instantiated nor should it become an entry
    point on the `opentelemetry-sdk` package. Instead, distros should subclass
    this Configurator and enchance it as needed.
    """

    def _configure(self, **kwargs):
        _initialize_components()

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
OpenTelemetry SDK Configurator for Easy Instrumentation with Distros
"""

from os import environ
from typing import Sequence, Tuple

from pkg_resources import iter_entry_points

from opentelemetry import trace
from opentelemetry.environment_variables import (
    OTEL_PYTHON_ID_GENERATOR,
    OTEL_TRACES_EXPORTER,
)
from opentelemetry.instrumentation.configurator import BaseConfigurator
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_SPAN = "otlp_proto_grpc_span"

_RANDOM_ID_GENERATOR = "random"
_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR


def _get_id_generator() -> str:
    return environ.get(OTEL_PYTHON_ID_GENERATOR, _DEFAULT_ID_GENERATOR)


def _get_exporter_names() -> Sequence[str]:
    trace_exporters = environ.get(OTEL_TRACES_EXPORTER)

    exporters = set()

    if trace_exporters and trace_exporters.lower().strip() != "none":
        exporters.update(
            {
                trace_exporter.strip()
                for trace_exporter in trace_exporters.split(",")
            }
        )

    if _EXPORTER_OTLP in exporters:
        exporters.remove(_EXPORTER_OTLP)
        exporters.add(_EXPORTER_OTLP_SPAN)

    return list(exporters)


def _init_tracing(
    exporters: Sequence[SpanExporter], id_generator: IdGenerator
):
    # if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
    # from the env variable else defaults to "unknown_service"
    provider = TracerProvider(
        id_generator=id_generator(),
    )
    trace.set_tracer_provider(provider)

    for _, exporter_class in exporters.items():
        exporter_args = {}
        provider.add_span_processor(
            BatchSpanProcessor(exporter_class(**exporter_args))
        )


def _import_tracer_provider_config_components(
    selected_components, entry_point_name
) -> Sequence[Tuple[str, object]]:
    component_entry_points = {
        ep.name: ep for ep in iter_entry_points(entry_point_name)
    }
    component_impls = []
    for selected_component in selected_components:
        entry_point = component_entry_points.get(selected_component, None)
        if not entry_point:
            raise RuntimeError(
                f"Requested component '{selected_component}' not found in entry points for '{entry_point_name}'"
            )

        component_impl = entry_point.load()
        component_impls.append((selected_component, component_impl))

    return component_impls


def _import_exporters(
    exporter_names: Sequence[str],
) -> Sequence[SpanExporter]:
    trace_exporters = {}

    for (
        exporter_name,
        exporter_impl,
    ) in _import_tracer_provider_config_components(
        exporter_names, "opentelemetry_traces_exporter"
    ):
        if issubclass(exporter_impl, SpanExporter):
            trace_exporters[exporter_name] = exporter_impl
        else:
            raise RuntimeError(f"{exporter_name} is not a trace exporter")
    return trace_exporters


def _import_id_generator(id_generator_name: str) -> IdGenerator:
    # pylint: disable=unbalanced-tuple-unpacking
    [
        (id_generator_name, id_generator_impl)
    ] = _import_tracer_provider_config_components(
        [id_generator_name.strip()], "opentelemetry_id_generator"
    )

    if issubclass(id_generator_impl, IdGenerator):
        return id_generator_impl

    raise RuntimeError(f"{id_generator_name} is not an IdGenerator")


def _initialize_components():
    exporter_names = _get_exporter_names()
    trace_exporters = _import_exporters(exporter_names)
    id_generator_name = _get_id_generator()
    id_generator = _import_id_generator(id_generator_name)
    _init_tracing(trace_exporters, id_generator)


class _OTelSDKConfigurator(BaseConfigurator):
    """A basic Configurator by OTel Python for initalizing OTel SDK components

    Initializes several crucial OTel SDK components (i.e. TracerProvider,
    MeterProvider, Processors...) according to a default implementation. Other
    Configurators can subclass and slightly alter this initialization.

    NOTE: This class should not be instantiated nor should it become an entry
    point on the `opentelemetry-sdk` package. Instead, distros should subclass
    this Configurator and enchance it as needed.
    """

    def _configure(self, **kwargs):
        _initialize_components()

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 241c8252d5d..4e07232d068 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   ([#2096](https://github.com/open-telemetry/opentelemetry-python/pull/2096))
 - Fix propagation bug caused by counting skipped entries
   ([#2071](https://github.com/open-telemetry/opentelemetry-python/pull/2071))
+- Add entry point for exporters with default protocol
+  ([#2093](https://github.com/open-telemetry/opentelemetry-python/pull/2093))
 - Do not skip sequence attribute on decode error
   ([#2097](https://github.com/open-telemetry/opentelemetry-python/pull/2097))
 - `opentelemetry-test`: Add `HttpTestBase` to allow tests with actual TCP sockets
diff --git a/exporter/opentelemetry-exporter-jaeger-proto-grpc/setup.cfg b/exporter/opentelemetry-exporter-jaeger-proto-grpc/setup.cfg
index bd792429899..4f4228f9c76 100644
--- a/exporter/opentelemetry-exporter-jaeger-proto-grpc/setup.cfg
+++ b/exporter/opentelemetry-exporter-jaeger-proto-grpc/setup.cfg
@@ -52,5 +52,5 @@ where = src
 test =
 
 [options.entry_points]
-opentelemetry_exporter =
+opentelemetry_traces_exporter =
     jaeger_proto = opentelemetry.exporter.jaeger.proto.grpc:JaegerExporter
diff --git a/exporter/opentelemetry-exporter-jaeger-thrift/setup.cfg b/exporter/opentelemetry-exporter-jaeger-thrift/setup.cfg
index 945150d6879..e2c98a48096 100644
--- a/exporter/opentelemetry-exporter-jaeger-thrift/setup.cfg
+++ b/exporter/opentelemetry-exporter-jaeger-thrift/setup.cfg
@@ -51,5 +51,5 @@ where = src
 test =
 
 [options.entry_points]
-opentelemetry_exporter =
-    jaeger_thrift = opentelemetry.exporter.jaeger.thrift:JaegerExporter
\ No newline at end of file
+opentelemetry_traces_exporter =
+    jaeger_thrift = opentelemetry.exporter.jaeger.thrift:JaegerExporter
diff --git a/exporter/opentelemetry-exporter-jaeger/setup.cfg b/exporter/opentelemetry-exporter-jaeger/setup.cfg
index 06e68fded5c..e882a65f975 100644
--- a/exporter/opentelemetry-exporter-jaeger/setup.cfg
+++ b/exporter/opentelemetry-exporter-jaeger/setup.cfg
@@ -44,3 +44,7 @@ install_requires =
 
 [options.extras_require]
 test =
+
+[options.entry_points]
+opentelemetry_traces_exporter =
+    jaeger = opentelemetry.exporter.jaeger.proto.grpc:JaegerExporter
diff --git a/exporter/opentelemetry-exporter-opencensus/setup.cfg b/exporter/opentelemetry-exporter-opencensus/setup.cfg
index 03e82b70f37..1e10bc7b968 100644
--- a/exporter/opentelemetry-exporter-opencensus/setup.cfg
+++ b/exporter/opentelemetry-exporter-opencensus/setup.cfg
@@ -53,5 +53,5 @@ where = src
 test =
 
 [options.entry_points]
-opentelemetry_exporter =
-    opencensus = opentelemetry.exporter.opencensus.trace_exporter:OpenCensusSpanExporter
\ No newline at end of file
+opentelemetry_traces_exporter =
+    opencensus = opentelemetry.exporter.opencensus.trace_exporter:OpenCensusSpanExporter
diff --git a/exporter/opentelemetry-exporter-otlp-proto-grpc/setup.cfg b/exporter/opentelemetry-exporter-otlp-proto-grpc/setup.cfg
index 7013425c958..5d144190d35 100644
--- a/exporter/opentelemetry-exporter-otlp-proto-grpc/setup.cfg
+++ b/exporter/opentelemetry-exporter-otlp-proto-grpc/setup.cfg
@@ -54,5 +54,5 @@ test =
 where = src
 
 [options.entry_points]
-opentelemetry_exporter =
-    otlp_proto_grpc_span = opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter
+opentelemetry_traces_exporter =
+    otlp_proto_grpc = opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter
diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/setup.cfg b/exporter/opentelemetry-exporter-otlp-proto-http/setup.cfg
index 5f6d102d120..38b82f42656 100644
--- a/exporter/opentelemetry-exporter-otlp-proto-http/setup.cfg
+++ b/exporter/opentelemetry-exporter-otlp-proto-http/setup.cfg
@@ -53,5 +53,5 @@ test =
 where = src
 
 [options.entry_points]
-opentelemetry_exporter =
-    otlp_proto_http_span = opentelemetry.exporter.otlp.proto.http.trace_exporter:OTLPSpanExporter
+opentelemetry_traces_exporter =
+    otlp_proto_http = opentelemetry.exporter.otlp.proto.http.trace_exporter:OTLPSpanExporter
diff --git a/exporter/opentelemetry-exporter-otlp/setup.cfg b/exporter/opentelemetry-exporter-otlp/setup.cfg
index 8d24fca2429..b971015497d 100644
--- a/exporter/opentelemetry-exporter-otlp/setup.cfg
+++ b/exporter/opentelemetry-exporter-otlp/setup.cfg
@@ -39,3 +39,7 @@ python_requires = >=3.6
 packages=find_namespace:
 install_requires =
     opentelemetry-exporter-otlp-proto-grpc == 1.5.0
+
+[options.entry_points]
+opentelemetry_traces_exporter =
+    otlp = opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter
diff --git a/exporter/opentelemetry-exporter-zipkin-json/setup.cfg b/exporter/opentelemetry-exporter-zipkin-json/setup.cfg
index 81fcbfa261c..2517b523261 100644
--- a/exporter/opentelemetry-exporter-zipkin-json/setup.cfg
+++ b/exporter/opentelemetry-exporter-zipkin-json/setup.cfg
@@ -52,5 +52,5 @@ where = src
 test =
 
 [options.entry_points]
-opentelemetry_exporter =
-    zipkin_json = opentelemetry.exporter.zipkin.json:ZipkinExporter
\ No newline at end of file
+opentelemetry_traces_exporter =
+    zipkin_json = opentelemetry.exporter.zipkin.json:ZipkinExporter
diff --git a/exporter/opentelemetry-exporter-zipkin-proto-http/setup.cfg b/exporter/opentelemetry-exporter-zipkin-proto-http/setup.cfg
index 874ce5a368e..a6cdd5b5f87 100644
--- a/exporter/opentelemetry-exporter-zipkin-proto-http/setup.cfg
+++ b/exporter/opentelemetry-exporter-zipkin-proto-http/setup.cfg
@@ -54,5 +54,5 @@ where = src
 test =
 
 [options.entry_points]
-opentelemetry_exporter =
-    zipkin_proto = opentelemetry.exporter.zipkin.proto.http:ZipkinExporter
\ No newline at end of file
+opentelemetry_traces_exporter =
+    zipkin_proto = opentelemetry.exporter.zipkin.proto.http:ZipkinExporter
diff --git a/exporter/opentelemetry-exporter-zipkin/setup.cfg b/exporter/opentelemetry-exporter-zipkin/setup.cfg
index bea3c1043d1..70e850bfe79 100644
--- a/exporter/opentelemetry-exporter-zipkin/setup.cfg
+++ b/exporter/opentelemetry-exporter-zipkin/setup.cfg
@@ -43,3 +43,7 @@ install_requires =
 
 [options.extras_require]
 test =
+
+[options.entry_points]
+opentelemetry_traces_exporter =
+    zipkin = opentelemetry.exporter.zipkin.proto.http:ZipkinExporter
diff --git a/opentelemetry-sdk/setup.cfg b/opentelemetry-sdk/setup.cfg
index 160fde86e51..0c699262c64 100644
--- a/opentelemetry-sdk/setup.cfg
+++ b/opentelemetry-sdk/setup.cfg
@@ -52,8 +52,8 @@ where = src
 [options.entry_points]
 opentelemetry_tracer_provider =
     sdk_tracer_provider = opentelemetry.sdk.trace:TracerProvider
-opentelemetry_exporter =
-    console_span = opentelemetry.sdk.trace.export:ConsoleSpanExporter
+opentelemetry_traces_exporter =
+    console = opentelemetry.sdk.trace.export:ConsoleSpanExporter
 opentelemetry_id_generator =
     random = opentelemetry.sdk.trace.id_generator:RandomIdGenerator
 
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
index 78bb1377801..79445a7a357 100644
--- a/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/_configuration/__init__.py
@@ -109,7 +109,7 @@ def _import_exporters(
         exporter_name,
         exporter_impl,
     ) in _import_tracer_provider_config_components(
-        exporter_names, "opentelemetry_exporter"
+        exporter_names, "opentelemetry_traces_exporter"
     ):
         if issubclass(exporter_impl, SpanExporter):
             trace_exporters[exporter_name] = exporter_impl

RuntimeError: Requested component 'jaeger' not found in entry points for 'opentelemetry_exporter'
From the exporters selection of [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#exporter-selection)

>Known values for OTEL_TRACES_EXPORTER are:
>
>- "otlp": OTLP
>- "jaeger": Jaeger gRPC
>- "zipkin": Zipkin (Defaults to protobuf format)
>- "none": No automatically configured exporter for traces.

We have split up the exporters based on protocol + serialisation to avoid taking unnecessary dependency so there is no entry point for `jaeger`. If someone reads the spec and follows that recommendation they will run into this error. We should either add an entry point `jaeger` or an alias that solves this problem.
