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

added initial telemetry work #386

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
95bd528
added initial telemetry work
Jul 9, 2021
5430b78
added telemetry queue size
Jul 12, 2021
d7bf70d
added unit tests
Jul 23, 2021
9cc6c8b
fixing problem
Jul 23, 2021
00b76b7
corrected tests
Jul 23, 2021
5f85940
added dependency
Jul 23, 2021
5c831e8
added chardet==3.0.4
Jul 23, 2021
b489291
added idna==2.7
Jul 23, 2021
5b3979f
added mock==3.0.5
Jul 23, 2021
9f5f317
installing more dependencies
Jul 23, 2021
c4267fc
added dependencies
Jul 23, 2021
99988fc
fixed dependencies
Jul 23, 2021
8c126a7
added chardet==3.0.4 idna==2.7 mock==3.0.5
Jul 23, 2021
5e459f7
added dependency to pyramid
Jul 23, 2021
9549e0d
fixing versions
Jul 23, 2021
3336002
fixing dependencies
Jul 23, 2021
f5d9139
fixing dependencies
Jul 23, 2021
7cebc64
fixing mock version
Jul 23, 2021
ab02383
fixing mock version
Jul 23, 2021
b1628cd
fixing mock version
Jul 23, 2021
6a902d7
added mock to TWISTED
Jul 23, 2021
353dba8
added mock
Jul 24, 2021
5a82b30
added mock to starlette
Jul 26, 2021
52a8318
fix tests
Jul 26, 2021
766e10a
corrected tests
Jul 26, 2021
d2bf643
corrected tests
Jul 26, 2021
53132d1
corrected tests
Jul 26, 2021
ec5208f
added mock to TWISTED
Jul 26, 2021
ca9df67
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
9f0d61d
Update rollbar/test/test_telemetry.py
pawelsz-rb Aug 4, 2021
e59e34c
Update rollbar/__init__.py
pawelsz-rb Aug 4, 2021
5797a30
Update rollbar/test/test_telemetry.py
pawelsz-rb Aug 4, 2021
cfafa97
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
0f09f7e
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
16e6a9b
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
070b188
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
b317564
Update rollbar/lib/telemetry.py
pawelsz-rb Aug 4, 2021
88d8da3
Update rollbar/test/test_telemetry.py
pawelsz-rb Aug 4, 2021
1f1d8da
addressing PR comments
Aug 5, 2021
7f57fae
corrected tests
Aug 5, 2021
2e09a11
corrected tests
Aug 5, 2021
1d27b19
addressing PR comments
Aug 5, 2021
2d270df
correcting tests
Aug 5, 2021
5d7c1f9
added support for urllib
Aug 6, 2021
91f8338
added support for httpx
Aug 6, 2021
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
150 changes: 81 additions & 69 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,47 +14,53 @@ jobs:
matrix:
python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8]
framework:
- FLASK_VERSION=0.10.1 Werkzeug\>=0.7,\<1.0
- FLASK_VERSION=0.11.1 Werkzeug\>=0.7,\<1.0
- FLASK_VERSION=0.12.4 Werkzeug\>=0.7,\<1.0
- FLASK_VERSION=1.0.2
- TWISTED_VERSION=15.5.0 treq==15.1.0 zope.interface==4.1.3
- TWISTED_VERSION=16.1.0 treq==16.12.0 zope.interface==4.1.3
- TWISTED_VERSION=16.2.0 treq==16.12.0 zope.interface==4.1.3
- TWISTED_VERSION=16.3.0 treq==16.12.0 zope.interface==4.2.0
- TWISTED_VERSION=16.4.0 treq==16.12.0 zope.interface==4.5.0
- TWISTED_VERSION=16.5.0 treq==16.12.0 zope.interface==4.5.0
- TWISTED_VERSION=16.6.0 treq==16.12.0 zope.interface==4.5.0
- TWISTED_VERSION=17.1.0 treq==16.12.0 zope.interface==4.5.0
- DJANGO_VERSION=1.11.20
- DJANGO_VERSION=2.0.13
- DJANGO_VERSION=2.1.7
- DJANGO_VERSION=2.1.15
- PYRAMID_VERSION=1.9.2
- PYRAMID_VERSION=1.10.4
- STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5
- STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5
- STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5
- FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5
- FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5
- FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5
- FLASK_VERSION=0.10.1 Werkzeug\>=0.7,\<1.0 chardet==3.0.4 idna==2.7 mock==3.0.5
- FLASK_VERSION=0.11.1 Werkzeug\>=0.7,\<1.0 chardet==3.0.4 idna==2.7 mock==3.0.5
- FLASK_VERSION=0.12.4 Werkzeug\>=0.7,\<1.0 chardet==3.0.4 idna==2.7 mock==3.0.5
- FLASK_VERSION=1.0.2 chardet==3.0.4 idna==2.7 mock==3.0.5
- TWISTED_VERSION=15.5.0 treq==15.1.0 zope.interface==4.1.3 mock==2.0.0
- TWISTED_VERSION=16.1.0 treq==16.12.0 zope.interface==4.1.3 mock==2.0.0
- TWISTED_VERSION=16.2.0 treq==16.12.0 zope.interface==4.1.3 mock==2.0.0
- TWISTED_VERSION=16.3.0 treq==16.12.0 zope.interface==4.2.0 mock==2.0.0
- TWISTED_VERSION=16.4.0 treq==16.12.0 zope.interface==4.5.0 mock==2.0.0
- TWISTED_VERSION=16.5.0 treq==16.12.0 zope.interface==4.5.0 mock==2.0.0
- TWISTED_VERSION=16.6.0 treq==16.12.0 zope.interface==4.5.0 mock==2.0.0
- TWISTED_VERSION=17.1.0 treq==16.12.0 zope.interface==4.5.0 mock==2.0.0
- DJANGO_VERSION=1.11.20 chardet==3.0.4 idna==2.7 mock==3.0.5
- DJANGO_VERSION=2.0.13 mock==2.0.0
- DJANGO_VERSION=2.1.7 mock==2.0.0
- DJANGO_VERSION=2.1.15 mock==2.0.0
- PYRAMID_VERSION=1.9.2 chardet==3.0.4 idna==2.7 mock==3.0.5
- PYRAMID_VERSION=1.10.4 chardet==3.0.4 idna==2.7 mock==3.0.5
- STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
Comment on lines +17 to +40
Copy link
Contributor

Choose a reason for hiding this comment

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

All Python versions from the matrix except 2.7 have the mock module included in the standard library. We don't need to install it manually.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah, but it does not hurt to install it, and just for that I would need to pull 2.7 out of this matrix and have a different definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO requiring the installation of unnecessary packages hurts quality.

Our other test suites solve it in this way:

try:
from unittest import mock
except ImportError:
import mock

You should follow the pattern. In my opinion, this PR does not require any changes in the CI builds. The builds failed for a good reason.

The installation of the mock package for Python <3.3 is already implemented:

pyrollbar/setup.py

Lines 20 to 24 in f67253a

tests_require = [
'webob',
'blinker',
'unittest2',
'mock<=3.0.5; python_version < "3.3"',

exclude:
- python-version: 2.7
framework: DJANGO_VERSION=2.0.13
framework: DJANGO_VERSION=2.0.13 mock==2.0.0
- python-version: 2.7
framework: DJANGO_VERSION=2.1.7
framework: DJANGO_VERSION=2.1.7 mock==2.0.0
- python-version: 2.7
framework: DJANGO_VERSION=2.1.15
framework: DJANGO_VERSION=2.1.15 mock==2.0.0
- python-version: 3.4
framework: DJANGO_VERSION=2.1.7
framework: DJANGO_VERSION=2.0.13 mock==2.0.0
- python-version: 3.4
framework: DJANGO_VERSION=2.1.15
framework: DJANGO_VERSION=2.1.7 mock==2.0.0
- python-version: 3.4
framework: DJANGO_VERSION=2.1.15 mock==2.0.0
- python-version: 3.5
framework: DJANGO_VERSION=2.1.15 mock==2.0.0
- python-version: 3.5
framework: DJANGO_VERSION=2.0.13 mock==2.0.0
- python-version: 3.5
framework: DJANGO_VERSION=2.1.15
framework: DJANGO_VERSION=2.1.7 mock==2.0.0
- python-version: 3.6
framework: DJANGO_VERSION=2.1.15
framework: DJANGO_VERSION=2.1.15 mock==2.0.0
- python-version: 3.7
framework: DJANGO_VERSION=2.1.15
framework: DJANGO_VERSION=2.1.15 mock==2.0.0
Comment on lines -43 to +63
Copy link
Contributor

Choose a reason for hiding this comment

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

As a result of the comment above these changes are also not required.


# twisted/treq setup.py allows:
# Twisted < 18.7.0 on python < 3.7
Expand Down Expand Up @@ -95,83 +101,89 @@ jobs:
framework: TWISTED_VERSION=17.1.0 treq==20.4.1 zope.interface==4.3.0

- python-version: 2.7
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 2.7
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 2.7
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.12 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.12.13 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5
framework: STARLETTE_VERSION=0.14.2 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5

- python-version: 2.7
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 2.7
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 2.7
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.4
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.40.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.50.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
- python-version: 3.5
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5
framework: FASTAPI_VERSION=0.63.0 httpx==0.18.1 python-multipart==0.0.5 mock==3.0.5
Comment on lines -98 to +139
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above.

include:
- python-version: 2.7
framework: FLASK_VERSION=0.9
- python-version: 3.3
framework: FLASK_VERSION=0.10.1
framework: FLASK_VERSION=0.10.1 mock==2.0.0
- python-version: 3.3
framework: FLASK_VERSION=0.11.1
framework: FLASK_VERSION=0.11.1 mock==2.0.0
- python-version: 3.3
framework: FLASK_VERSION=0.12.4
framework: FLASK_VERSION=0.12.4 mock==2.0.0
- python-version: 3.3
framework: FLASK_VERSION=1.0.2
framework: FLASK_VERSION=1.0.2 mock==2.0.0
- python-version: 3.3
framework: DJANGO_VERSION=1.6.11
framework: DJANGO_VERSION=1.6.11 mock==2.0.0
- python-version: 3.3
framework: DJANGO_VERSION=1.8.19
framework: DJANGO_VERSION=1.8.19 mock==2.0.0
- python-version: 3.4
framework: DJANGO_VERSION=1.7.11
framework: DJANGO_VERSION=1.7.11 chardet==3.0.4 idna==2.7 mock==3.0.5
- python-version: 3.4
framework: DJANGO_VERSION=1.8.19
framework: DJANGO_VERSION=1.8.19 chardet==3.0.4 idna==2.7 mock==3.0.5
- python-version: 3.4
framework: DJANGO_VERSION=1.9.13
framework: DJANGO_VERSION=1.9.13 chardet==3.0.4 idna==2.7 mock==3.0.5
- python-version: 3.4
framework: DJANGO_VERSION=1.10.8
framework: DJANGO_VERSION=1.10.8 chardet==3.0.4 idna==2.7 mock==3.0.5
- python-version: 3.4
framework: DJANGO_VERSION=2.0.13 chardet==3.0.4 idna==2.7 mock==3.0.5
- python-version: 3.5
framework: DJANGO_VERSION=1.8.19 mock==3.0.5
- python-version: 3.5
framework: DJANGO_VERSION=1.9.13 mock==3.0.5
- python-version: 3.5
framework: DJANGO_VERSION=1.8.19
framework: DJANGO_VERSION=1.10.8 mock==3.0.5
- python-version: 3.5
framework: DJANGO_VERSION=1.9.13
framework: DJANGO_VERSION=2.0.13 mock==3.0.5
- python-version: 3.5
framework: DJANGO_VERSION=1.10.8
framework: DJANGO_VERSION=2.1.7 mock==3.0.5
- python-version: 3.7
framework: TWISTED_VERSION=18.9.0 treq==20.4.1 zope.interface==4.5.0
framework: TWISTED_VERSION=18.9.0 treq==20.4.1 zope.interface==4.5.0 mock==3.0.5
- python-version: 3.7
framework: TWISTED_VERSION=19.10.0 treq==20.4.1 zope.interface==4.6.0
framework: TWISTED_VERSION=19.10.0 treq==20.4.1 zope.interface==4.6.0 mock==3.0.5
- python-version: 3.7
framework: TWISTED_VERSION=20.3.0 treq==20.4.1 zope.interface==4.7.0
framework: TWISTED_VERSION=20.3.0 treq==20.4.1 zope.interface==4.7.0 mock==3.0.5
- python-version: 3.8
framework: TWISTED_VERSION=18.9.0 treq==20.4.1 zope.interface==4.5.0
framework: TWISTED_VERSION=18.9.0 treq==20.4.1 zope.interface==4.5.0 mock==3.0.5
- python-version: 3.8
framework: TWISTED_VERSION=19.10.0 treq==20.4.1 zope.interface==4.6.0
framework: TWISTED_VERSION=19.10.0 treq==20.4.1 zope.interface==4.6.0 mock==3.0.5
- python-version: 3.8
framework: TWISTED_VERSION=20.3.0 treq==20.4.1 zope.interface==4.7.0
framework: TWISTED_VERSION=20.3.0 treq==20.4.1 zope.interface==4.7.0 mock==3.0.5
Comment on lines -138 to +186
Copy link
Contributor

Choose a reason for hiding this comment

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

Python 3.3+ includes the mock module. I don't think it is necessary.

steps:
- uses: actions/checkout@v2
with:
Expand Down
26 changes: 25 additions & 1 deletion rollbar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import requests
import six

from rollbar.lib import events, filters, dict_merge, parse_qs, text, transport, urljoin, iteritems, defaultJSONEncode
from rollbar.lib import events, filters, dict_merge, parse_qs, text, transport, telemetry, urljoin, iteritems, defaultJSONEncode
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved


__version__ = '0.16.1'
Expand Down Expand Up @@ -381,6 +381,22 @@ def init(access_token, environment='production', scrub_fields=None, url_fields=N
if SETTINGS.get('allow_logging_basic_config'):
logging.basicConfig()

queue_size = SETTINGS.get('set_custom_queue_size')
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
if queue_size:
Copy link
Contributor

Choose a reason for hiding this comment

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

This is buggy. You use the value as a number in other parts of the code but the value pulled from SETTINGS['set_custom_queue_size'] may be passed as a string like "0" that would raise exception later or the condition's result can be incorrect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's fine. We have more settings that accept a number. Not sure why you would want to pass a string when it expects a number.

Copy link
Contributor

Choose a reason for hiding this comment

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

Python does not support strong typing, and you can't assume a source of the configuration data for the SDKs. It's common to read configuration settings from a serialized text file or even from a keyboard.
So there can be many cases where the value can be of type string and lead to the bug.

We have more settings that accept a number.

I am not against accepting numbers but the context of use. If you noticed any other settings that can lead to any bug, you can fix them by the way.

telemetry.TELEMETRY_QUEUE = telemetry.Queue(queue_size)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a global queue? This messes up events between requests. IMHO would be much more valuable to keep a unique queue per request.


if SETTINGS.get('log_telemetry'):
formatter = SETTINGS.get('log_telemetry_formatter')
telemetry.set_log_telemetry(formatter)
if SETTINGS.get('network_telemetry'):
enable_req_headers = SETTINGS.get('enable_req_headers')
enable_response_headers = SETTINGS.get('enable_response_headers')
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
requests.get = telemetry.request(requests.get, enable_req_headers, enable_response_headers )
requests.post = telemetry.request(requests.post, enable_req_headers, enable_response_headers)
Copy link
Contributor

Choose a reason for hiding this comment

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

POST requests to the Rollbar API are NOT reported (probably ok to ignore it) because of using requests.sessions.Session object instead of invoking the requests.post() function. However, requests send by the Session objects shall be reported.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

POST requests to Rollbar API should be ignored.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, but other requests from Session objects should be collected and reported.

requests.put = telemetry.request(requests.put, enable_req_headers, enable_response_headers)
requests.patch = telemetry.request(requests.patch, enable_req_headers, enable_response_headers)
requests.delete = telemetry.request(requests.delete, enable_req_headers, enable_response_headers)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only partial support for the requests package.
The wrapped API functions are at the top of the stack. There are other ways to send requests such as requests.request() or requests.sessions.Session().request(). The Session.request is a low-level function and it should be enough to wrap only it.

pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved

if SETTINGS.get('handler') == 'agent':
agent_log = _create_agent_log()

Expand Down Expand Up @@ -777,6 +793,7 @@ def _report_exc_info(exc_info, request, extra_data, payload_data, level=None):
_add_request_data(data, request)
_add_person_data(data, request)
_add_lambda_context_data(data)
_add_telemetry(data)
data['server'] = _build_server_data()

if payload_data:
Expand Down Expand Up @@ -857,6 +874,7 @@ def _report_message(message, level, request, extra_data, payload_data):
_add_request_data(data, request)
_add_person_data(data, request)
_add_lambda_context_data(data)
_add_telemetry(data)
data['server'] = _build_server_data()

if payload_data:
Expand Down Expand Up @@ -1119,6 +1137,12 @@ def _add_request_data(data, request):
data['request'] = request_data


def _add_telemetry(data):
telemetry_data = telemetry.TELEMETRY_QUEUE.get_items()
if telemetry_data:
data['body']['telemetry'] = telemetry_data


def _check_add_locals(frame, frame_num, total_frames):
"""
Returns True if we should record local variables for the given frame.
Expand Down
94 changes: 94 additions & 0 deletions rollbar/lib/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import logging
import threading
import time

import rollbar


class Queue:
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not implement your own data structures unless there is a strong reason to do so. Use the structures available in the standard library such as queue.Queue as they are optimized and well tested. Based on your current implementation, you should probably be using collections.deque objects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I looked at the functionality of these, and I could not find what I was looking for. I need to return all the items with the predefined queue size. and if the new item makes the queue size bigger than the predefined queue size then the first item must be removed. Like I said, I did not see anything here that would work efficiently.

Copy link
Contributor

Choose a reason for hiding this comment

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

From the Python documentation:

class collections.deque([iterable[, maxlen]])
(...)
If maxlen is not specified or is None, deques may grow to an arbitrary length. Otherwise, the deque is bounded to the specified maximum length. Once a bounded length deque is full, when new items are added, a corresponding number of items are discarded from the opposite end. Bounded length deques provide functionality similar to the tail filter in Unix. They are also useful for tracking transactions and other pools of data where only the most recent activity is of interest.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, but it does not have a function to retrieve all the items. I guess I can do the list()

Copy link
Contributor

Choose a reason for hiding this comment

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

There is no need to provide such a function to any of the Python containers. In fact, none of the built-in containers provide such a function. Python supports the iterator protocol (see PEP 234).

I guess I can do the list()

If you need a list, you can do so (thanks to the iterator protocol).

def __init__(self, size):
self.size = size
self.items = []
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
self.lock = threading.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

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

Locking thread breaks asynchronous code. You can't use it here.


def put(self, item):
with self.lock:
if len(self.items) >= self.size:
self.items = self.items[1:]
self.items.append(item)

def get_items(self):
with self.lock:
return self.items

def clear_items(self):
with self.lock:
self.items = []


TELEMETRY_QUEUE_SIZE = 50
TELEMETRY_QUEUE = Queue(TELEMETRY_QUEUE_SIZE)
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved


class TelemetryLogHandler(logging.Handler):
def __init__(self, formatter=None):
super(TelemetryLogHandler, self).__init__()
self.formatter = formatter

def emit(self, record):
self.setFormatter(self.formatter)
msg = {'message': self.format(record)}
data = {'body': msg, 'source': 'client', 'timestamp_ms': get_current_timestamp(), 'type': 'log',
'level': record.levelname}
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved

TELEMETRY_QUEUE.put(data)


def set_log_telemetry(log_formatter=None):
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
logging.getLogger().addHandler(TelemetryLogHandler(log_formatter))


def get_current_timestamp():
return int(time.time())
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved


def request(request_function, enable_req_headers, enable_response_headers):
def telemetry(*args, **kwargs):

pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
def clean_headers(headers):
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
if not headers:
return []
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
for h in headers:
if h in rollbar.SETTINGS['scrub_fields']:
headers[h] = '[FILTERED]'
return headers
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
data = {'level': 'info'}
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
data_body = {'status_code': None}
try:
response = request_function(*args, **kwargs)
except: # noqa: E722
response = None
Comment on lines +53 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bug. You shouldn't modify the behavior of the wrapped object. The response object is returned later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't modify anything response object is not return at all

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it should re-raise after logging the telemetry event. It looks like the caught exception gets lost here.

Copy link
Contributor

Choose a reason for hiding this comment

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

@pawelsz-rb I don't modify anything response object is not return at all

You modified (95bd528) the response object by transforming (scrubbing) mutual objects:

            if enable_response_headers:
                data_body['response'] = {'headers': clean_headers(response.headers)}
        if enable_req_headers:
            data_body['request_headers'] = clean_headers(kwargs.get('headers'))

Then the response object is returned at the end of the function (line 90 at the moment).

@waltjones I think it should re-raise after logging the telemetry event. It looks like the caught exception gets lost here.

I'm sure that this (and the bare except in general) will be resolved implicitly after adding support for non-HTTP network events.

Copy link
Collaborator Author

@pawelsz-rb pawelsz-rb Aug 10, 2021

Choose a reason for hiding this comment

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

I thought you are referring to

  try:
            response = request_function(*args, **kwargs)
        except:  # noqa: E722
            response = None

cleaning the headers is already fixed

Copy link
Contributor

@bxsx bxsx Aug 10, 2021

Choose a reason for hiding this comment

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

This line: response = None


if response is not None:
data_body['status_code'] = response.status_code
if response.status_code >= 500:
data['level'] = 'critical'
elif response.status_code >= 400:
data['level'] = 'error'
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
if enable_response_headers:
data_body['response'] = {'headers': clean_headers(response.headers)}
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
if enable_req_headers:
data_body['request_headers'] = clean_headers(kwargs.get('headers'))
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
data_body['url'] = args[0]
data_body['method'] = request_function.__name__
pawelsz-rb marked this conversation as resolved.
Show resolved Hide resolved
data_body['subtype'] = 'http'
data['body'] = data_body

data['source'] = 'client'
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't be server?

data['timestamp_ms'] = get_current_timestamp()
data['type'] = 'network'
TELEMETRY_QUEUE.put(data)

return response

return telemetry
Loading