def test_no_propagators(self): propagator = CompositeHTTPPropagator([]) new_carrier = {} propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {}) context = propagator.extract(get_as_list, carrier=new_carrier, context={}) self.assertEqual(context, {})
def test_no_propagators(self): propagator = CompositeHTTPPropagator([]) new_carrier = {} propagator.inject(new_carrier) self.assertEqual(new_carrier, {}) context = propagator.extract(carrier=new_carrier, context={}, getter=get_as_list) self.assertEqual(context, {})
def test_single_propagator(self): propagator = CompositeHTTPPropagator([self.mock_propagator_0]) new_carrier = {} propagator.inject(new_carrier) self.assertEqual(new_carrier, {"mock-0": "data"}) context = propagator.extract(carrier=new_carrier, context={}, getter=get_as_list) self.assertEqual(context, {"mock-0": "context"})
def test_multiple_propagators(self): propagator = CompositeHTTPPropagator( [self.mock_propagator_0, self.mock_propagator_1]) new_carrier = {} propagator.inject(dict.__setitem__, carrier=new_carrier) self.assertEqual(new_carrier, {"mock-0": "data", "mock-1": "data"}) context = propagator.extract(get_as_list, carrier=new_carrier, context={}) self.assertEqual(context, {"mock-0": "context", "mock-1": "context"})
def test_multiple_propagators_same_key(self): # test that when multiple propagators extract/inject the same # key, the later propagator values are extracted/injected propagator = CompositeHTTPPropagator( [self.mock_propagator_0, self.mock_propagator_2]) new_carrier = {} propagator.inject(new_carrier) self.assertEqual(new_carrier, {"mock-0": "data2"}) context = propagator.extract(carrier=new_carrier, context={}, getter=get_as_list) self.assertEqual(context, {"mock-0": "context2"})
def test_fields(self): propagator = CompositeHTTPPropagator([ self.mock_propagator_0, self.mock_propagator_1, self.mock_propagator_2, ]) mock_setter = Mock() propagator.inject({}, setter=mock_setter) inject_fields = set() for mock_call in mock_setter.mock_calls: inject_fields.add(mock_call[1][1]) self.assertEqual(inject_fields, propagator.fields)
def get_otel_tracer(): set_global_textmap(CompositeHTTPPropagator([B3Format()])) span_exporter = get_otlp_exporter() trace.get_tracer_provider().add_span_processor( BatchExportSpanProcessor(span_exporter)) return trace.get_tracer(__name__)
dd_agent = os.getenv('DD_AGENT_HOST') app = Sanic(name=service_name) resource = Resource.create({"service.name": service_name}) trace.set_tracer_provider(TracerProvider(resource=resource)) trace.get_tracer_provider().add_span_processor( DatadogExportSpanProcessor( DatadogSpanExporter(agent_url="http://" + dd_agent + ":8126", service=service_name))) global_textmap = get_global_textmap() if isinstance(global_textmap, CompositeHTTPPropagator) and not any( isinstance(p, DatadogFormat) for p in global_textmap._propagators): set_global_textmap( CompositeHTTPPropagator(global_textmap._propagators + [DatadogFormat()])) else: set_global_textmap(DatadogFormat()) tracer = trace.get_tracer(__name__) @app.route('/') async def test(request): param = request.args.get("param") with tracer.start_as_current_span("hello-world"): if param == "error": raise ValueError("forced server error") return json({'hello': param})
def configure_opentelemetry( access_token: str = _LS_ACCESS_TOKEN, span_exporter_endpoint: str = _OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, service_name: str = _LS_SERVICE_NAME, service_version: str = _LS_SERVICE_VERSION, propagators: str = _OTEL_PROPAGATORS, resource_attributes: str = _OTEL_RESOURCE_ATTRIBUTES, log_level: str = _OTEL_LOG_LEVEL, span_exporter_insecure: bool = _OTEL_EXPORTER_OTLP_TRACES_INSECURE, _auto_instrumented: bool = False, ): # pylint: disable=too-many-locals # pylint: disable=too-many-statements """ Configures OpenTelemetry with Lightstep environment variables This function works as a configuration layer that allows the Lightstep end user to use current environment variables seamlessly with OpenTelemetry. In this way, it is not needed to make any configuration changes to the environment before using OpenTelemetry. The configuration can be done via environment variables (prefixed with `LS`) or via arguments passed to this function. Each argument has a 1:1 correspondence with an environment variable, their description follows: Arguments: access_token (str): LS_ACCESS_TOKEN, the access token used to authenticate with the Lightstep satellite. This configuration value is mandatory. span_exporter_endpoint (str): OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, the URL of the Lightstep satellite where the spans are to be exported. Defaults to `ingest.lightstep.com:443`. service_name (str): LS_SERVICE_NAME, the name of the service that is used along with the access token to send spans to the Lighstep satellite. This configuration value is mandatory. service_version (str): LS_SERVICE_VERSION, the version of the service used to sernd spans to the Lightstep satellite. Defaults to `None`. propagators (str): OTEL_PROPAGATORS, a list of propagators to be used. The list is specified as a comma-separated string of values, for example: `a,b,c,d,e,f`. Defaults to `b3`. resource_attributes (str): OTEL_RESOURCE_ATTRIBUTES, a dictionary of key value pairs used to instantiate the resouce of the tracer provider. The dictionary is specified as a string of comma-separated `key=value` pairs. For example: `a=1,b=2,c=3`. Defaults to `telemetry.sdk.language=python,telemetry.sdk.version=X` where `X` is the version of this package. log_level (str): OTEL_LOG_LEVEL, one of: - `NOTSET` (0) - `DEBUG` (10) - `INFO` (20) - `WARNING` (30) - `ERROR` (40) - `CRITICAL` (50) Defaults to `logging.ERROR`. span_exporter_insecure (bool): OTEL_EXPORTER_OTLP_TRACES_INSECURE, a boolean value that indicates if an insecure channel is to be used to send spans to the satellite. Defaults to `False`. """ log_levels = { "NOTSET": NOTSET, "DEBUG": DEBUG, "INFO": INFO, "WARNING": WARNING, "ERROR": ERROR, "CRITICAL": CRITICAL, } # No environment variable is passed here as the first argument since what # is intended here is just to parse the value of the already obtained value # of the environment variables OTEL_PROPAGATORS and # OTEL_RESOURCE_ATTRIBUTES into a list and a dictionary. This is not done # at the attribute declaration to avoid having mutable objects as default # arguments. propagators = _env.list("", propagators) resource_attributes = _env.dict("", resource_attributes) log_level = log_level.upper() if log_level not in log_levels.keys(): message = ( "Invalid configuration: invalid log_level value." "It must be one of {}.".format(", ".join(log_levels.keys())) ) _logger.error(message) raise InvalidConfigurationError(message) log_level = log_levels[log_level] basicConfig(level=log_level) _logger.debug("configuration") if not _validate_service_name(service_name): message = ( "Invalid configuration: service name missing. " "Set environment variable LS_SERVICE_NAME" ) if not _auto_instrumented: message += ( " or call configure_opentelemetry with service_name defined" ) _logger.error(message) raise InvalidConfigurationError(message) resource_attributes["service.name"] = service_name logged_attributes = { "access_token": access_token, "span_exporter_endpoint": span_exporter_endpoint, "service_name": service_name, "propagators": propagators, "resource_attributes": resource_attributes, "log_level": getLevelName(log_level), "span_exporter_insecure": span_exporter_insecure, } if service_version is not None: resource_attributes["service.version"] = service_version logged_attributes["service_version"] = service_version for key, value in logged_attributes.items(): _logger.debug("%s: %s", key, value) if access_token is None: if ( span_exporter_endpoint == _DEFAULT_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ): message = ( "Invalid configuration: token missing. " "Must be set to send data to {}. " "Set environment variable LS_ACCESS_TOKEN" ).format(_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) if not _auto_instrumented: message += " or call configure_opentelemetry with access_token defined" _logger.error(message) raise InvalidConfigurationError(message) if access_token is not None and not _validate_token(access_token): message = ( "Invalid configuration: invalid token. " "Token must be a 32, 84 or 104 character long string." ) _logger.error(message) raise InvalidConfigurationError(message) _logger.debug("configuring propagation") # FIXME use entry points (instead of a dictionary) to locate propagator # classes set_global_textmap( CompositeHTTPPropagator( [ { "b3": B3Format(), "baggage": BaggagePropagator(), "tracecontext": TraceContextTextMapPropagator(), }[propagator] for propagator in propagators ] ) ) headers = None if access_token != "": headers = (("lightstep-access-token", access_token),) _logger.debug("configuring tracing") credentials = _common_configuration( set_tracer_provider, TracerProvider, "OTEL_PYTHON_TRACER_PROVIDER", span_exporter_insecure, ) get_tracer_provider().add_span_processor( BatchExportSpanProcessor( LightstepOTLPSpanExporter( endpoint=span_exporter_endpoint, credentials=credentials, headers=headers, ) ) ) if _ATTRIBUTE_HOST_NAME not in resource_attributes.keys() or not ( resource_attributes[_ATTRIBUTE_HOST_NAME] ): no_hostname_message = ( "set it with the environment variable OTEL_RESOURCE_ATTRIBUTES or " 'with the resource_attributes argument. Use "host.name" as key ' "in both cases." ) try: hostname = gethostname() if not hostname: _logger.warning("Hostname is empty, %s", no_hostname_message) else: resource_attributes[_ATTRIBUTE_HOST_NAME] = hostname # pylint: disable=broad-except except Exception: _logger.exception( "Unable to get hostname, %s", no_hostname_message ) get_tracer_provider().resource = Resource(resource_attributes) if log_level <= DEBUG: get_tracer_provider().add_span_processor( BatchExportSpanProcessor(ConsoleSpanExporter()) )
def configure_opentelemetry( access_token: str = _LS_ACCESS_TOKEN, span_endpoint: str = _OTEL_EXPORTER_OTLP_SPAN_ENDPOINT, metric_endpoint: str = _OTEL_EXPORTER_OTLP_METRIC_ENDPOINT, service_name: str = _LS_SERVICE_NAME, service_version: str = _LS_SERVICE_VERSION, propagator: list = _OTEL_PROPAGATORS, resource_labels: str = _OTEL_RESOURCE_LABELS, log_level: int = _OTEL_LOG_LEVEL, span_exporter_endpoint_insecure: bool = _OTEL_EXPORTER_OTLP_SPAN_INSECURE, metric_exporter_endpoint_insecure: bool = ( _OTEL_EXPORTER_OTLP_METRIC_INSECURE ), ): """ Configures OpenTelemetry with Lightstep environment variables This function works as a configuration layer that allows the Lightstep end user to use current environment variables seamlessly with OpenTelemetry. In this way, it is not needed to make any configuration changes to the environment before using OpenTelemetry. The configuration can be done via environment variables (prefixed with `LS`) or via arguments passed to this function. Each argument has a 1:1 correspondence with an environment variable, their description follows: Arguments: access_token (str): LS_ACCESS_TOKEN, the access token used to authenticate with the Lightstep satellite. This configuration value is mandatory. span_endpoint (str): OTEL_EXPORTER_OTLP_SPAN_ENDPOINT, the URL of the Lightstep satellite where the spans are to be exported. Defaults to `ingest.lightstep.com:443`. metric_endpoint (str): OTEL_EXPORTER_OTLP_METRIC_ENDPOINT, the URL of the metrics collector where the metrics are to be exported. Defaults to `ingest.lightstep.com:443/metrics`. service_name (str): LS_SERVICE_NAME, the name of the service that is used along with the access token to send spans to the Lighstep satellite. This configuration value is mandatory. service_version (str): LS_SERVICE_VERSION, the version of the service used to sernd spans to the Lightstep satellite. Defaults to `"unknown"`. propagator (list): OTEL_PROPAGATORS, a list of propagators to be used. Defaults to `["b3"]`. resource_labels (dict): OTEL_RESOURCE_LABELS, a dictionary of key value pairs used to instantiate the resouce of the tracer provider. Defaults to `{ "service.name": _LS_SERVICE_NAME, "service.version": _LS_SERVICE_VERSION, "telemetry.sdk.language": "python", "telemetry.sdk.version": "0.9b0", }` log_level (int): OTEL_LOG_LEVEL, a boolean value that indicates the log level. Defaults to `logging.DEBUG`. information is to be printed. Defaults to `False`. span_exporter_endpoint_insecure (bool): OTEL_EXPORTER_OTLP_SPAN_INSECURE, a boolean value that indicates if an insecure channel is to be used to send spans to the satellite. Defaults to `False`. metric_exporter_endpoint_insecure (bool): OTEL_EXPORTER_OTLP_METRIC_INSECURE, a boolean value that indicates if an insecure channel is to be used to send spans to the satellite. Defaults to `False`. """ basicConfig(level=log_level) _logger.debug("configuration") for key, value in { "access_token": access_token, "span_endpoint": span_endpoint, "metric_endpoint": metric_endpoint, "service_name": service_name, "service_version": service_version, "propagator": propagator, "resource_labels": resource_labels, "log_level": log_level, "span_exporter_endpoint_insecure": span_exporter_endpoint_insecure, "metric_exporter_endpoint_insecure": metric_exporter_endpoint_insecure, }.items(): _logger.debug("%s: %s", key, value) if not _validate_service_name(service_name): message = ( "Invalid configuration: service name missing. " "Set environment variable LS_SERVICE_NAME or call " "configure_opentelemetry with service_name defined" ) _logger.error(message) raise InvalidConfigurationError(message) if access_token is None: if span_endpoint == _DEFAULT_OTEL_EXPORTER_OTLP_SPAN_ENDPOINT: message = ( "Invalid configuration: token missing. " "Must be set to send data to {}. " "Set environment variable LS_ACCESS_TOKEN or call" "configure_opentelemetry with access_token defined" ).format(_OTEL_EXPORTER_OTLP_SPAN_ENDPOINT) _logger.error(message) raise InvalidConfigurationError(message) if not _validate_token(access_token): message = ( "Invalid configuration: invalid token. " "Token must be a 32, 84 or 104 character long string." ) _logger.error(message) raise InvalidConfigurationError(message) _logger.debug("configuring propagation") # FIXME use entry points (instead of a dictionary) to locate propagator # classes set_global_httptextformat( CompositeHTTPPropagator( [{"b3": B3Format}[propagator] for propagator in _OTEL_PROPAGATORS] ) ) _logger.debug("configuring tracing") metadata = None if _env.str("OPENTELEMETRY_PYTHON_TRACER_PROVIDER", None) is None: # FIXME now that new values can be set in the global configuration # object, check for this object having a tracer_provider attribute, if # not, set it to "sdk_tracer_provider" instead of using # set_tracer_provider, this in order to avoid having more than one # method of setting configuration. set_tracer_provider(TracerProvider()) if access_token != "": metadata = (("lightstep-access-token", access_token),) credentials = ssl_channel_credentials() if span_exporter_endpoint_insecure: credentials = None # FIXME Do the same for metrics when the OTLPMetricsExporter is in # OpenTelemetry. get_tracer_provider().add_span_processor( BatchExportSpanProcessor( LightstepOTLPSpanExporter( endpoint=span_endpoint, credentials=credentials, metadata=metadata, ) ) ) get_tracer_provider().resource = Resource(resource_labels) if log_level == DEBUG: get_tracer_provider().add_span_processor( BatchExportSpanProcessor(ConsoleSpanExporter()) )