From f6054e5daf5f2aee9a7fc71aadfd8853fd2fefeb Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 8 Feb 2023 15:50:46 -0800 Subject: [PATCH 1/7] python config prototype Signed-off-by: Alex Boten --- prototypes/python/.gitignore | 1 + prototypes/python/README.md | 11 +++++ prototypes/python/otel.py | 70 ++++++++++++++++++++++++++++++ prototypes/python/prototype.py | 24 ++++++++++ prototypes/python/requirements.txt | 4 ++ 5 files changed, 110 insertions(+) create mode 100644 prototypes/python/.gitignore create mode 100644 prototypes/python/README.md create mode 100644 prototypes/python/otel.py create mode 100644 prototypes/python/prototype.py create mode 100644 prototypes/python/requirements.txt diff --git a/prototypes/python/.gitignore b/prototypes/python/.gitignore new file mode 100644 index 0000000..1d17dae --- /dev/null +++ b/prototypes/python/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/prototypes/python/README.md b/prototypes/python/README.md new file mode 100644 index 0000000..012320a --- /dev/null +++ b/prototypes/python/README.md @@ -0,0 +1,11 @@ +# JSON Schema validation with Python + +The code in this directory shows an example of using `jsonschema` for validation in combination with `pyyaml` YAML parser in Python. +Usage: + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +python prototype.py ../../config.yaml ../../json_schema/schema/schema.json +``` diff --git a/prototypes/python/otel.py b/prototypes/python/otel.py new file mode 100644 index 0000000..6f7e7ad --- /dev/null +++ b/prototypes/python/otel.py @@ -0,0 +1,70 @@ + +import logging + +import yaml +from pathlib import Path +from jsonschema import validate, validators +from jsonschema.exceptions import ValidationError + +from opentelemetry.trace import set_tracer_provider + +class Config: + def __init__(self, config=None) -> None: + self._config = config + + def _resource(self): + # resource detection + # attributes + from opentelemetry.sdk.resources import Resource + return Resource.create(self._config.get("sdk").get("resource").get("attributes")) + + def set_tracer_provider(self): + from opentelemetry.sdk.trace import TracerProvider + provider = TracerProvider(resource=self._resource()) + from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter + + processor = BatchSpanProcessor(ConsoleSpanExporter()) + provider.add_span_processor(processor) + set_tracer_provider(provider) + + def set_meter_provider(self): + pass + + def apply(self): + logging.debug("applying configuration %s", self._config) + if self._config is None or self._config.get("sdk").get("disabled"): + logging.debug("sdk disabled, nothing to apply") + # do nothing + return + self.set_tracer_provider() + self.set_meter_provider() + + +NoOpConfig = Config() + + +def parse_and_validate_from_config_file(filename: str, schema: str="../schema/schema.json") -> Config: + logging.debug(f"Loading config file: {filename}") + path = Path(__file__).parent.resolve() + resolver = validators.RefResolver( + base_uri=f"{path.as_uri()}/", + referrer=True, + ) + + with open(filename, "r") as stream: + try: + parse_config = yaml.safe_load(stream) + logging.debug("YAML parsed successfully") + logging.debug(f"Validating using schema file: {schema}") + validate( + instance=parse_config, + schema={"$ref": schema}, + resolver=resolver, + ) + logging.debug("No validation errors") + return Config(parse_config) + except yaml.YAMLError as exc: + logging.error(exc) + except ValidationError as exc: + logging.error(exc) + return NoOpConfig \ No newline at end of file diff --git a/prototypes/python/prototype.py b/prototypes/python/prototype.py new file mode 100644 index 0000000..8098a8c --- /dev/null +++ b/prototypes/python/prototype.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import logging +import sys + +from opentelemetry.trace import get_tracer + +from otel import parse_and_validate_from_config_file + + + +def main(): + logging.basicConfig(level=logging.DEBUG) + config = parse_and_validate_from_config_file(sys.argv[1], sys.argv[2]) + config.apply() + + tracer = get_tracer("config-prototype") + + with tracer.start_as_current_span("operation-a"): + with tracer.start_as_current_span("operation-a"): + with tracer.start_as_current_span("operation-a"): + logging.debug("you should see traces after this line") + +main() \ No newline at end of file diff --git a/prototypes/python/requirements.txt b/prototypes/python/requirements.txt new file mode 100644 index 0000000..c88b438 --- /dev/null +++ b/prototypes/python/requirements.txt @@ -0,0 +1,4 @@ +jsonschema +pyyaml +opentelemetry-api +opentelemetry-sdk \ No newline at end of file From 54f5255d74541f4ee0b10475c9c8ae9312720f9a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 9 Feb 2023 14:26:45 -0800 Subject: [PATCH 2/7] a few ergonomics changes Updated the list of propagators to an array of strings. Renamed *_provider to the name of the signal instead. This makes searching the parsed configuration a bit cleaner, since exporters are configured per signal, and not necessarily per provider (at least conceptually i found it easier to grok). Signed-off-by: Alex Boten --- config.yaml | 9 +++++---- json_schema/schema/schema.json | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/config.yaml b/config.yaml index febfd3e..6390534 100644 --- a/config.yaml +++ b/config.yaml @@ -12,6 +12,7 @@ sdk: disabled: false # Configure resource attributes and resource detection for all signals. resource: + # detectors: [gcp, host] # Key-value pairs to be used as resource attributes. # # Environment variable: OTEL_RESOURCE_ATTRIBUTES @@ -35,7 +36,7 @@ sdk: # Environment variable: OTEL_ATTRIBUTE_COUNT_LIMIT attribute_count_limit: 128 # Configure the tracer provider. - tracer_provider: + traces: # Span exporters. Each exporter key refers to the type of the exporter. Values configure the exporter. Exporters must be associated with a span processor. exporters: # Configure the otlp exporter. @@ -140,7 +141,7 @@ sdk: # Sets the exporter. Exporter must refer to a key in sdk.tracer_provider.exporters. # # Environment variable: OTEL_TRACES_EXPORTER - exporter: otlp + exporter: console # Add a batch span processor configured with zipkin exporter. For full description of options see sdk.tracer_provider.span_processors[0]. - type: batch args: @@ -228,7 +229,7 @@ sdk: # Set the sampler. Sampler must refer to a key in sdk.tracer_provider.sampler_config. sampler: parent_based # Configure the meter provider. - meter_provider: + metrics: # Metric exporters. Each exporter key refers to the type of the exporter. Values configure the exporter. Exporters must be associated with a metric reader. exporters: # Configure the otlp exporter. @@ -342,7 +343,7 @@ sdk: - key1 - key2 # Configure the logger provider. - logger_provider: + logs: # Log record exporters. Each exporter key refers to the type of the exporter. Values configure the exporter. Exporters must be associated with a log record processor. exporters: # Configure the otlp exporter. diff --git a/json_schema/schema/schema.json b/json_schema/schema/schema.json index 76af51b..f079766 100644 --- a/json_schema/schema/schema.json +++ b/json_schema/schema/schema.json @@ -38,13 +38,13 @@ "attribute_limits": { "$ref": "#/definitions/Limits" }, - "tracer_provider": { + "traces": { "$ref": "#/definitions/TracerProvider" }, - "meter_provider": { + "metrics": { "$ref": "#/definitions/MeterProvider" }, - "logger_provider": { + "logs": { "$ref": "#/definitions/LoggerProvider" } }, @@ -78,7 +78,7 @@ "$ref": "#/definitions/LoggerProviderExporters" }, "log_record_processors": { - "type": "array", + "type": "object", "items": { "$ref": "#/definitions/Processor" } @@ -423,7 +423,7 @@ "$ref": "#/definitions/TracerProviderExporters" }, "span_processors": { - "type": "array", + "type": "object", "items": { "$ref": "#/definitions/Processor" } From d7e9916a89d2ea29494736146c885c152dd458be Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Thu, 9 Feb 2023 15:26:21 -0800 Subject: [PATCH 3/7] more work to get prototype working Signed-off-by: Alex Boten --- prototypes/python/otel.py | 57 +++++++++++++++++++++++++++++++--- prototypes/python/prototype.py | 5 ++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/prototypes/python/otel.py b/prototypes/python/otel.py index 6f7e7ad..7def73e 100644 --- a/prototypes/python/otel.py +++ b/prototypes/python/otel.py @@ -1,4 +1,5 @@ +import importlib import logging import yaml @@ -8,6 +9,7 @@ from opentelemetry.trace import set_tracer_provider + class Config: def __init__(self, config=None) -> None: self._config = config @@ -17,14 +19,60 @@ def _resource(self): # attributes from opentelemetry.sdk.resources import Resource return Resource.create(self._config.get("sdk").get("resource").get("attributes")) + + # _get_exporter returns a configured span + def _get_exporter(self, signal, name): + if name not in self._config.get("sdk").get(signal).get("exporters"): + raise Exception(f"exporter {name} not specified for {signal} signal") + # TODO: replace to use entrypoints + _KNOWN_TRACE_EXPORTER_MAP = { + "traces": { + "console": { + "pkg": "opentelemetry.sdk.trace.export", + "class": "ConsoleSpanExporter", + }, + "jaeger": { + "pkg": "opentelemetry.exporter.jaeger.thrift", + "class": "JaegerExporter", + }, + "zipkin": { + "pkg": "opentelemetry.exporter.zipkin.json", + "class": "ZipkinExporter", + }, + }, + "metrics": { + "console": { + "pkg": "opentelemetry.sdk.metrics.export", + "class": "ConsoleExporter", + }, + }, + "logs": { + "console": { + "pkg": "opentelemetry.sdk.logs.export", + "class": "ConsoleExporter", + }, + } + } + # look for known exporters + if name in _KNOWN_TRACE_EXPORTER_MAP.get(signal): + mod = importlib.__import__(_KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("pkg"), fromlist=[_KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("class")]) + _cls = getattr(mod, _KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("class")) + return _cls() + # handle case where a custom exporter is used + def set_tracer_provider(self): from opentelemetry.sdk.trace import TracerProvider provider = TracerProvider(resource=self._resource()) - from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter + from opentelemetry.sdk.trace.export import BatchSpanProcessor - processor = BatchSpanProcessor(ConsoleSpanExporter()) - provider.add_span_processor(processor) + for processor in self._config.get("sdk").get("traces").get("span_processors"): + logging.debug("adding span processor %s", processor) + try: + processor = BatchSpanProcessor(self._get_exporter("traces", self._config.get("sdk").get("traces").get("span_processors").get(processor).get("args").get("exporter"))) + provider.add_span_processor(processor) + except ModuleNotFoundError as exc: + logging.error("module not found", exc) set_tracer_provider(provider) def set_meter_provider(self): @@ -34,7 +82,6 @@ def apply(self): logging.debug("applying configuration %s", self._config) if self._config is None or self._config.get("sdk").get("disabled"): logging.debug("sdk disabled, nothing to apply") - # do nothing return self.set_tracer_provider() self.set_meter_provider() @@ -42,6 +89,8 @@ def apply(self): NoOpConfig = Config() +def configure(configuration: Config) -> None: + configuration.apply() def parse_and_validate_from_config_file(filename: str, schema: str="../schema/schema.json") -> Config: logging.debug(f"Loading config file: {filename}") diff --git a/prototypes/python/prototype.py b/prototypes/python/prototype.py index 8098a8c..097bfa0 100644 --- a/prototypes/python/prototype.py +++ b/prototypes/python/prototype.py @@ -5,14 +5,13 @@ from opentelemetry.trace import get_tracer -from otel import parse_and_validate_from_config_file +import otel def main(): logging.basicConfig(level=logging.DEBUG) - config = parse_and_validate_from_config_file(sys.argv[1], sys.argv[2]) - config.apply() + otel.configure(otel.parse_and_validate_from_config_file(sys.argv[1], sys.argv[2])) tracer = get_tracer("config-prototype") From c3006c9e4a8fc9ed6c0ae60a4c20824e7030478a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 14 Feb 2023 20:17:18 -0800 Subject: [PATCH 4/7] change object to array --- json_schema/schema/schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/json_schema/schema/schema.json b/json_schema/schema/schema.json index f079766..2139f5c 100644 --- a/json_schema/schema/schema.json +++ b/json_schema/schema/schema.json @@ -78,7 +78,7 @@ "$ref": "#/definitions/LoggerProviderExporters" }, "log_record_processors": { - "type": "object", + "type": "array", "items": { "$ref": "#/definitions/Processor" } @@ -423,7 +423,7 @@ "$ref": "#/definitions/TracerProviderExporters" }, "span_processors": { - "type": "object", + "type": "array", "items": { "$ref": "#/definitions/Processor" } From a0f95edd3e2f03ce2a81504ed3071b20818219ef Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 14 Feb 2023 20:21:13 -0800 Subject: [PATCH 5/7] use entry points where possible Signed-off-by: Alex Boten --- prototypes/python/otel.py | 70 +++++++++++++----------------- prototypes/python/prototype.py | 1 - prototypes/python/requirements.txt | 4 +- 3 files changed, 32 insertions(+), 43 deletions(-) diff --git a/prototypes/python/otel.py b/prototypes/python/otel.py index 7def73e..5b517b3 100644 --- a/prototypes/python/otel.py +++ b/prototypes/python/otel.py @@ -1,6 +1,8 @@ -import importlib import logging +from typing import List, Sequence, Tuple +from pkg_resources import iter_entry_points + import yaml from pathlib import Path @@ -8,7 +10,23 @@ from jsonschema.exceptions import ValidationError from opentelemetry.trace import set_tracer_provider +from opentelemetry.sdk.trace.export import SpanExporter + + +# borromed from opentelemetry/sdk/_configuration +def _import_config_component( + selected_component: str, entry_point_name: str +) -> object: + component_entry_points = { + ep.name: ep for ep in iter_entry_points(entry_point_name) + } + 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}'" + ) + return entry_point.load() class Config: def __init__(self, config=None) -> None: @@ -20,46 +38,15 @@ def _resource(self): from opentelemetry.sdk.resources import Resource return Resource.create(self._config.get("sdk").get("resource").get("attributes")) - # _get_exporter returns a configured span - def _get_exporter(self, signal, name): - if name not in self._config.get("sdk").get(signal).get("exporters"): - raise Exception(f"exporter {name} not specified for {signal} signal") + # _get_exporter returns an exporter class for the signal + def _get_exporter(self, signal: str, name: str): + # if name not in self._config.get("sdk").get(signal).get("exporters"): + # raise Exception(f"exporter {name} not specified for {signal} signal") - # TODO: replace to use entrypoints - _KNOWN_TRACE_EXPORTER_MAP = { - "traces": { - "console": { - "pkg": "opentelemetry.sdk.trace.export", - "class": "ConsoleSpanExporter", - }, - "jaeger": { - "pkg": "opentelemetry.exporter.jaeger.thrift", - "class": "JaegerExporter", - }, - "zipkin": { - "pkg": "opentelemetry.exporter.zipkin.json", - "class": "ZipkinExporter", - }, - }, - "metrics": { - "console": { - "pkg": "opentelemetry.sdk.metrics.export", - "class": "ConsoleExporter", - }, - }, - "logs": { - "console": { - "pkg": "opentelemetry.sdk.logs.export", - "class": "ConsoleExporter", - }, - } - } - # look for known exporters - if name in _KNOWN_TRACE_EXPORTER_MAP.get(signal): - mod = importlib.__import__(_KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("pkg"), fromlist=[_KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("class")]) - _cls = getattr(mod, _KNOWN_TRACE_EXPORTER_MAP.get(signal).get(name).get("class")) - return _cls() - # handle case where a custom exporter is used + exporter = _import_config_component(name, f"opentelemetry_{signal}_exporter") + if issubclass(exporter, SpanExporter): + return exporter + raise RuntimeError(f"{name} is not a {signal} exporter") def set_tracer_provider(self): from opentelemetry.sdk.trace import TracerProvider @@ -69,7 +56,8 @@ def set_tracer_provider(self): for processor in self._config.get("sdk").get("traces").get("span_processors"): logging.debug("adding span processor %s", processor) try: - processor = BatchSpanProcessor(self._get_exporter("traces", self._config.get("sdk").get("traces").get("span_processors").get(processor).get("args").get("exporter"))) + # TODO: pass in exporter arguments + processor = BatchSpanProcessor(self._get_exporter("traces", processor.get("args").get("exporter"))()) provider.add_span_processor(processor) except ModuleNotFoundError as exc: logging.error("module not found", exc) diff --git a/prototypes/python/prototype.py b/prototypes/python/prototype.py index 097bfa0..bbe2d2d 100644 --- a/prototypes/python/prototype.py +++ b/prototypes/python/prototype.py @@ -8,7 +8,6 @@ import otel - def main(): logging.basicConfig(level=logging.DEBUG) otel.configure(otel.parse_and_validate_from_config_file(sys.argv[1], sys.argv[2])) diff --git a/prototypes/python/requirements.txt b/prototypes/python/requirements.txt index c88b438..ee36367 100644 --- a/prototypes/python/requirements.txt +++ b/prototypes/python/requirements.txt @@ -1,4 +1,6 @@ jsonschema pyyaml opentelemetry-api -opentelemetry-sdk \ No newline at end of file +opentelemetry-sdk +opentelemetry-exporter-jaeger +opentelemetry-exporter-zipkin \ No newline at end of file From e123d1467b67f2b3389402b576f2945e252e7883 Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Wed, 15 Feb 2023 15:34:12 -0800 Subject: [PATCH 6/7] update prototype to test out otlp, added insecure field Signed-off-by: Alex Boten --- json_schema/schema/schema.json | 6 ++++-- prototypes/python/otel.py | 8 ++++---- prototypes/python/prototype.py | 4 ++-- prototypes/python/requirements.txt | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/json_schema/schema/schema.json b/json_schema/schema/schema.json index 2139f5c..7fc86bc 100644 --- a/json_schema/schema/schema.json +++ b/json_schema/schema/schema.json @@ -145,11 +145,13 @@ }, "default_histogram_aggregation": { "type": "string" + }, + "insecure": { + "type": "boolean" } }, "required": [ - "endpoint", - "protocol" + "endpoint" ], "title": "Otlp" }, diff --git a/prototypes/python/otel.py b/prototypes/python/otel.py index 5b517b3..8e939fd 100644 --- a/prototypes/python/otel.py +++ b/prototypes/python/otel.py @@ -40,12 +40,12 @@ def _resource(self): # _get_exporter returns an exporter class for the signal def _get_exporter(self, signal: str, name: str): - # if name not in self._config.get("sdk").get(signal).get("exporters"): - # raise Exception(f"exporter {name} not specified for {signal} signal") + if name not in self._config.get("sdk").get(signal).get("exporters"): + raise Exception(f"exporter {name} not specified for {signal} signal") exporter = _import_config_component(name, f"opentelemetry_{signal}_exporter") if issubclass(exporter, SpanExporter): - return exporter + return exporter(**self._config.get("sdk").get(signal).get("exporters").get(name)) raise RuntimeError(f"{name} is not a {signal} exporter") def set_tracer_provider(self): @@ -57,7 +57,7 @@ def set_tracer_provider(self): logging.debug("adding span processor %s", processor) try: # TODO: pass in exporter arguments - processor = BatchSpanProcessor(self._get_exporter("traces", processor.get("args").get("exporter"))()) + processor = BatchSpanProcessor(self._get_exporter("traces", processor.get("args").get("exporter"))) provider.add_span_processor(processor) except ModuleNotFoundError as exc: logging.error("module not found", exc) diff --git a/prototypes/python/prototype.py b/prototypes/python/prototype.py index bbe2d2d..510d988 100644 --- a/prototypes/python/prototype.py +++ b/prototypes/python/prototype.py @@ -15,8 +15,8 @@ def main(): tracer = get_tracer("config-prototype") with tracer.start_as_current_span("operation-a"): - with tracer.start_as_current_span("operation-a"): - with tracer.start_as_current_span("operation-a"): + with tracer.start_as_current_span("operation-b"): + with tracer.start_as_current_span("operation-c"): logging.debug("you should see traces after this line") main() \ No newline at end of file diff --git a/prototypes/python/requirements.txt b/prototypes/python/requirements.txt index ee36367..214cfbe 100644 --- a/prototypes/python/requirements.txt +++ b/prototypes/python/requirements.txt @@ -2,5 +2,6 @@ jsonschema pyyaml opentelemetry-api opentelemetry-sdk +opentelemetry-exporter-otlp opentelemetry-exporter-jaeger opentelemetry-exporter-zipkin \ No newline at end of file From bacb1d4dc30275fe31109e6edd7f8084cd36b60a Mon Sep 17 00:00:00 2001 From: Alex Boten Date: Tue, 21 Feb 2023 15:37:28 -0800 Subject: [PATCH 7/7] add meter provider configuration Signed-off-by: Alex Boten --- config.yaml | 18 +++++++++------- json_schema/schema/schema.json | 5 +---- prototypes/python/otel.py | 38 ++++++++++++++++++++++++++-------- prototypes/python/prototype.py | 12 +++++++++-- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/config.yaml b/config.yaml index 6390534..20c0790 100644 --- a/config.yaml +++ b/config.yaml @@ -114,6 +114,7 @@ sdk: # # Environment variable: OTEL_EXPORTER_JAEGER_AGENT_HOST agent_port: 6832 + console: # List of span processors. Each span processor has a type and args used to configure it. span_processors: # Add a batch span processor. @@ -143,13 +144,13 @@ sdk: # Environment variable: OTEL_TRACES_EXPORTER exporter: console # Add a batch span processor configured with zipkin exporter. For full description of options see sdk.tracer_provider.span_processors[0]. - - type: batch - args: - exporter: zipkin - # Add a batch span processor configured with jaeger exporter. For full description of options see sdk.tracer_provider.span_processors[0]. - - type: batch - args: - exporter: jaeger + # - type: batch + # args: + # exporter: zipkin + # # Add a batch span processor configured with jaeger exporter. For full description of options see sdk.tracer_provider.span_processors[0]. + # - type: batch + # args: + # exporter: jaeger # Configure the span limits. See also sdk.attribute_limits. span_limits: # Set the max span attribute value size. Overrides sdk.attribute_limits.attribute_value_length_limit. @@ -275,6 +276,7 @@ sdk: # # Environment variable: OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION default_histogram_aggregation: exponential_bucket_histogram + console: # List of metric readers. Each metric reader has a type and args used to configure it. metric_readers: # Add a periodic metric reader. @@ -293,7 +295,7 @@ sdk: # Sets the exporter. Exporter must refer to a key in sdk.meter_provider.exporters. # # Environment variable: OTEL_METRICS_EXPORTER - exporter: otlp + exporter: console # Add a prometheus metric reader. Some languages SDKs may implement this as a metric exporter. # # Environment variable: OTEL_METRICS_EXPORTER=prometheus diff --git a/json_schema/schema/schema.json b/json_schema/schema/schema.json index 7fc86bc..c1aee73 100644 --- a/json_schema/schema/schema.json +++ b/json_schema/schema/schema.json @@ -96,15 +96,12 @@ }, "LoggerProviderExporters": { "type": "object", - "additionalProperties": false, + "additionalProperties": true, "properties": { "otlp": { "$ref": "#/definitions/Otlp" } }, - "required": [ - "otlp" - ], "title": "LoggerProviderExporters" }, "Otlp": { diff --git a/prototypes/python/otel.py b/prototypes/python/otel.py index 8e939fd..8887436 100644 --- a/prototypes/python/otel.py +++ b/prototypes/python/otel.py @@ -9,8 +9,10 @@ from jsonschema import validate, validators from jsonschema.exceptions import ValidationError +from opentelemetry.metrics import set_meter_provider from opentelemetry.trace import set_tracer_provider from opentelemetry.sdk.trace.export import SpanExporter +from opentelemetry.sdk.metrics.export import MetricExporter # borromed from opentelemetry/sdk/_configuration @@ -44,35 +46,53 @@ def _get_exporter(self, signal: str, name: str): raise Exception(f"exporter {name} not specified for {signal} signal") exporter = _import_config_component(name, f"opentelemetry_{signal}_exporter") - if issubclass(exporter, SpanExporter): - return exporter(**self._config.get("sdk").get(signal).get("exporters").get(name)) + if signal == "metrics": + cls_type = MetricExporter + elif signal == "traces": + cls_type = SpanExporter + elif signal == "logs": + cls_type = SpanExporter + if issubclass(exporter, cls_type): + if self._config.get("sdk").get(signal).get("exporters").get(name) is not None: + return exporter(**self._config.get("sdk").get(signal).get("exporters").get(name)) + return exporter() raise RuntimeError(f"{name} is not a {signal} exporter") - def set_tracer_provider(self): + def configure_tracing(self, cfg): + if cfg is None: + return from opentelemetry.sdk.trace import TracerProvider provider = TracerProvider(resource=self._resource()) from opentelemetry.sdk.trace.export import BatchSpanProcessor - for processor in self._config.get("sdk").get("traces").get("span_processors"): + for processor in cfg.get("span_processors"): logging.debug("adding span processor %s", processor) try: - # TODO: pass in exporter arguments processor = BatchSpanProcessor(self._get_exporter("traces", processor.get("args").get("exporter"))) provider.add_span_processor(processor) except ModuleNotFoundError as exc: logging.error("module not found", exc) set_tracer_provider(provider) - def set_meter_provider(self): - pass + def configure_metrics(self, cfg): + if cfg is None: + return + from opentelemetry.sdk.metrics import MeterProvider + readers = [] + for reader in cfg.get("metric_readers"): + if reader.get("type") == "periodic": + from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader + readers.append(PeriodicExportingMetricReader(self._get_exporter("metrics", reader.get("args").get("exporter")))) + provider = MeterProvider(resource=self._resource(), metric_readers=readers) + set_meter_provider(provider) def apply(self): logging.debug("applying configuration %s", self._config) if self._config is None or self._config.get("sdk").get("disabled"): logging.debug("sdk disabled, nothing to apply") return - self.set_tracer_provider() - self.set_meter_provider() + self.configure_tracing(self._config.get("sdk").get("traces")) + self.configure_metrics(self._config.get("sdk").get("metrics")) NoOpConfig = Config() diff --git a/prototypes/python/prototype.py b/prototypes/python/prototype.py index 510d988..acd22da 100644 --- a/prototypes/python/prototype.py +++ b/prototypes/python/prototype.py @@ -4,6 +4,7 @@ import sys from opentelemetry.trace import get_tracer +from opentelemetry.metrics import get_meter import otel @@ -13,10 +14,17 @@ def main(): otel.configure(otel.parse_and_validate_from_config_file(sys.argv[1], sys.argv[2])) tracer = get_tracer("config-prototype") + meter = get_meter("config-prototype") + + counter = meter.create_counter("work", unit="1") with tracer.start_as_current_span("operation-a"): with tracer.start_as_current_span("operation-b"): with tracer.start_as_current_span("operation-c"): - logging.debug("you should see traces after this line") + logging.debug("you should see telemetry after this line") + counter.add(1) + + -main() \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file