Skip to content

Commit b3a2b33

Browse files
LS-5226: Add support for Proto over HTTP
- Fixed an issue in examples where argparser will not parse bool as expected (use_tls) - Added options to Tracer use_thrift or use_http - Created Converter abstract class, either a Thrift or an HTTP converter will be used - Implemented HTTPConnection
1 parent ffc71ff commit b3a2b33

13 files changed

+490
-209
lines changed

examples/http/context_in_headers.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import opentracing.ext.tags
3232
import lightstep
3333

34+
3435
class RemoteHandler(BaseHTTPRequestHandler):
3536
"""This handler receives the request from the client.
3637
"""
@@ -121,18 +122,23 @@ def lightstep_tracer_from_args():
121122
default='collector.lightstep.com')
122123
parser.add_argument('--port', help='The LightStep reporting service port.',
123124
type=int, default=443)
124-
parser.add_argument('--use_tls', help='Whether to use TLS for reporting',
125-
type=bool, default=True)
125+
parser.add_argument('--use_tls', help='Use TLS for reporting',
126+
dest="use_tls", action='store_true')
126127
parser.add_argument('--component_name', help='The LightStep component name',
127128
default='TrivialExample')
128129
args = parser.parse_args()
129130

131+
if args.use_tls:
132+
collector_encryption = 'tls'
133+
else:
134+
collector_encryption = 'none'
135+
130136
return lightstep.Tracer(
131137
component_name=args.component_name,
132138
access_token=args.token,
133139
collector_host=args.host,
134140
collector_port=args.port,
135-
collector_encryption=('tls' if args.use_tls else 'none'),
141+
collector_encryption=collector_encryption,
136142
)
137143

138144

examples/nontrivial/main.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
library.
44
"""
55
import argparse
6-
import contextlib
76
import sys
87
import time
98
import threading
@@ -16,13 +15,15 @@
1615
import opentracing
1716
import lightstep
1817

18+
1919
def sleep_dot():
2020
"""Short sleep and writes a dot to the STDOUT.
2121
"""
2222
time.sleep(0.05)
2323
sys.stdout.write('.')
2424
sys.stdout.flush()
2525

26+
2627
def add_spans():
2728
"""Calls the opentracing API, doesn't use any LightStep-specific code.
2829
"""
@@ -69,18 +70,23 @@ def lightstep_tracer_from_args():
6970
default='collector.lightstep.com')
7071
parser.add_argument('--port', help='The LightStep reporting service port.',
7172
type=int, default=443)
72-
parser.add_argument('--use_tls', help='Whether to use TLS for reporting',
73-
type=bool, default=True)
73+
parser.add_argument('--use_tls', help='Use TLS for reporting',
74+
dest="use_tls", action='store_true')
7475
parser.add_argument('--component_name', help='The LightStep component name',
7576
default='NonTrivialExample')
7677
args = parser.parse_args()
7778

79+
if args.use_tls:
80+
collector_encryption = 'tls'
81+
else:
82+
collector_encryption = 'none'
83+
7884
return lightstep.Tracer(
79-
component_name=args.component_name,
80-
access_token=args.token,
81-
collector_host=args.host,
82-
collector_port=args.port,
83-
collector_encryption=('tls' if args.use_tls else 'none'))
85+
component_name=args.component_name,
86+
access_token=args.token,
87+
collector_host=args.host,
88+
collector_port=args.port,
89+
collector_encryption=collector_encryption)
8490

8591

8692
if __name__ == '__main__':

examples/trivial/main.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
"""Simple example showing several generations of spans in a trace.
22
"""
33
import argparse
4-
import contextlib
54
import sys
65
import time
76
import traceback
87

98
import opentracing
109
import lightstep.tracer
1110

11+
1212
def sleep_dot():
1313
"""Short sleep and writes a dot to the STDOUT.
1414
"""
1515
time.sleep(0.05)
1616
sys.stdout.write('.')
1717
sys.stdout.flush()
1818

19+
1920
def add_spans():
2021
"""Calls the opentracing API, doesn't use any LightStep-specific code.
2122
"""
@@ -62,19 +63,28 @@ def lightstep_tracer_from_args():
6263
default='collector.lightstep.com')
6364
parser.add_argument('--port', help='The LightStep reporting service port.',
6465
type=int, default=443)
65-
parser.add_argument('--use_tls', help='Whether to use TLS for reporting',
66-
type=bool, default=True)
66+
parser.add_argument('--use_tls', help='Use TLS for reporting',
67+
dest="use_tls", action='store_true')
6768
parser.add_argument('--component_name', help='The LightStep component name',
6869
default='TrivialExample')
70+
parser.add_argument('--use_http', help='Use proto over http',
71+
dest="use_http", action='store_true')
6972
args = parser.parse_args()
7073

74+
if args.use_tls:
75+
collector_encryption = 'tls'
76+
else:
77+
collector_encryption = 'none'
78+
7179
return lightstep.Tracer(
7280
component_name=args.component_name,
7381
access_token=args.token,
7482
collector_host=args.host,
7583
collector_port=args.port,
7684
verbosity=1,
77-
collector_encryption=('tls' if args.use_tls else 'none'))
85+
collector_encryption=collector_encryption,
86+
use_thrift=not args.use_http,
87+
use_http=args.use_http)
7888

7989

8090
if __name__ == '__main__':

lightstep/converter.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from abc import ABCMeta, abstractmethod
2+
3+
4+
class Converter(object):
5+
"""Converter is a simple abstract interface for converting span data to wire compatible formats for the Satellites.
6+
"""
7+
8+
__metaclass__ = ABCMeta
9+
10+
@abstractmethod
11+
def create_auth(self, access_token):
12+
pass
13+
14+
@abstractmethod
15+
def create_runtime(self, component_name, tags, guid):
16+
pass
17+
18+
@abstractmethod
19+
def create_span_record(self, span, guid):
20+
pass
21+
22+
@abstractmethod
23+
def append_attribute(self, span_record, key, value):
24+
pass
25+
26+
@abstractmethod
27+
def append_join_id(self, span_record, key, value):
28+
pass
29+
30+
@abstractmethod
31+
def append_log(self, span_record, log):
32+
pass
33+
34+
@abstractmethod
35+
def create_report(self, runtime, span_records):
36+
pass
37+
38+
@abstractmethod
39+
def combine_span_records(self, report_request, span_records):
40+
pass
41+
42+
@abstractmethod
43+
def num_span_records(self, report_request):
44+
pass
45+
46+
@abstractmethod
47+
def get_span_records(self, report_request):
48+
pass
49+
50+
@abstractmethod
51+
def get_span_name(self, span_record):
52+
pass

lightstep/http_connection.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
""" Connection class establishes HTTP connection with server.
2+
Utilized to send Proto Report Requests.
3+
"""
4+
import threading
5+
import requests
6+
7+
from lightstep.collector_pb2 import ReportResponse
8+
9+
CONSECUTIVE_ERRORS_BEFORE_RECONNECT = 200
10+
11+
12+
class _HTTPConnection(object):
13+
"""Instances of _Connection are used to establish a connection to the
14+
server via HTTP protocol.
15+
"""
16+
def __init__(self, collector_url):
17+
self._collector_url = collector_url
18+
self._lock = threading.Lock()
19+
self.ready = True
20+
self._report_eof_count = 0
21+
self._report_consecutive_errors = 0
22+
23+
def open(self):
24+
"""Establish HTTP connection to the server.
25+
"""
26+
pass
27+
28+
# May throw an Exception on failure.
29+
def report(self, *args, **kwargs):
30+
"""Report to the server."""
31+
# Notice the annoying case change on the method name. I chose to stay
32+
# consistent with casing in this class vs staying consistent with the
33+
# casing of the pass-through method.
34+
auth = args[0]
35+
report = args[1]
36+
with self._lock:
37+
try:
38+
report.auth.access_token = auth.access_token
39+
headers = {"Content-Type": "application/octet-stream",
40+
"Accept": "application/octet-stream"}
41+
42+
r = requests.post(self._collector_url, headers=headers, data=report.SerializeToString())
43+
resp = ReportResponse()
44+
resp.ParseFromString(r.content)
45+
self._report_consecutive_errors = 0
46+
return resp
47+
except EOFError:
48+
self._report_consecutive_errors += 1
49+
self._report_eof_count += 1
50+
raise Exception('EOFError')
51+
finally:
52+
# In case the client has fallen into an unrecoverable state,
53+
# recreate the data structure if there are continued report
54+
# failures
55+
if self._report_consecutive_errors == CONSECUTIVE_ERRORS_BEFORE_RECONNECT:
56+
self._report_consecutive_errors = 0
57+
self.ready = False
58+
59+
def close(self):
60+
"""Close HTTP connection to the server."""
61+
self.ready = False
62+
pass

lightstep/http_converter.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from lightstep.collector_pb2 import Auth, ReportRequest, Span, Reporter, KeyValue, Reference, SpanContext
2+
from lightstep.converter import Converter
3+
from . import util
4+
from . import version as tracer_version
5+
import sys
6+
from google.protobuf.timestamp_pb2 import Timestamp
7+
8+
9+
class HttpConverter(Converter):
10+
11+
def create_auth(self, access_token):
12+
auth = Auth()
13+
auth.access_token = access_token
14+
return auth
15+
16+
def create_runtime(self, component_name, tags, guid):
17+
if component_name is None:
18+
component_name = sys.argv[0]
19+
20+
python_version = '.'.join(map(str, sys.version_info[0:3]))
21+
22+
if tags is None:
23+
tags = {}
24+
tracer_tags = tags.copy()
25+
26+
tracer_tags.update({
27+
'lightstep.tracer_platform': 'python',
28+
'lightstep.tracer_platform_version': python_version,
29+
'lightstep.tracer_version': tracer_version.LIGHTSTEP_PYTHON_TRACER_VERSION,
30+
'lightstep.component_name': component_name,
31+
'lightstep.guid': util._id_to_hex(guid),
32+
})
33+
34+
# Convert tracer_tags to a list of KeyValue pairs.
35+
runtime_attrs = [KeyValue(key=k, string_value=util._coerce_str(v)) for (k, v) in tracer_tags.items()]
36+
37+
return Reporter(reporter_id=guid, tags=runtime_attrs)
38+
39+
def create_span_record(self, span, guid):
40+
span_context = SpanContext(trace_id=span.context.trace_id,
41+
span_id=span.context.span_id)
42+
span_record = Span(span_context=span_context,
43+
operation_name=util._coerce_str(span.operation_name),
44+
start_timestamp=Timestamp(seconds=int(span.start_time)),
45+
duration_micros=int(util._time_to_micros(span.duration)))
46+
if span.parent_id is not None:
47+
reference = span_record.references.add()
48+
reference.relationship=Reference.CHILD_OF
49+
reference.span_context.span_id=span.parent_id
50+
51+
return span_record
52+
53+
def append_attribute(self, span_record, key, value):
54+
kv = span_record.tags.add()
55+
kv.key = key
56+
kv.string_value = value
57+
58+
def append_join_id(self, span_record, key, value):
59+
self.append_attribute(span_record, key, value)
60+
61+
def append_log(self, span_record, log):
62+
if log.key_values is not None and len(log.key_values) > 0:
63+
proto_log = span_record.logs.add()
64+
proto_log.timestamp.seconds=int(log.timestamp)
65+
for k, v in log.key_values.items():
66+
field = proto_log.fields.add()
67+
field.key = k
68+
field.string_value = util._coerce_str(v)
69+
70+
def create_report(self, runtime, span_records):
71+
return ReportRequest(reporter=runtime, spans=span_records)
72+
73+
def combine_span_records(self, report_request, span_records):
74+
report_request.spans.extend(span_records)
75+
return report_request.spans
76+
77+
def num_span_records(self, report_request):
78+
return len(report_request.spans)
79+
80+
def get_span_records(self, report_request):
81+
return report_request.spans
82+
83+
def get_span_name(self, span_record):
84+
return span_record.operation_name

0 commit comments

Comments
 (0)