Skip to content

Commit e118f26

Browse files
authored
Add falcon plugin (#146)
1 parent d988f59 commit e118f26

File tree

12 files changed

+389
-0
lines changed

12 files changed

+389
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## Change Logs
22

3+
### 0.7.0
4+
5+
- New plugins
6+
- Falcon Plugin (#146)
7+
38
### 0.6.0
49

510
- Fixes:

docs/Plugins.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Library | Versions | Plugin Name
2020
| [pyramid](https://trypyramid.com) | >= 1.9 | `sw_pyramid` |
2121
| [psycopg2](https://www.psycopg.org/) | >= 2.8.6 | `sw_psycopg2` |
2222
| [celery](https://docs.celeryproject.org/) | >= 4.2.1 | `sw_celery` |
23+
| [falcon](https://falcon.readthedocs.io/en/stable/) | >= 1.4.1 | `sw_falcon` |
2324

2425
* 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.
2526

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ sqlparse==0.3.1
5454
testcontainers==3.0.3
5555
toml==0.10.1
5656
tornado==6.0.4
57+
hug==2.4.1
5758
urllib3==1.25.10
5859
websockets==8.1
5960
websocket-client==0.57.0

skywalking/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Component(Enum):
4343
Pyramid = 7009
4444
Psycopg = 7010
4545
Celery = 7011
46+
Falcon = 7012
4647

4748

4849
class Layer(Enum):

skywalking/plugins/sw_falcon.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
from skywalking import Layer, Component
19+
from skywalking.trace.carrier import Carrier
20+
from skywalking.trace.context import get_context
21+
from skywalking.trace.span import NoopSpan
22+
from skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpParams, TagHttpStatusCode
23+
24+
25+
def install():
26+
from falcon import API, request, response
27+
28+
_original_falcon_api = API.__call__
29+
_original_falcon_handle_exception = API._handle_exception
30+
31+
def params_tostring(params):
32+
return "\n".join([k + "=" + v for k, v in params.items()])
33+
34+
def _sw_falcon_api(this: API, env, start_response):
35+
context = get_context()
36+
carrier = Carrier()
37+
headers = get_headers(env)
38+
for item in carrier:
39+
key = item.key.replace("_", "-") if "_" in item.key else item.key
40+
if key.capitalize() in headers:
41+
item.val = headers[key.capitalize()]
42+
with context.new_entry_span(op="/", carrier=carrier) as span:
43+
span.layer = Layer.Http
44+
span.component = Component.Falcon
45+
46+
from falcon import RequestOptions
47+
48+
req = request.Request(env, RequestOptions())
49+
span.op = str(req.url).split("?")[0]
50+
span.peer = "%s:%s" % (req.remote_addr, req.port)
51+
52+
span.tag(TagHttpMethod(req.method))
53+
span.tag(TagHttpURL(str(req.url)))
54+
if req.params:
55+
span.tag(TagHttpParams(params_tostring(req.params)[0:]))
56+
57+
resp = _original_falcon_api(this, env, start_response)
58+
59+
from falcon import ResponseOptions
60+
61+
resp_obj = response.Response(ResponseOptions())
62+
63+
resp_status = parse_status(resp_obj.status)
64+
if int(resp_status[0]) >= 400:
65+
span.error_occurred = True
66+
67+
span.tag(TagHttpStatusCode(int(resp_status[0])))
68+
69+
return resp
70+
71+
def _sw_handle_exception(this: API, req, resp, ex, params):
72+
if ex is not None:
73+
entry_span = get_context().active_span()
74+
if entry_span is not None and type(entry_span) is not NoopSpan:
75+
entry_span.raised()
76+
77+
return _original_falcon_handle_exception(this, req, resp, ex, params)
78+
79+
API.__call__ = _sw_falcon_api
80+
API._handle_exception = _sw_handle_exception
81+
82+
83+
def get_headers(env):
84+
headers = {}
85+
wsgi_content_headers = frozenset(["CONTENT_TYPE", "CONTENT_LENGTH"])
86+
87+
for name, value in env.items():
88+
if name.startswith("HTTP_"):
89+
headers[name[5:].replace("_", "-")] = value
90+
91+
elif name in wsgi_content_headers:
92+
headers[name.replace("_", "-")] = value
93+
94+
return headers
95+
96+
97+
def parse_status(status_str):
98+
return status_str.split(" ") if status_str else [404, "status is empty"]

tests/plugin/sw_falcon/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
version: '2.1'
19+
20+
services:
21+
collector:
22+
extends:
23+
service: collector
24+
file: ../docker/docker-compose.base.yml
25+
26+
provider:
27+
extends:
28+
service: agent
29+
file: ../docker/docker-compose.base.yml
30+
ports:
31+
- 9091:9091
32+
volumes:
33+
- .:/app
34+
command: [ 'bash', '-c', 'pip install hug && pip install -r /app/requirements.txt && hug -f /app/services/provider.py' ]
35+
depends_on:
36+
collector:
37+
condition: service_healthy
38+
healthcheck:
39+
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/9091" ]
40+
interval: 5s
41+
timeout: 60s
42+
retries: 120
43+
44+
consumer:
45+
extends:
46+
service: agent
47+
file: ../docker/docker-compose.base.yml
48+
ports:
49+
- 9090:9090
50+
volumes:
51+
- .:/app
52+
command: [ 'bash', '-c', 'pip install hug && pip install -r /app/requirements.txt && hug -f /app/services/consumer.py' ]
53+
depends_on:
54+
collector:
55+
condition: service_healthy
56+
provider:
57+
condition: service_healthy
58+
59+
networks:
60+
beyond:
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
segmentItems:
18+
- segmentSize: 1
19+
segments:
20+
- segmentId: not null
21+
spans:
22+
- componentId: 7012
23+
endTime: gt 0
24+
isError: false
25+
operationId: 0
26+
operationName: http://provider:9091/users
27+
parentSpanId: -1
28+
peer: not null
29+
skipAnalysis: false
30+
spanId: 0
31+
spanLayer: Http
32+
spanType: Entry
33+
startTime: gt 0
34+
tags:
35+
- key: http.method
36+
value: GET
37+
- key: http.url
38+
value: http://provider:9091/users
39+
- key: http.status.code
40+
value: '200'
41+
serviceName: provider
42+
- segmentSize: 1
43+
segments:
44+
- segmentId: not null
45+
spans:
46+
- componentId: 7002
47+
endTime: gt 0
48+
isError: false
49+
operationId: 0
50+
operationName: /users
51+
parentSpanId: 0
52+
peer: provider:9091
53+
skipAnalysis: false
54+
spanId: 1
55+
spanLayer: Http
56+
spanType: Exit
57+
startTime: gt 0
58+
tags:
59+
- key: http.method
60+
value: GET
61+
- key: http.url
62+
value: http://provider:9091/users
63+
- key: http.status.code
64+
value: '200'
65+
- componentId: 7012
66+
endTime: gt 0
67+
isError: false
68+
operationId: 0
69+
operationName: http://0.0.0.0:9090/users
70+
parentSpanId: -1
71+
peer: not null
72+
skipAnalysis: false
73+
spanId: 0
74+
spanLayer: Http
75+
spanType: Entry
76+
startTime: gt 0
77+
tags:
78+
- key: http.method
79+
value: GET
80+
- key: http.url
81+
value: http://0.0.0.0:9090/users
82+
- key: http.status.code
83+
value: '200'
84+
serviceName: consumer
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
from skywalking import agent, config
18+
19+
import requests
20+
import hug
21+
22+
config.service_name = 'consumer'
23+
config.logging_level = 'DEBUG'
24+
agent.start()
25+
26+
27+
@hug.get('/users')
28+
def get():
29+
res = requests.get("http://provider:9091/users")
30+
return res.json()
31+
32+
33+
hug.API(__name__).http.serve(port=9090)

0 commit comments

Comments
 (0)