Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Tornado Plugin #48

Merged
merged 2 commits into from
Jul 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Library | Plugin Name
| [PyMySQL](https://pymysql.readthedocs.io/en/latest/) | `sw_pymysql` |
| [Django](https://www.djangoproject.com/) | `sw_django` |
| [redis-py](https://github.com/andymccurdy/redis-py/) | `sw_redis` |
| [tornado](https://www.tornadoweb.org/en/stable/) | `sw_tornado` |

## API

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"Werkzeug",
"pymysql",
"redis",
"tornado",
],
},
classifiers=[
Expand Down
1 change: 1 addition & 0 deletions skywalking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Component(Enum):
Requests = 7002
PyMysql = 7003
Django = 7004
Tornado = 7005
Redis = 7


Expand Down
100 changes: 100 additions & 0 deletions skywalking/plugins/sw_tornado/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#
# 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 logging
from inspect import iscoroutinefunction, isawaitable

from skywalking import Layer, Component
from skywalking.trace import tags
from skywalking.trace.carrier import Carrier
from skywalking.trace.context import get_context
from skywalking.trace.tags import Tag

logger = logging.getLogger(__name__)


def install():
try:
from tornado.web import RequestHandler
old_execute = RequestHandler._execute
old_log_exception = RequestHandler.log_exception
RequestHandler._execute = _gen_sw_get_response_func(old_execute)

def _sw_handler_uncaught_exception(self: RequestHandler, ty, value, tb, *args, **kwargs):
if value is not None:
entry_span = get_context().active_span()
if entry_span is not None:
entry_span.raised()

return old_log_exception(self, ty, value, tb, *args, **kwargs)

RequestHandler.log_exception = _sw_handler_uncaught_exception
except Exception:
logger.warning('failed to install plugin %s', __name__)


def _gen_sw_get_response_func(old_execute):
from tornado.gen import coroutine

awaitable = iscoroutinefunction(old_execute)
if awaitable:
# Starting Tornado 6 RequestHandler._execute method is a standard Python coroutine (async/await)
# In that case our method should be a coroutine function too
async def _sw_get_response(self, *args, **kwargs):
request = self.request
context = get_context()
carrier = Carrier()
for item in carrier:
if item.key.capitalize() in request.headers:
item.val = request.headers[item.key.capitalize()]
with context.new_entry_span(op=request.path, carrier=carrier) as span:
span.layer = Layer.Http
span.component = Component.Tornado
peer = request.connection.stream.socket.getpeername()
span.peer = '{0}:{1}'.format(*peer)
span.tag(Tag(key=tags.HttpMethod, val=request.method))
span.tag(
Tag(key=tags.HttpUrl, val='{}://{}{}'.format(request.protocol, request.host, request.path)))
result = old_execute(self, *args, **kwargs)
span.tag(Tag(key=tags.HttpStatus, val=self._status_code))
if isawaitable(result):
result = await result
if self._status_code >= 400:
span.error_occurred = True
return result
else:
@coroutine
def _sw_get_response(self, *args, **kwargs):
request = self.request
context = get_context()
carrier = Carrier()
for item in carrier:
if item.key.capitalize() in request.headers:
item.val = request.headers[item.key.capitalize()]
with context.new_entry_span(op=request.path, carrier=carrier) as span:
span.layer = Layer.Http
span.component = Component.Tornado
peer = request.connection.stream.socket.getpeername()
span.peer = '{0}:{1}'.format(*peer)
span.tag(Tag(key=tags.HttpMethod, val=request.method))
span.tag(
Tag(key=tags.HttpUrl, val='{}://{}{}'.format(request.protocol, request.host, request.path)))
result = yield from old_execute(self, *args, **kwargs)
span.tag(Tag(key=tags.HttpStatus, val=self._status_code))
if self._status_code >= 400:
span.error_occurred = True
return result
return _sw_get_response
16 changes: 16 additions & 0 deletions tests/plugin/sw_tornado/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# 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.
#
60 changes: 60 additions & 0 deletions tests/plugin/sw_tornado/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#
# 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.
#

version: '2.1'

services:
collector:
extends:
service: collector
file: ../docker/docker-compose.base.yml

provider:
extends:
service: agent
file: ../docker/docker-compose.base.yml
ports:
- 9091:9091
volumes:
- ./services/provider.py:/provider.py
command: ['bash', '-c', 'pip3 install tornado && python3 /provider.py']
depends_on:
collector:
condition: service_healthy
healthcheck:
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091"]
interval: 5s
timeout: 60s
retries: 120

consumer:
extends:
service: agent
file: ../docker/docker-compose.base.yml
ports:
- 9090:9090
volumes:
- ./services/consumer.py:/consumer.py
command: ['bash', '-c', 'pip3 install tornado && python3 /consumer.py']
depends_on:
collector:
condition: service_healthy
provider:
condition: service_healthy

networks:
beyond:
91 changes: 91 additions & 0 deletions tests/plugin/sw_tornado/expected.data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#
# 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.
#

segmentItems:
- serviceName: provider
segmentSize: 1
segments:
- segmentId: not null
spans:
- operationName: /users
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
tags:
- key: http.method
value: GET
- key: url
value: http://provider:9091/users
- key: status.code
value: '200'
refs:
- parentEndpoint: /users
networkAddress: provider:9091
refType: CrossProcess
parentSpanId: 1
parentTraceSegmentId: not null
parentServiceInstance: not null
parentService: consumer
traceId: not null
startTime: gt 0
endTime: gt 0
componentId: 7005
spanType: Entry
peer: not null
skipAnalysis: false
- serviceName: consumer
segmentSize: 1
segments:
- segmentId: not null
spans:
- operationName: /users
operationId: 0
parentSpanId: 0
spanId: 1
spanLayer: Http
tags:
- key: http.method
value: GET
- key: url
value: http://provider:9091/users
- key: status.code
value: '200'
startTime: gt 0
endTime: gt 0
componentId: 7002
spanType: Exit
peer: provider:9091
skipAnalysis: false
- operationName: /users
operationId: 0
parentSpanId: -1
spanId: 0
spanLayer: Http
tags:
- key: http.method
value: GET
- key: url
value: http://0.0.0.0:9090/users
- key: status.code
value: '200'
startTime: gt 0
endTime: gt 0
componentId: 7005
spanType: Entry
peer: not null
skipAnalysis: false
16 changes: 16 additions & 0 deletions tests/plugin/sw_tornado/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# 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.
#
40 changes: 40 additions & 0 deletions tests/plugin/sw_tornado/services/consumer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# 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.
#
from skywalking import agent, config

if __name__ == "__main__":
config.service_name = 'consumer'
config.logging_level = 'DEBUG'
agent.start()

import requests
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
res = requests.get("http://provider:9091/users")
self.write(res.text)

def make_app():
return tornado.web.Application([
(r"/users", MainHandler),
])

app = make_app()
app.listen(9090, '0.0.0.0')
tornado.ioloop.IOLoop.current().start()
42 changes: 42 additions & 0 deletions tests/plugin/sw_tornado/services/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# 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.
#
from skywalking import agent, config

if __name__ == "__main__":
config.service_name = "provider"
config.logging_level = "DEBUG"
agent.start()

import json
import time

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
time.sleep(0.5)
self.write(json.dumps({'song': 'Despacito', 'artist': 'Luis Fonsi'}))

def make_app():
return tornado.web.Application([
(r"/users", MainHandler),
])

app = make_app()
app.listen(9091, '0.0.0.0')
tornado.ioloop.IOLoop.current().start()
Loading