mirror of
https://github.com/apache/arrow.git
synced 2026-10-29 09:05:47 +08:00
### Rationale for this change
Currently we are not building wheels with OpenTelemetry.
### What changes are included in this PR?
- Adding a new feature on our `vcpkg.json` to install required opentelemetry-cpp
- Enabling the opentelemetry feature when downloading dependencies via vcpkg for our wheels.
- Setting `ARROW_WITH_OPENTELEMETRY=ON` on the wheel build scripts
- Minor fix on checking exposed symbols (opentelemetry symbols are exposed)
- Minor improvement to `python/examples/flight/middleware.py` to be able to easily enable opentelemetry middleware tracing for testing.
- Fix on `server_tracing_middleware.cc` to retrieve tracer from `arrow::internal::tracing::GetTracer();` instead of `otel::trace::Provider::GetTracerProvider()->GetTracer("arrow");`
- Expose missing symbols to enable Windows builds
- Fix Windows compatibility adding windows compat header.
- Add `is_opentelemetry_enabled` utility function in pyarrow to validate whether OpenTelemetry was enabled or not when building Arrow C++. Expose this information on `pa.show_info()`.
### Are these changes tested?
Yes, wheels are being successfully built and tested locally with a built wheel that traces are being generated when properly configured.
Added test to validate traces appear on stdout and I've also validated Acero traces are being correctly generated by running the test suite and seeing the opentelemetry traces on Jaeger UI.
### Are there any user-facing changes?
Yes, PyArrow wheel users will be able to enable OpenTelemetry traces.
* GitHub Issue: #49382
Authored-by: Raúl Cumplido <raulcumplido@gmail.com>
Signed-off-by: Raúl Cumplido <raulcumplido@gmail.com>
176 lines
5.5 KiB
Python
176 lines
5.5 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you 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.
|
|
|
|
"""Example of invisibly propagating a request ID with middleware."""
|
|
|
|
import argparse
|
|
import sys
|
|
import threading
|
|
import uuid
|
|
|
|
import pyarrow as pa
|
|
import pyarrow.flight as flight
|
|
|
|
|
|
class TraceContext:
|
|
_locals = threading.local()
|
|
_locals.trace_id = None
|
|
|
|
@classmethod
|
|
def current_trace_id(cls):
|
|
if not getattr(cls._locals, "trace_id", None):
|
|
cls.set_trace_id(uuid.uuid4().hex)
|
|
return cls._locals.trace_id
|
|
|
|
@classmethod
|
|
def set_trace_id(cls, trace_id):
|
|
cls._locals.trace_id = trace_id
|
|
|
|
|
|
TRACE_HEADER = "x-tracing-id"
|
|
|
|
|
|
class TracingServerMiddleware(flight.ServerMiddleware):
|
|
def __init__(self, trace_id):
|
|
self.trace_id = trace_id
|
|
|
|
def sending_headers(self):
|
|
return {
|
|
TRACE_HEADER: self.trace_id,
|
|
}
|
|
|
|
|
|
class TracingServerMiddlewareFactory(flight.ServerMiddlewareFactory):
|
|
def start_call(self, info, headers):
|
|
print("Starting new call:", info)
|
|
if TRACE_HEADER in headers:
|
|
trace_id = headers[TRACE_HEADER][0]
|
|
print("Found trace header with value:", trace_id)
|
|
TraceContext.set_trace_id(trace_id)
|
|
return TracingServerMiddleware(TraceContext.current_trace_id())
|
|
|
|
|
|
class TracingClientMiddleware(flight.ClientMiddleware):
|
|
def sending_headers(self):
|
|
print("Sending trace ID:", TraceContext.current_trace_id())
|
|
return {
|
|
"x-tracing-id": TraceContext.current_trace_id(),
|
|
}
|
|
|
|
def received_headers(self, headers):
|
|
if TRACE_HEADER in headers:
|
|
trace_id = headers[TRACE_HEADER][0]
|
|
print("Found trace header with value:", trace_id)
|
|
# Don't overwrite our trace ID
|
|
|
|
|
|
class TracingClientMiddlewareFactory(flight.ClientMiddlewareFactory):
|
|
def start_call(self, info):
|
|
print("Starting new call:", info)
|
|
return TracingClientMiddleware()
|
|
|
|
|
|
class FlightServer(flight.FlightServerBase):
|
|
def __init__(self, delegate, **kwargs):
|
|
super().__init__(**kwargs)
|
|
if delegate:
|
|
self.delegate = flight.connect(
|
|
delegate,
|
|
middleware=(TracingClientMiddlewareFactory(),))
|
|
else:
|
|
self.delegate = None
|
|
|
|
def list_actions(self, context):
|
|
return [
|
|
("get-trace-id", "Get the trace context ID."),
|
|
]
|
|
|
|
def do_action(self, context, action):
|
|
trace_middleware = context.get_middleware("trace")
|
|
if trace_middleware:
|
|
TraceContext.set_trace_id(trace_middleware.trace_id)
|
|
if action.type == "get-trace-id":
|
|
if self.delegate:
|
|
for result in self.delegate.do_action(action):
|
|
yield result
|
|
else:
|
|
trace_id = TraceContext.current_trace_id().encode("utf-8")
|
|
print("Returning trace ID:", trace_id)
|
|
buf = pa.py_buffer(trace_id)
|
|
yield pa.flight.Result(buf)
|
|
else:
|
|
raise KeyError(f"Unknown action {action.type!r}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
subparsers = parser.add_subparsers(dest="command")
|
|
client = subparsers.add_parser("client", help="Run the client.")
|
|
client.add_argument("server")
|
|
client.add_argument("--request-id", default=None)
|
|
|
|
server = subparsers.add_parser("server", help="Run the server.")
|
|
server.add_argument(
|
|
"--listen",
|
|
required=True,
|
|
help="The location to listen on (example: grpc://localhost:5050)",
|
|
)
|
|
server.add_argument(
|
|
"--delegate",
|
|
required=False,
|
|
default=None,
|
|
help=("A location to delegate to. That is, this server will "
|
|
"simply call the given server for the response. Demonstrates "
|
|
"propagation of the trace ID between servers."),
|
|
)
|
|
server.add_argument(
|
|
"--otel",
|
|
action="store_true",
|
|
help="Use OpenTelemetry instrumentation."
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
if not getattr(args, "command"):
|
|
parser.print_help()
|
|
return 1
|
|
|
|
if args.command == "server":
|
|
middleware = {"trace": TracingServerMiddlewareFactory()}
|
|
if args.otel:
|
|
middleware["otel"] = flight.TracingServerMiddlewareFactory()
|
|
server = FlightServer(
|
|
args.delegate,
|
|
location=args.listen,
|
|
middleware=middleware)
|
|
server.serve()
|
|
elif args.command == "client":
|
|
client = flight.connect(
|
|
args.server,
|
|
middleware=(TracingClientMiddlewareFactory(),))
|
|
if args.request_id:
|
|
TraceContext.set_trace_id(args.request_id)
|
|
else:
|
|
TraceContext.set_trace_id("client-chosen-id")
|
|
|
|
for result in client.do_action(flight.Action("get-trace-id", b"")):
|
|
print(result.body.to_pybytes())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main() or 0)
|