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

[plugin] add falcon plugin #146

Merged
merged 20 commits into from
Aug 11, 2021
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Change Logs

### 0.7.0

- New plugins
- Falcon Plugin (#146)

kezhenxu94 marked this conversation as resolved.
Show resolved Hide resolved
### 0.6.0

- Fixes:
Expand Down
1 change: 1 addition & 0 deletions docs/Plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Library | Versions | Plugin Name
| [pyramid](https://trypyramid.com) | >= 1.9 | `sw_pyramid` |
| [psycopg2](https://www.psycopg.org/) | >= 2.8.6 | `sw_psycopg2` |
| [celery](https://docs.celeryproject.org/) | >= 4.2.1 | `sw_celery` |
| [falcon](https://falcon.readthedocs.io/en/stable/) | >= 1.4.1 | `sw_falcon` |

* Note: The celery server running with "celery -A ..." should be run with the http protocol as it uses multiprocessing by default which is not compatible with the grpc protocol implementation in skywalking currently. Celery clients can use whatever protocol they want.

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ sqlparse==0.3.1
testcontainers==3.0.3
toml==0.10.1
tornado==6.0.4
hug==2.4.1
urllib3==1.25.10
websockets==8.1
websocket-client==0.57.0
Expand Down
1 change: 1 addition & 0 deletions skywalking/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Component(Enum):
Pyramid = 7009
Psycopg = 7010
Celery = 7011
Falcon = 7012
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i add it.



class Layer(Enum):
Expand Down
98 changes: 98 additions & 0 deletions skywalking/plugins/sw_falcon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#
# 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 Layer, Component
from skywalking.trace.carrier import Carrier
from skywalking.trace.context import get_context
from skywalking.trace.span import NoopSpan
from skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpParams, TagHttpStatusCode


def install():
from falcon import API, request, response

_original_falcon_api = API.__call__
_original_falcon_handle_exception = API._handle_exception

def params_tostring(params):
return "\n".join([k + "=" + v for k, v in params.items()])

def _sw_falcon_api(this: API, env, start_response):
context = get_context()
carrier = Carrier()
headers = get_headers(env)
for item in carrier:
key = item.key.replace("_", "-") if "_" in item.key else item.key
if key.capitalize() in headers:
item.val = headers[key.capitalize()]
with context.new_entry_span(op="/", carrier=carrier) as span:
span.layer = Layer.Http
span.component = Component.Falcon

from falcon import RequestOptions

req = request.Request(env, RequestOptions())
span.op = str(req.url).split("?")[0]
span.peer = "%s:%s" % (req.remote_addr, req.port)

span.tag(TagHttpMethod(req.method))
span.tag(TagHttpURL(str(req.url)))
if req.params:
span.tag(TagHttpParams(params_tostring(req.params)[0:]))

resp = _original_falcon_api(this, env, start_response)

from falcon import ResponseOptions

resp_obj = response.Response(ResponseOptions())

resp_status = parse_status(resp_obj.status)
if int(resp_status[0]) >= 400:
span.error_occurred = True

span.tag(TagHttpStatusCode(int(resp_status[0])))

return resp

def _sw_handle_exception(this: API, req, resp, ex, params):
if ex is not None:
entry_span = get_context().active_span()
if entry_span is not None and type(entry_span) is not NoopSpan:
entry_span.raised()

return _original_falcon_handle_exception(this, req, resp, ex, params)

API.__call__ = _sw_falcon_api
API._handle_exception = _sw_handle_exception


def get_headers(env):
headers = {}
wsgi_content_headers = frozenset(["CONTENT_TYPE", "CONTENT_LENGTH"])

for name, value in env.items():
if name.startswith("HTTP_"):
headers[name[5:].replace("_", "-")] = value

elif name in wsgi_content_headers:
headers[name.replace("_", "-")] = value

return headers


def parse_status(status_str):
return status_str.split(" ") if status_str else [404, "status is empty"]
16 changes: 16 additions & 0 deletions tests/plugin/sw_falcon/__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_falcon/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:
- .:/app
command: [ 'bash', '-c', 'pip install hug && pip install -r /app/requirements.txt && hug -f /app/services/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:
- .:/app
command: [ 'bash', '-c', 'pip install hug && pip install -r /app/requirements.txt && hug -f /app/services/consumer.py' ]
depends_on:
collector:
condition: service_healthy
provider:
condition: service_healthy

networks:
beyond:
84 changes: 84 additions & 0 deletions tests/plugin/sw_falcon/expected.data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#
# 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:
- segmentSize: 1
segments:
- segmentId: not null
spans:
- componentId: 7012
endTime: gt 0
isError: false
operationId: 0
operationName: http://provider:9091/users
parentSpanId: -1
peer: not null
skipAnalysis: false
spanId: 0
spanLayer: Http
spanType: Entry
startTime: gt 0
tags:
- key: http.method
value: GET
- key: http.url
value: http://provider:9091/users
- key: http.status.code
value: '200'
serviceName: provider
- segmentSize: 1
segments:
- segmentId: not null
spans:
- componentId: 7002
endTime: gt 0
isError: false
operationId: 0
operationName: /users
parentSpanId: 0
peer: provider:9091
skipAnalysis: false
spanId: 1
spanLayer: Http
spanType: Exit
startTime: gt 0
tags:
- key: http.method
value: GET
- key: http.url
value: http://provider:9091/users
- key: http.status.code
value: '200'
- componentId: 7012
endTime: gt 0
isError: false
operationId: 0
operationName: http://0.0.0.0:9090/users
parentSpanId: -1
peer: not null
skipAnalysis: false
spanId: 0
spanLayer: Http
spanType: Entry
startTime: gt 0
tags:
- key: http.method
value: GET
- key: http.url
value: http://0.0.0.0:9090/users
- key: http.status.code
value: '200'
serviceName: consumer
16 changes: 16 additions & 0 deletions tests/plugin/sw_falcon/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.
#
33 changes: 33 additions & 0 deletions tests/plugin/sw_falcon/services/consumer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# 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

import requests
import hug

config.service_name = 'consumer'
config.logging_level = 'DEBUG'
agent.start()


@hug.get('/users')
def get():
res = requests.get("http://provider:9091/users")
return res.json()


hug.API(__name__).http.serve(port=9090)
35 changes: 35 additions & 0 deletions tests/plugin/sw_falcon/services/provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#
# 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

import hug
import time
import json

config.service_name = 'provider'
config.logging_level = 'DEBUG'
agent.start()


@hug.get('/users')
def get():
time.sleep(0.5)
return json.dumps({'song': 'Despacito', 'artist': 'Luis Fonsi'})


hug.API(__name__).http.serve(port=9091)
Loading