Skip to content

Commit 1db5c4d

Browse files
committed
LNX-329 - Initial commit for RUMv2 support
1 parent 63594f2 commit 1db5c4d

File tree

12 files changed

+578
-1
lines changed

12 files changed

+578
-1
lines changed

docker/stackify-python-api-test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ ARG from_version
33
FROM python:${from_version}
44

55
ARG version
6+
ARG test
7+
ARG test_repo
68

79
RUN \
810
apt-get update && \
@@ -13,5 +15,6 @@ RUN mkdir /build
1315
COPY . /build/
1416

1517
RUN cat /build/requirements.txt | xargs -n 1 pip install; exit 0
18+
RUN if [ "${test}" = 1 ]; then pip install -i "${test_repo}" stackify-python-apm; fi; exit 0
1619

1720
CMD /bin/bash -c "cd /build && source test-docker-execute.sh"

stackify/compat.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# -*- coding: utf-8 -*-
2+
import sys
3+
import types
4+
5+
PY2 = sys.version_info[0] == 2
6+
PY3 = sys.version_info[0] == 3
7+
8+
9+
if PY2:
10+
import StringIO
11+
import Queue as queue # noqa F401
12+
import urlparse # noqa F401
13+
from urllib2 import HTTPError # noqa F401
14+
from urllib import unquote as unquote_core # noqa F401
15+
16+
StringIO = BytesIO = StringIO.StringIO
17+
18+
string_types = (basestring,) # noqa F821
19+
integer_types = (int, long) # noqa F821
20+
class_types = (type, types.ClassType)
21+
text_type = unicode # noqa F821
22+
binary_type = str
23+
list_type = list
24+
dict_type = dict
25+
26+
def b(s):
27+
return s
28+
29+
def iterkeys(d, **kwargs):
30+
return d.iterkeys(**kwargs)
31+
32+
def iteritems(d, **kwargs):
33+
return d.iteritems(**kwargs)
34+
35+
def iterlists(d, **kwargs):
36+
return d.iterlists(**kwargs)
37+
38+
def unquote(*args, **kwargs): # noqa F811
39+
return unquote_core(*args, **kwargs)
40+
else:
41+
import io
42+
import queue # noqa F401
43+
from urllib import parse as urlparse # noqa F401
44+
from urllib.error import HTTPError # noqa F401
45+
46+
StringIO = io.StringIO
47+
BytesIO = io.BytesIO
48+
49+
string_types = (str,)
50+
integer_types = (int,)
51+
class_types = (type,)
52+
text_type = str
53+
binary_type = bytes
54+
list_type = list
55+
dict_type = dict
56+
57+
def b(s):
58+
return s.encode("latin-1")
59+
60+
def iterkeys(d, **kwargs):
61+
return iter(d.keys(**kwargs))
62+
63+
def iteritems(d, **kwargs):
64+
return iter(d.items(**kwargs))
65+
66+
def iterlists(d, **kwargs):
67+
return iter(d.lists(**kwargs))
68+
69+
def unquote(*args, **kwargs):
70+
return urlparse.unquote(*args, **kwargs)
71+
72+
73+
def multidict_to_dict(d):
74+
return dict((k, v[0] if len(v) == 1 else v) for k, v in iterlists(d))

stackify/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from stackify.constants import DEFAULT_RUM_KEY, DEFAULT_RUM_SCRIPT_URL
2+
3+
rum_key = DEFAULT_RUM_KEY
4+
rum_script_url = DEFAULT_RUM_SCRIPT_URL
5+
application = None
6+
environment = None

stackify/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@
3838
TRANSPORT_TYPE_DEFAULT = 'default'
3939
TRANSPORT_TYPE_AGENT_SOCKET = 'agent_socket'
4040
TRANSPORT_TYPE_AGENT_HTTP = 'agent_http'
41+
42+
DEFAULT_RUM_SCRIPT_URL = "https://stckjs.stackify.com/stckjs.js"
43+
DEFAULT_RUM_KEY = ""

stackify/rum.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import json
2+
import base64
3+
from stackify import config
4+
5+
apm_installed = False
6+
7+
try:
8+
apm_installed = True
9+
from stackifyapm import insert_rum_script as insert_rum_script_apm
10+
except ImportError:
11+
pass
12+
13+
14+
def insert_rum_script():
15+
if apm_installed is True:
16+
return insert_rum_script_apm()
17+
18+
rum_key = config.rum_key
19+
rum_script_url = config.rum_script_url
20+
21+
if not rum_script_url or not rum_key:
22+
return None
23+
24+
transaction_id = get_transaction_id()
25+
if not transaction_id:
26+
return None
27+
28+
reporting_url = get_reporting_url()
29+
if not reporting_url:
30+
return None
31+
32+
application_name = config.application
33+
if not application_name:
34+
return None
35+
36+
environment = config.environment
37+
if not environment:
38+
return None
39+
40+
settings = {
41+
"ID": transaction_id
42+
}
43+
44+
if application_name:
45+
application_name_b64 = base64.b64encode(application_name.encode("utf-8")).decode("utf-8")
46+
if (application_name_b64):
47+
settings["Name"] = application_name_b64
48+
49+
if environment:
50+
environment_b64 = base64.b64encode(environment.encode("utf-8")).decode("utf-8")
51+
if (environment_b64):
52+
settings["Env"] = environment_b64
53+
54+
if reporting_url:
55+
reporting_url_b64 = base64.b64encode(reporting_url.encode("utf-8")).decode("utf-8")
56+
if (reporting_url_b64):
57+
settings["Trans"] = reporting_url_b64
58+
59+
if not settings:
60+
return None
61+
62+
return '<script type="text/javascript">(window.StackifySettings || (window.StackifySettings = {}))</script><script src="{}" data-key="{}" async></script>'.format(
63+
json.dumps(settings),
64+
rum_script_url,
65+
rum_key
66+
)
67+
68+
69+
def get_transaction_id():
70+
return ''
71+
72+
73+
def get_reporting_url():
74+
return ''

stackify/transport/application.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import socket
22
import os
3+
import logging
34

45
from stackify.utils import arg_or_env
56
from stackify.constants import API_URL
@@ -9,6 +10,12 @@
910
from stackify.constants import TRANSPORT_TYPE_AGENT_SOCKET
1011
from stackify.constants import TRANSPORT_TYPE_DEFAULT
1112
from stackify.transport.default.formats import JSONObject
13+
from stackify.constants import DEFAULT_RUM_SCRIPT_URL
14+
from stackify.constants import DEFAULT_RUM_KEY
15+
from stackify.utils import RegexValidator, ConfigError
16+
from stackify import config
17+
18+
internal_logger = logging.getLogger(__name__)
1219

1320

1421
class EnvironmentDetail(JSONObject):
@@ -38,6 +45,8 @@ def __init__(
3845
socket_url=SOCKET_URL,
3946
transport=None,
4047
http_endpoint=DEFAULT_HTTP_ENDPOINT,
48+
rum_script_url=DEFAULT_RUM_SCRIPT_URL,
49+
rum_key=DEFAULT_RUM_KEY
4150
):
4251
self.api_key = api_key
4352
self.api_url = api_url
@@ -47,6 +56,39 @@ def __init__(
4756
self.http_endpoint = http_endpoint
4857
self.transport = transport
4958

59+
self.rum_script_url = DEFAULT_RUM_SCRIPT_URL
60+
self.rum_key = DEFAULT_RUM_KEY
61+
62+
# Rum config validation
63+
if rum_script_url != DEFAULT_RUM_SCRIPT_URL:
64+
self.validate(
65+
RegexValidator("^((((https?|ftps?|gopher|telnet|nntp)://)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-\(\)_.!~*';/?:@&=+$,A-Za-z0-9])+)([).!';/?:,][\[:blank:|:blank:\]])?$"),
66+
rum_script_url,
67+
'rum_script_url'
68+
)
69+
config.rum_script_url = self.rum_script_url
70+
71+
if rum_key != DEFAULT_RUM_KEY:
72+
self.validate(
73+
RegexValidator("^[A-Za-z0-9_-]+$"),
74+
rum_key,
75+
'rum_key'
76+
)
77+
config.rum_key = self.rum_key
78+
79+
config.environment = self.environment
80+
config.application = self.application
81+
82+
def validate(self, validator, value, key):
83+
if not validator:
84+
return
85+
86+
try:
87+
value = validator(value, key)
88+
setattr(self, key, str(value))
89+
except ConfigError as e:
90+
internal_logger.exception(str(e))
91+
5092

5193
def get_configuration(**kwargs):
5294
"""
@@ -69,4 +111,6 @@ def get_configuration(**kwargs):
69111
socket_url=arg_or_env('socket_url', kwargs, SOCKET_URL),
70112
http_endpoint=arg_or_env('http_endpoint', kwargs, DEFAULT_HTTP_ENDPOINT, env_key='STACKIFY_TRANSPORT_HTTP_ENDPOINT'),
71113
transport=transport,
114+
rum_script_url=arg_or_env('rum_script_url', kwargs, DEFAULT_RUM_SCRIPT_URL, env_key='RETRACE_RUM_SCRIPT_URL'),
115+
rum_key=arg_or_env('rum_key', kwargs, DEFAULT_RUM_KEY, env_key='RETRACE_RUM_KEY')
72116
)

stackify/utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22
import json
33
import logging
4+
import re
5+
from stackify import compat
46

57
internal_logger = logging.getLogger(__name__)
68

@@ -36,3 +38,22 @@ def get_default_object(obj):
3638

3739
def object_is_iterable(obj):
3840
return hasattr(obj, '__iter__') or isinstance(obj, str)
41+
42+
43+
class RegexValidator(object):
44+
def __init__(self, regex, verbose_pattern=None):
45+
self.regex = regex
46+
self.verbose_pattern = verbose_pattern or regex
47+
48+
def __call__(self, value, field_name):
49+
value = compat.text_type(value)
50+
match = re.match(self.regex, value)
51+
if match:
52+
return value
53+
raise ConfigError("{} does not match pattern {}".format(value, self.verbose_pattern), field_name)
54+
55+
56+
class ConfigError(ValueError):
57+
def __init__(self, msg, field_name):
58+
self.field_name = field_name
59+
super(ValueError, self).__init__(msg)

test-docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ do
1818
fi
1919

2020
echo "Building stackify-python-api-test-${i}..."
21-
docker build --no-cache --build-arg from_version=${i} --build-arg version=${i} --file docker/stackify-python-api-test . -t stackify-python-api-test-${i}:latest
21+
docker build --no-cache --build-arg from_version=${i} --build-arg version=${i} --build-arg test=${TEST} --build-arg test_repo=${TEST_REPO} --file docker/stackify-python-api-test . -t stackify-python-api-test-${i}:latest
2222

2323
echo "Running stackify-python-api-test-${i}..."
2424
docker run --network="host" --name "stackify-python-api-test-${i}" stackify-python-api-test-${i}:latest

tests/bases.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def setUp(self):
2323
'STACKIFY_API_URL',
2424
'STACKIFY_TRANSPORT',
2525
'STACKIFY_TRANSPORT_HTTP_ENDPOINT',
26+
'RETRACE_RUM_SCRIPT_URL',
27+
'RETRACE_RUM_KEY'
2628
]
2729
self.saved = {}
2830
for key in to_save:

tests/test_compat.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from unittest import TestCase
2+
3+
from stackifyapm.utils.compat import b
4+
from stackifyapm.utils.compat import iterkeys
5+
from stackifyapm.utils.compat import iteritems
6+
7+
8+
class CompatTest(TestCase):
9+
def setUp(self):
10+
self.dict_data = {
11+
"key1": "value1",
12+
"key2": "value2"
13+
}
14+
self.list_data = ["foo", "bar"]
15+
16+
def test_convert_string_to_byte(self):
17+
byte = '1'
18+
19+
value = b(byte)
20+
21+
assert isinstance(value, bytes)
22+
23+
def test_iterkeys_should_return_iterator(self):
24+
iter_keys = iterkeys(self.dict_data)
25+
26+
self.assert_instance_is_an_iterator(iter_keys)
27+
28+
def test_iteritems_should_return_iterator(self):
29+
iter_items = iteritems(self.dict_data)
30+
31+
self.assert_instance_is_an_iterator(iter_items)
32+
33+
def assert_instance_is_an_iterator(self, item):
34+
try:
35+
iter(item)
36+
assert True
37+
except Exception:
38+
assert False

0 commit comments

Comments
 (0)