Skip to content

Adding support for entrypoint loading of log exporters #2253

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

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#2234](https://github.com/open-telemetry/opentelemetry-python/pull/2234))
- Update visibility of OTEL_METRICS_EXPORTER environment variable
([#2303](https://github.com/open-telemetry/opentelemetry-python/pull/2303))
- Adding entrypoints for log emitter provider and console, otlp log exporters
([#2253](https://github.com/open-telemetry/opentelemetry-python/pull/2253))

## [1.7.1-0.26b1](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.7.0-0.26b0) - 2021-11-11

Expand Down
2 changes: 2 additions & 0 deletions exporter/opentelemetry-exporter-otlp-proto-grpc/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ where = src
[options.entry_points]
opentelemetry_traces_exporter =
otlp_proto_grpc = opentelemetry.exporter.otlp.proto.grpc.trace_exporter:OTLPSpanExporter
opentelemetry_logs_exporter =
otlp_proto_grpc = opentelemetry.exporter.otlp.proto.grpc._log_exporter:OTLPLogExporter
2 changes: 2 additions & 0 deletions opentelemetry-sdk/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ opentelemetry_traces_exporter =
console = opentelemetry.sdk.trace.export:ConsoleSpanExporter
opentelemetry_log_emitter_provider =
sdk_log_emitter_provider = opentelemetry.sdk._logs:LogEmitterProvider
opentelemetry_logs_exporter =
console = opentelemetry.sdk._logs.export:ConsoleExporter
opentelemetry_id_generator =
random = opentelemetry.sdk.trace.id_generator:RandomIdGenerator
opentelemetry_environment_variables =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,39 +28,40 @@
OTEL_PYTHON_ID_GENERATOR,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.sdk._logs import (
LogEmitterProvider,
set_log_emitter_provider,
)
from opentelemetry.sdk._logs.export import BatchLogProcessor, LogExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator
from opentelemetry.semconv.resource import ResourceAttributes

_EXPORTER_OTLP = "otlp"
_EXPORTER_OTLP_SPAN = "otlp_proto_grpc"
_EXPORTER_OTLP_PROTO_GRPC = "otlp_proto_grpc"

_RANDOM_ID_GENERATOR = "random"
_DEFAULT_ID_GENERATOR = _RANDOM_ID_GENERATOR

# TODO: add log exporter env variable
_OTEL_LOGS_EXPORTER = "OTEL_LOGS_EXPORTER"


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)

def _get_exporter_names(names: str) -> Sequence[str]:
exporters = set()

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

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

return list(exporters)

Expand Down Expand Up @@ -91,7 +92,29 @@ def _init_tracing(
)


def _import_tracer_provider_config_components(
def _init_logging(
exporters: Dict[str, Sequence[LogExporter]],
auto_instrumentation_version: Optional[str] = None,
):
# if env var OTEL_RESOURCE_ATTRIBUTES is given, it will read the service_name
# from the env variable else defaults to "unknown_service"
auto_resource = {}
# populate version if using auto-instrumentation
if auto_instrumentation_version:
auto_resource[
ResourceAttributes.TELEMETRY_AUTO_VERSION
] = auto_instrumentation_version
provider = LogEmitterProvider(resource=Resource.create(auto_resource))
set_log_emitter_provider(provider)

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


def _import_config_components(
selected_components, entry_point_name
) -> Sequence[Tuple[str, object]]:
component_entry_points = {
Expand All @@ -112,28 +135,42 @@ def _import_tracer_provider_config_components(


def _import_exporters(
exporter_names: Sequence[str],
) -> Dict[str, Type[SpanExporter]]:
trace_exporter_names: Sequence[str],
log_exporter_names: Sequence[str],
) -> Tuple[Dict[str, Type[SpanExporter]], Dict[str, Type[LogExporter]]]:
trace_exporters = {}
log_exporters = {}

for (
exporter_name,
exporter_impl,
) in _import_tracer_provider_config_components(
exporter_names, "opentelemetry_traces_exporter"
) in _import_config_components(
trace_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

for (
exporter_name,
exporter_impl,
) in _import_config_components(
log_exporter_names, "opentelemetry_logs_exporter"
):
if issubclass(exporter_impl, LogExporter):
log_exporters[exporter_name] = exporter_impl
else:
raise RuntimeError(f"{exporter_name} is not a log exporter")

return trace_exporters, log_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(
] = _import_config_components(
[id_generator_name.strip()], "opentelemetry_id_generator"
)

Expand All @@ -144,11 +181,14 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator:


def _initialize_components(auto_instrumentation_version):
exporter_names = _get_exporter_names()
trace_exporters = _import_exporters(exporter_names)
trace_exporters, log_exporters = _import_exporters(
_get_exporter_names(environ.get(OTEL_TRACES_EXPORTER)),
_get_exporter_names(environ.get(_OTEL_LOGS_EXPORTER)),
)
id_generator_name = _get_id_generator()
id_generator = _import_id_generator(id_generator_name)
_init_tracing(trace_exporters, id_generator, auto_instrumentation_version)
_init_logging(log_exporters, auto_instrumentation_version)


class _BaseConfigurator(ABC):
Expand Down
44 changes: 28 additions & 16 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@
from unittest.mock import patch

from opentelemetry import trace
from opentelemetry.environment_variables import (
OTEL_PYTHON_ID_GENERATOR,
OTEL_TRACES_EXPORTER,
)
from opentelemetry.environment_variables import OTEL_PYTHON_ID_GENERATOR
from opentelemetry.sdk._configuration import (
_EXPORTER_OTLP,
_EXPORTER_OTLP_SPAN,
_EXPORTER_OTLP_PROTO_GRPC,
_get_exporter_names,
_get_id_generator,
_import_exporters,
_import_id_generator,
_init_tracing,
)
from opentelemetry.sdk._logs.export import ConsoleExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
from opentelemetry.sdk.trace.id_generator import IdGenerator, RandomIdGenerator


Expand Down Expand Up @@ -164,22 +164,34 @@ def test_trace_init_custom_id_generator(self, mock_iter_entry_points):

class TestExporterNames(TestCase):
def test_otlp_exporter_overwrite(self):
for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_SPAN]:
with patch.dict(environ, {OTEL_TRACES_EXPORTER: exporter}):
self.assertEqual(_get_exporter_names(), [_EXPORTER_OTLP_SPAN])
for exporter in [_EXPORTER_OTLP, _EXPORTER_OTLP_PROTO_GRPC]:
self.assertEqual(
_get_exporter_names(exporter), [_EXPORTER_OTLP_PROTO_GRPC]
)

@patch.dict(environ, {OTEL_TRACES_EXPORTER: "jaeger,zipkin"})
def test_multiple_exporters(self):
self.assertEqual(sorted(_get_exporter_names()), ["jaeger", "zipkin"])
self.assertEqual(
sorted(_get_exporter_names("jaeger,zipkin")), ["jaeger", "zipkin"]
)

@patch.dict(environ, {OTEL_TRACES_EXPORTER: "none"})
def test_none_exporters(self):
self.assertEqual(sorted(_get_exporter_names()), [])
self.assertEqual(sorted(_get_exporter_names("none")), [])

@patch.dict(environ, {}, clear=True)
def test_no_exporters(self):
self.assertEqual(sorted(_get_exporter_names()), [])
self.assertEqual(sorted(_get_exporter_names(None)), [])

@patch.dict(environ, {OTEL_TRACES_EXPORTER: ""})
def test_empty_exporters(self):
self.assertEqual(sorted(_get_exporter_names()), [])
self.assertEqual(sorted(_get_exporter_names("")), [])


class TestImportExporters(TestCase):
def test_console_exporters(self):
trace_exporters, logs_exporters = _import_exporters(
["console"], ["console"]
)
self.assertEqual(
trace_exporters["console"].__class__, ConsoleSpanExporter.__class__
)
self.assertEqual(
logs_exporters["console"].__class__, ConsoleExporter.__class__
)