| # |
| # 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. |
| # |
| |
| import inspect |
| |
| from skywalking import Layer, Component, config |
| from skywalking.trace.carrier import Carrier |
| from skywalking.trace.context import get_context, NoopContext |
| from skywalking.trace.span import NoopSpan |
| from skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode |
| |
| link_vector = ['https://docs.python.org/3/library/http.server.html', |
| 'https://werkzeug.palletsprojects.com/'] |
| support_matrix = { |
| 'http_server': { |
| '>=3.7': ['*'] |
| }, |
| 'werkzeug': { |
| '>=3.7': ['1.0.1', '2.0'] |
| } |
| } |
| note = """""" |
| |
| |
| def install(): |
| from http.server import BaseHTTPRequestHandler |
| |
| _handle = BaseHTTPRequestHandler.handle |
| |
| def _sw_handle(handler: BaseHTTPRequestHandler): |
| clazz = handler.__class__ |
| if 'werkzeug.serving.WSGIRequestHandler' == '.'.join([clazz.__module__, clazz.__name__]): |
| wrap_werkzeug_request_handler(handler) |
| else: |
| wrap_default_request_handler(handler) |
| _handle(handler) |
| |
| BaseHTTPRequestHandler.handle = _sw_handle |
| |
| def _sw_send_response_only(self, code, *args, **kwargs): |
| self._status_code = code |
| |
| return _send_response_only(self, code, *args, **kwargs) |
| |
| _send_response_only = BaseHTTPRequestHandler.send_response_only |
| BaseHTTPRequestHandler.send_response_only = _sw_send_response_only |
| |
| |
| def wrap_werkzeug_request_handler(handler): |
| """ |
| Wrap run_wsgi of werkzeug.serving.WSGIRequestHandler to add skywalking instrument code. |
| """ |
| _run_wsgi = handler.run_wsgi |
| |
| def _wrap_run_wsgi(): |
| carrier = Carrier() |
| method = handler.command |
| |
| for item in carrier: |
| item.val = handler.headers[item.key.capitalize()] |
| path = handler.path or '/' |
| |
| span = NoopSpan(NoopContext()) if config.ignore_http_method_check(method) \ |
| else get_context().new_entry_span(op=path.split('?')[0], carrier=carrier) |
| |
| with span: |
| url = f"http://{handler.headers['Host']}{path}" if 'Host' in handler.headers else path |
| span.layer = Layer.Http |
| span.component = Component.General |
| client_address = handler.client_address |
| span.peer = f'{client_address[0]}:{client_address[1]}' |
| span.tag(TagHttpMethod(method)) |
| span.tag(TagHttpURL(url)) |
| |
| try: |
| return _run_wsgi() |
| finally: |
| status_code = int(getattr(handler, '_status_code', -1)) |
| if status_code > -1: |
| span.tag(TagHttpStatusCode(status_code)) |
| if status_code >= 400: |
| span.error_occurred = True |
| |
| handler.run_wsgi = _wrap_run_wsgi |
| |
| def _sw_send_response(self, code, *args, **kwargs): |
| self._status_code = code |
| |
| return _send_response(self, code, *args, **kwargs) |
| |
| WSGIRequestHandler = handler.__class__ # noqa |
| |
| if not getattr(WSGIRequestHandler, '_sw_wrapped', False): |
| _send_response = WSGIRequestHandler.send_response |
| WSGIRequestHandler.send_response = _sw_send_response |
| WSGIRequestHandler._sw_wrapped = True |
| |
| |
| def wrap_default_request_handler(handler): |
| http_methods = ('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH') |
| for method in http_methods: |
| _wrap_do_method(handler, method) |
| |
| |
| def _wrap_do_method(handler, method): |
| if hasattr(handler, f'do_{method}') and inspect.ismethod(getattr(handler, f'do_{method}')): |
| _do_method = getattr(handler, f'do_{method}') |
| |
| def _sw_do_method(): |
| carrier = Carrier() |
| for item in carrier: |
| item.val = handler.headers[item.key.capitalize()] |
| path = handler.path or '/' |
| |
| span = NoopSpan(NoopContext()) if config.ignore_http_method_check(method) \ |
| else get_context().new_entry_span(op=path.split('?')[0], carrier=carrier) |
| |
| with span: |
| url = f"http://{handler.headers['Host']}{path}" if 'Host' in handler.headers else path |
| span.layer = Layer.Http |
| span.component = Component.General |
| client_address = handler.client_address |
| span.peer = f'{client_address[0]}:{client_address[1]}' |
| span.tag(TagHttpMethod(method)) |
| span.tag(TagHttpURL(url)) |
| |
| try: |
| _do_method() |
| finally: |
| status_code = int(getattr(handler, '_status_code', -1)) |
| if status_code > -1: |
| span.tag(TagHttpStatusCode(status_code)) |
| if status_code >= 400: |
| span.error_occurred = True |
| |
| setattr(handler, f'do_{method}', _sw_do_method) |