def setup_instrumentation(app): settings: TracingSettings = _get_settings() _TRACE_PROVIDER = TracerProvider( resource=Resource.create({"service.name": settings.jaeger_service}) ) trace.set_tracer_provider(_TRACE_PROVIDER) if settings.jaeger_hostname: # pragma: no cover _JAEGER_EXPORTER = JaegerExporter( agent_host_name=settings.jaeger_hostname, agent_port=settings.jaeger_port, ) _TRACE_PROVIDER.add_span_processor(BatchSpanProcessor(_JAEGER_EXPORTER)) AioHttpClientInstrumentor().instrument() RequestsInstrumentor().instrument() # Register logging middleware app.middleware("http")(_log_requests_middleware) app.middleware("http")(_bind_logger_tracecontext_middleware) FastAPIInstrumentor.instrument_app(app) return app
def test_custom_tracer_provider(self): provider = TracerProvider( resource=Resource.create( { "service.name": "test", "deployment.environment": "env", "service.version": "1234", }, ), ) provider.add_span_processor( export.SimpleSpanProcessor(self.memory_exporter) ) SQLAlchemyInstrumentor().instrument(tracer_provider=provider) from sqlalchemy import create_engine # pylint: disable-all engine = create_engine("sqlite:///:memory:") cnx = engine.connect() cnx.execute("SELECT 1 + 1;").fetchall() spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 2) self.assertEqual(spans[0].resource.attributes["service.name"], "test") self.assertEqual( spans[0].resource.attributes["deployment.environment"], "env" ) self.assertEqual( spans[0].resource.attributes["service.version"], "1234" )
def _configure_tracing(options: _Options) -> TracerProvider: provider = TracerProvider(resource=options.resource) set_global_response_propagator(options.response_propagator) # type: ignore trace.set_tracer_provider(provider) for factory in options.span_exporter_factories: provider.add_span_processor(BatchSpanProcessor(factory(options))) return provider
class OpenTelemetryBase(unittest.TestCase): def setUp(self): if HAS_OPENTELEMETRY_INSTALLED: self.original_tracer_provider = trace_api.get_tracer_provider() self.tracer_provider = TracerProvider() self.memory_exporter = InMemorySpanExporter() span_processor = export.SimpleExportSpanProcessor( self.memory_exporter) self.tracer_provider.add_span_processor(span_processor) trace_api.set_tracer_provider(self.tracer_provider) def tearDown(self): if HAS_OPENTELEMETRY_INSTALLED: trace_api.set_tracer_provider(self.original_tracer_provider) def assertNoSpans(self): if HAS_OPENTELEMETRY_INSTALLED: span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) def assertSpanAttributes(self, name, status=StatusCanonicalCode.OK, attributes=None, span=None): if HAS_OPENTELEMETRY_INSTALLED: if not span: span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual(span.name, name) self.assertEqual(span.status.canonical_code, status) self.assertEqual(dict(span.attributes), attributes)
def setup(): reload_module(opentelemetry_tracing) tracer_provider = TracerProvider() memory_exporter = InMemorySpanExporter() span_processor = SimpleExportSpanProcessor(memory_exporter) tracer_provider.add_span_processor(span_processor) trace.set_tracer_provider(tracer_provider) yield memory_exporter
def init(tracefile: Optional[IO[str]] = None) -> None: tracer_provider = TracerProvider() if tracefile is not None: tracer_provider.add_span_processor( SimpleExportSpanProcessor( ConsoleSpanExporter(out=tracefile, formatter=single_line_json) ) ) trace.set_tracer_provider(tracer_provider)
def setUp(self): tracer_provider = TracerProvider() self.exporter = OTLPSpanExporter(insecure=True) tracer_provider.add_span_processor( SimpleExportSpanProcessor(self.exporter) ) self.tracer = tracer_provider.get_tracer(__name__) self.server = server(ThreadPoolExecutor(max_workers=10)) self.server.add_insecure_port("[::]:55680") self.server.start() event_mock = Mock( **{ "timestamp": 1591240820506462784, "attributes": OrderedDict([("a", 1), ("b", False)]), } ) type(event_mock).name = PropertyMock(return_value="a") self.span = _Span( "a", context=Mock( **{ "trace_state": OrderedDict([("a", "b"), ("c", "d")]), "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, } ), resource=SDKResource(OrderedDict([("a", 1), ("b", False)])), parent=Mock(**{"span_id": 12345}), attributes=OrderedDict([("a", 1), ("b", True)]), events=[event_mock], links=[ Mock( **{ "context.trace_id": 1, "context.span_id": 2, "attributes": OrderedDict([("a", 1), ("b", False)]), "kind": OTLPSpan.SpanKind.SPAN_KIND_INTERNAL, # pylint: disable=no-member } ) ], instrumentation_info=InstrumentationInfo( name="name", version="version" ), ) self.span.start() self.span.end() Configuration._reset() # pylint: disable=protected-access
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 init_tracing(): global initialized if initialized: return initialized = True provider = TracerProvider() trace.set_tracer_provider(provider) provider.add_span_processor(BatchExportSpanProcessor(ConsoleSpanExporter())) auto_instrument()
def tracer_provider(memory_exporter): original_tracer_provider = trace_api.get_tracer_provider() tracer_provider = TracerProvider() span_processor = export.SimpleSpanProcessor(memory_exporter) tracer_provider.add_span_processor(span_processor) trace_api.set_tracer_provider(tracer_provider) yield tracer_provider trace_api.set_tracer_provider(original_tracer_provider)
def _configure_tracing(options: Options) -> None: provider = TracerProvider(resource=Resource.create( attributes={ "service.name": options.service_name, "telemetry.auto.version": __version__, })) propagate.set_global_textmap(B3Format()) if options.response_propagation: set_global_response_propagator( ServerTimingResponsePropagator()) # type: ignore trace.set_tracer_provider(provider) exporter = _new_jaeger_exporter(options) provider.add_span_processor(BatchSpanProcessor(exporter))
def setUp(self): tracer_provider = TracerProvider() self.exporter = OTLPSpanExporter() tracer_provider.add_span_processor( SimpleExportSpanProcessor(self.exporter) ) self.tracer = tracer_provider.get_tracer(__name__) self.server = server(ThreadPoolExecutor(max_workers=10)) self.server.add_insecure_port("[::]:55678") self.server.start() event_mock = Mock( **{ "timestamp": 1591240820506462784, "attributes": OrderedDict([("a", 1), ("b", False)]), } ) type(event_mock).name = PropertyMock(return_value="a") self.span = Span( "a", context=Mock( **{ "trace_state": OrderedDict([("a", "b"), ("c", "d")]), "span_id": 10217189687419569865, "trace_id": 67545097771067222548457157018666467027, } ), resource=SDKResource(OrderedDict([("a", 1), ("b", False)])), parent=Mock(**{"span_id": 12345}), attributes=OrderedDict([("a", 1), ("b", True)]), events=[event_mock], links=[ Mock( **{ "context.trace_id": 1, "context.span_id": 2, "attributes": OrderedDict([("a", 1), ("b", False)]), "kind": SpanKind.INTERNAL, } ) ], ) self.span.start() self.span.end()
def tracer_provider( tracer_type: str = Provide[config.tracing.type], sample_rate: t.Optional[float] = Provide[config.tracing.sample_rate], zipkin_server_url: t.Optional[str] = Provide[ config.tracing.zipkin.url], jaeger_server_address: t.Optional[str] = Provide[ config.tracing.jaeger.address], jaeger_server_port: t.Optional[int] = Provide[ config.tracing.jaeger.port], ): from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from ..utils.telemetry import ParentBasedTraceIdRatio if sample_rate is None: sample_rate = 0.0 provider = TracerProvider( sampler=ParentBasedTraceIdRatio(sample_rate), # resource: Resource = Resource.create({}), # shutdown_on_exit: bool = True, # active_span_processor: Union[ # SynchronousMultiSpanProcessor, ConcurrentMultiSpanProcessor # ] = None, # id_generator: IdGenerator = None, ) if tracer_type == "zipkin" and zipkin_server_url is not None: from opentelemetry.exporter.zipkin.json import ZipkinExporter exporter = ZipkinExporter(endpoint=zipkin_server_url, ) provider.add_span_processor(BatchSpanProcessor(exporter)) return provider elif (tracer_type == "jaeger" and jaeger_server_address is not None and jaeger_server_port is not None): from opentelemetry.exporter.jaeger.thrift import JaegerExporter exporter = JaegerExporter( agent_host_name=jaeger_server_address, agent_port=jaeger_server_port, ) provider.add_span_processor(BatchSpanProcessor(exporter)) return provider else: return provider
def create_tracer_provider(**kwargs): """Helper to create a configured tracer provider. Creates and configures a `TracerProvider` with a `SimpleSpanProcessor` and a `InMemorySpanExporter`. All the parameters passed are forwarded to the TracerProvider constructor. Returns: A list with the tracer provider in the first element and the in-memory span exporter in the second. """ tracer_provider = TracerProvider(**kwargs) memory_exporter = InMemorySpanExporter() span_processor = export.SimpleSpanProcessor(memory_exporter) tracer_provider.add_span_processor(span_processor) return tracer_provider, memory_exporter
def __init__(self, env: Env) -> None: self.optional_variables = [ ("OPENTELEMETRY_ENABLED", env.bool), ("OTEL_TRACES_EXPORTER", env.str), ] self.configure(env) if not self.OPENTELEMETRY_ENABLED or self.OTEL_TRACES_EXPORTER == "none": raise PluginMissingConfiguration("Opentelemetry not enabled") if not HAS_OPENTELEMETRY: raise PluginMissingConfiguration("Extra packages for opentelemetry missing") pipeline_resource = Resource.create( { "pipeline.ci": str(settings.active_ci), "pipeline.commit_sha": settings.GIT_COMMIT_SHA, "pipeline.commit_ref_name": settings.GIT_COMMIT_REF_NAME, "pipeline.environment": settings.ENVIRONMENT_SLUG, "pipeline.id": settings.JOB_PIPELINE_ID, "pipeline.job_id": settings.JOB_ID, "pipeline.job_name": settings.JOB_NAME, "pipeline.pr_id": settings.PR_ID, "pipeline.project_id": settings.PROJECT_ID, "service.name": "kolga", # "service.version": "settings.KOLGA_VERSION", }, ) tracer_provider = TracerProvider(resource=pipeline_resource) span_processor = BatchSpanProcessor(self._get_exporter()) tracer_provider.add_span_processor(span_processor) trace.set_tracer_provider(tracer_provider) tracer = trace.get_tracer(__name__) self.context_detach_tokens: List[str] = [] self.lifecycle_key = context.create_key("kolga_lifecycle") self.known_exceptions: Set[int] = set() self.tracer = tracer # FIXME: We would like to get sub-traces from buildkit but at the time of writing it # segfaults when OTEL is detected: https://github.com/docker/buildx/pull/925. for key in os.environ: if key.startswith("OTEL_"): del os.environ[key]
def _init_tracing(exporters: Sequence[SpanExporter], ids_generator: IdsGenerator): service_name = _get_service_name() provider = TracerProvider( resource=Resource.create({"service.name": service_name}), ids_generator=ids_generator(), ) trace.set_tracer_provider(provider) for exporter_name, exporter_class in exporters.items(): exporter_args = {} if exporter_name not in [ EXPORTER_OTLP, EXPORTER_OTLP_SPAN, ]: exporter_args["service_name"] = service_name provider.add_span_processor( BatchExportSpanProcessor(exporter_class(**exporter_args)))
def _init_tracing( exporters: Dict[str, Type[SpanExporter]], id_generator: IdGenerator, 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 = TracerProvider( id_generator=id_generator(), resource=Resource.create(auto_resource), ) trace.set_tracer_provider(provider) for _, exporter_class in exporters.items(): exporter_args = {} provider.add_span_processor( BatchSpanProcessor(exporter_class(**exporter_args)))
def test_custom_tracer_provider(celery_app, memory_exporter): @celery_app.task def fn_task(): return 42 resource = resources.Resource.create({}) tracer_provider = TracerProvider(resource=resource) span_processor = export.SimpleExportSpanProcessor(memory_exporter) tracer_provider.add_span_processor(span_processor) trace_api.set_tracer_provider(tracer_provider) CeleryInstrumentor().uninstrument() CeleryInstrumentor().instrument(tracer_provider=tracer_provider) fn_task.delay() spans_list = memory_exporter.get_finished_spans() assert len(spans_list) == 1 span = spans_list[0] assert span.resource == resource
from opentelemetry.exporter.otlp.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export.controller import PushController from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor span_exporter = OTLPSpanExporter( # optional # endpoint:="myCollectorURL:55678", # credentials=ChannelCredentials(credentials), # metadata=(("metadata", "metadata")), ) tracer_provider = TracerProvider() trace.set_tracer_provider(tracer_provider) span_processor = BatchExportSpanProcessor(span_exporter) tracer_provider.add_span_processor(span_processor) metric_exporter = OTLPMetricsExporter( # optional # endpoint:="myCollectorURL:55678", # credentials=ChannelCredentials(credentials), # metadata=(("metadata", "metadata")), ) # Meter is responsible for creating and recording metrics metrics.set_meter_provider(MeterProvider()) meter = metrics.get_meter(__name__) # controller collects metrics created from meter and exports it via the # exporter every interval controller = PushController(meter, metric_exporter, 5)
class SodaTelemetry: """Main entry point for Open Telemetry tracing. For more info about what and why visit https://github.com/sodadata/soda-sql/issues/543. The main goal of this class is to concentrate as much tracing data and logic as reasonable. This code design means that compromises have been made on code design in order to facilitate maximum transparency and avoid scattering tracing code around the codebase. With that being said, some tracing is still present in other files, e.g.: - `/core/sodasql/cli/cli.py` - CLI arguments and options tracing - `/core/sodasql/scan/warehouse.py` - safe datasource type and hash tracing This list is not necessarily exhaustive, search for `from sodasql.telemetry.soda_telemetry import SodaTelemetry` imports OR `set_attribute` method usage to obtain the full list. """ ENDPOINT = 'https://collect.soda.io/v1/traces' __instance = None soda_config = ConfigHelper.get_instance() @staticmethod def get_instance(test_mode: bool = False): if test_mode: SodaTelemetry.__instance = None if SodaTelemetry.__instance is None: SodaTelemetry(test_mode=test_mode) return SodaTelemetry.__instance def __init__(self, test_mode: bool): if SodaTelemetry.__instance is not None: raise Exception("This class is a singleton!") else: SodaTelemetry.__instance = self self.__send = self.soda_config.send_anonymous_usage_stats or test_mode if self.__send: logger.info("Setting up usage telemetry.") self.__provider = TracerProvider( resource=Resource.create( { 'os.architecture': platform.architecture(), 'python.version': platform.python_version(), 'python.implementation': platform.python_implementation(), ResourceAttributes.OS_TYPE: platform.system(), ResourceAttributes.OS_VERSION: platform.version(), 'platform': platform.platform(), ResourceAttributes.SERVICE_VERSION: SODA_SQL_VERSION, ResourceAttributes.SERVICE_NAME: 'soda', ResourceAttributes.SERVICE_NAMESPACE: 'soda-sql', } ) ) if test_mode: self.__setup_for_test() else: self.__setup() else: logger.info("Skipping usage telemetry setup.") def __setup(self): """Set up Open Telemetry processors and exporters for normal use. """ local_debug_mode = self.soda_config.get_value('tracing_local_debug_mode') if local_debug_mode or logger.getEffectiveLevel() == logging.DEBUG: self.__provider.add_span_processor(BatchSpanProcessor(SodaConsoleSpanExporter())) if not local_debug_mode: otlp_exporter = SodaOTLPSpanExporter(endpoint=self.ENDPOINT) otlp_processor = BatchSpanProcessor(otlp_exporter) self.__provider.add_span_processor(otlp_processor) trace.set_tracer_provider(self.__provider) def __setup_for_test(self): """Set up Open Telemetry processors and exporters for usage in tests. """ self.__provider.add_span_processor(SimpleSpanProcessor(MemorySpanExporter.get_instance())) trace.set_tracer_provider(self.__provider) def set_attribute(self, key: str, value: str) -> None: """Set attribute value in the current span. """ if self.__send: current_span = trace.get_current_span() current_span.set_attribute(key, value) @staticmethod def obtain_datasource_hash(dialect: Dialect): return dialect.generate_hash_safe() @property def user_cookie_id(self) -> str: return self.soda_config.get_value('user_cookie_id')
import requests from opentelemetry import trace from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.propagate import set_global_textmap from opentelemetry.propagators.cloud_trace_propagator import ( CloudTraceFormatPropagator, ) from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor set_global_textmap(CloudTraceFormatPropagator()) tracer_provider = TracerProvider() cloud_trace_exporter = CloudTraceSpanExporter() tracer_provider.add_span_processor( # BatchSpanProcessor buffers spans and sends them in batches in a # background thread. The default parameters are sensible, but can be # tweaked to optimize your performance BatchSpanProcessor(cloud_trace_exporter) ) trace.set_tracer_provider(tracer_provider) tracer = trace.get_tracer(__name__) RequestsInstrumentor().instrument() res = requests.get("http://localhost:6000") print(res.text)
import logging from flask import Flask, request from opentelemetry import propagators, trace from opentelemetry.exporter.otlp.trace_exporter import OTLPSpanExporter from opentelemetry.instrumentation.flask import FlaskInstrumentor from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor logging.basicConfig(level=logging.DEBUG) app = Flask(__name__) FlaskInstrumentor().instrument_app(app) tracer_provider = TracerProvider(resource=Resource.create({"service.name": "backend"})) tracer_provider.add_span_processor( SimpleExportSpanProcessor(OTLPSpanExporter(endpoint="opentelemetry-collector:55680")) ) trace.set_tracer_provider(tracer_provider) @app.route("/backend") def server_request(): return "served" if __name__ == "__main__": app.run(host="0.0.0.0", port=8081)
from opentelemetry.ext import http_requests from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor from opentelemetry.ext.wsgi import OpenTelemetryMiddleware # configure tracer and exporter ot_exporter = CollectorSpanExporter( service_name="py-service", endpoint="localhost:55678", ) provider = TracerProvider() trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__) provider.add_span_processor(BatchExportSpanProcessor(ot_exporter)) # instrument http client http_requests.enable(provider) # create and instrument flask server app = Flask(__name__) app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) @app.route("/") def hello(): with tracer.start_as_current_span("fetch-from-node"): response = fetch_from_node() return "hello from python\n" + response
SERVICE_NAME: str = "orchestrator-core" LOGGING_HOST: str = "localhost" LOG_LEVEL: str = "DEBUG" SLACK_ENGINE_SETTINGS_HOOK_ENABLED: bool = False SLACK_ENGINE_SETTINGS_HOOK_URL: str = "" TRACING_ENABLED: bool = False TRANSLATIONS_DIR: Optional[Path] = None WEBSOCKET_BROADCASTER_URL: str = "memory://" ENABLE_WEBSOCKETS: bool = True DISABLE_INSYNC_CHECK: bool = False DEFAULT_PRODUCT_WORKFLOWS: List[str] = ["modify_note"] class Oauth2Settings(BaseSettings): OAUTH2_ACTIVE: bool = False OAUTH2_RESOURCE_SERVER_ID: str = "" OAUTH2_RESOURCE_SERVER_SECRET: str = "" OAUTH2_TOKEN_URL: str = "" OIDC_CONF_WELL_KNOWN_URL: str = "" OPA_URL: str = "http://127.0.0.1:8181/v1/data/automation/authorization/allow" app_settings = AppSettings() oauth2_settings = Oauth2Settings() # Tracer settings tracer_provider = TracerProvider() jaeger_exporter = JaegerExporter(agent_host_name=app_settings.LOGGING_HOST, udp_split_oversized_batches=True) tracer_provider.add_span_processor(BatchSpanProcessor(jaeger_exporter))
# 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. # tracing.py from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchSpanProcessor, ConsoleSpanExporter, ) provider = TracerProvider() processor = BatchSpanProcessor(ConsoleSpanExporter()) provider.add_span_processor(processor) trace.set_tracer_provider(provider) tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): print("Hello world from OpenTelemetry Python!")
TRACER_SERVICE_NAME_ENV_VARIABLE = 'TRACER_SERVICE_NAME' logger = Logger() class LoggerContextSimpleExportSpanProcessor(SimpleSpanProcessor): def __init__(self, span_exporter): super().__init__(span_exporter) def on_start(self, span, parent_context=None): trace_id = format_trace_id(span.context.trace_id) logger.structure_logs(append=True, traceId=trace_id, correlationId=trace_id) trace_provider = TracerProvider(resource=Resource.create({ SERVICE_NAME: os.environ.get(TRACER_SERVICE_NAME_ENV_VARIABLE, 'TRACER_SERVICE_NAME_UNSPECIFIED') })) logger_span_exporter = LoggerSpanExporter() trace_provider.add_span_processor( LoggerContextSimpleExportSpanProcessor(logger_span_exporter)) trace.set_tracer_provider(trace_provider) tracer: Tracer = trace.get_tracer(__name__) propagate.set_global_textmap(B3MultiFormat()) BotocoreInstrumentor().instrument()
import requests from flask import Flask from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter from opentelemetry.instrumentation.flask import FlaskInstrumentor from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.exporter.jaeger.thrift import JaegerExporter exporter = ConsoleSpanExporter() resource = Resource.create({"service.name": "grocery-store"}) provider = TracerProvider(resource=resource) span_processor = BatchSpanProcessor(exporter) provider.add_span_processor(span_processor) provider.add_span_processor(BatchSpanProcessor(JaegerExporter())) trace.set_tracer_provider(provider) app = Flask(__name__) FlaskInstrumentor().instrument_app(app) RequestsInstrumentor().instrument() @app.route("/") def welcome(): with trace.get_tracer(__name__).start_as_current_span("welcome message"): return "Welcome to the grocery store!" @app.route("/whats-in-store")