Skip to content
This repository was archived by the owner on Jan 3, 2023. It is now read-only.

Commit 3e5df21

Browse files
authored
Merge pull request #36 from signalfx/context-propagation
Add context propagation
2 parents 583af57 + 4664a27 commit 3e5df21

File tree

6 files changed

+121
-53
lines changed

6 files changed

+121
-53
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
*.pyc
22
.idea
33
*.iml
4+
.vscode
5+
venv
46
dist
57
build
6-
*.egg-info
8+
*.egg-info

README.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ The decorators can be used individually or together.
133133
134134
import signalfx_lambda
135135
136-
@signalfx_lambda.emits_metrics
136+
@signalfx_lambda.emits_metrics()
137137
def handler(event, context):
138138
# your code
139139
@@ -143,10 +143,24 @@ The decorators can be used individually or together.
143143
144144
import signalfx_lambda
145145
146-
@signalfx_lambda.is_traced
146+
@signalfx_lambda.is_traced()
147147
def handler(event, context):
148148
# your code
149149
150+
3. Optionally, you can tell the wrapper to not auto-create a span but still initialize tracing for manual usage.
151+
152+
This is useful when processing SQS messages and you want each message to tie to the trace from producer that emitted the message.
153+
154+
.. code:: python
155+
156+
import signalfx_lambda
157+
158+
@signalfx_lambda.is_traced(with_span=False)
159+
def handler(event, context):
160+
for record in event.get('Records', []):
161+
with signalfx_lambda.create_span(record, context):
162+
# your code to process record
163+
150164
151165
Step 5: Send custom metrics from a Lambda function
152166
-------------------------------------------------------

signalfx_lambda/__init__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
from .version import name, version
88

99

10-
# backwards compatibility
11-
def wrapper(*args, **kwargs):
12-
return metrics.wrapper(*args, **kwargs)
13-
14-
1510
def emits_metrics(*args, **kwargs):
1611
return metrics.wrapper(*args, **kwargs)
1712

@@ -20,6 +15,10 @@ def is_traced(*args, **kwargs):
2015
return tracing.wrapper(*args, **kwargs)
2116

2217

18+
# backwards compatibility
19+
wrapper = emits_metrics
20+
21+
2322
# less convenient method
2423
def send_metric(counters=[], gauges=[]):
2524
metrics.send_metric(counters, gauges)

signalfx_lambda/metrics.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,25 @@ def call(*args, **kwargs):
108108
return wrapper_decorator
109109

110110

111-
def wrapper(*args, **kwargs):
112-
access_token = utils.get_access_token()
113-
if len(args) == 1 and callable(args[0]):
114-
# plain wrapper with no parameter
115-
# call the wrapper decorator like normally would
116-
decorator = generate_wrapper_decorator(access_token)
117-
return decorator(args[0])
118-
else:
119-
dimensions = kwargs.get('dimensions')
120-
if isinstance(dimensions, dict):
121-
# wrapper with dimension parameter
122-
# assign default dimensions
123-
# then return the wrapper decorator
124-
default_dimensions.update(dimensions)
125-
126-
token = kwargs.get('access_token')
127-
if isinstance(token, six.string_types):
128-
access_token = token
129-
130-
return generate_wrapper_decorator(access_token)
111+
def wrapper():
112+
def inner(*args, **kwargs):
113+
access_token = utils.get_access_token()
114+
if len(args) == 1 and callable(args[0]):
115+
# plain wrapper with no parameter
116+
# call the wrapper decorator like normally would
117+
decorator = generate_wrapper_decorator(access_token)
118+
return decorator(args[0])
119+
else:
120+
dimensions = kwargs.get('dimensions')
121+
if isinstance(dimensions, dict):
122+
# wrapper with dimension parameter
123+
# assign default dimensions
124+
# then return the wrapper decorator
125+
default_dimensions.update(dimensions)
126+
127+
token = kwargs.get('access_token')
128+
if isinstance(token, six.string_types):
129+
access_token = token
130+
131+
return generate_wrapper_decorator(access_token)
132+
return inner

signalfx_lambda/tracing.py

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,31 @@
66

77
from . import utils
88

9-
def wrapper(func):
10-
@functools.wraps(func)
11-
def call(*args, **kwargs):
12-
context = args[1]
9+
_tracer = None
1310

14-
tracer = init_jaeger_tracer(context)
11+
span_kind_mapping = {
12+
'aws:sqs': ext_tags.SPAN_KIND_CONSUMER,
13+
}
1514

16-
span_tags = utils.get_tracing_fields(context)
17-
span_tags['component'] = 'python-lambda-wrapper'
18-
span_tags[ext_tags.SPAN_KIND] = ext_tags.SPAN_KIND_RPC_SERVER
15+
def wrapper(with_span=True):
16+
def inner(func):
17+
@functools.wraps(func)
18+
def call(event, context):
19+
tracer = init_jaeger_tracer(context)
20+
try:
21+
if with_span:
22+
with create_span(event, context):
23+
# call the original handler
24+
return func(event, context)
25+
else:
26+
return func(event, context)
27+
except BaseException as e:
28+
raise
29+
finally:
30+
tracer.close()
1931

20-
span_prefix = os.getenv('SIGNALFX_SPAN_PREFIX', 'lambda_python_')
21-
22-
try:
23-
with tracer.start_active_span(span_prefix + context.function_name, tags=span_tags) as scope:
24-
# call the original handler
25-
return func(*args, **kwargs)
26-
except BaseException as e:
27-
scope.span.set_tag('error', True)
28-
scope.span.log_kv({'message': e})
29-
30-
raise
31-
finally:
32-
tracer.close()
33-
34-
return call
32+
return call
33+
return inner
3534

3635

3736
def init_jaeger_tracer(context):
@@ -56,6 +55,58 @@ def init_jaeger_tracer(context):
5655
config = Config(config=tracer_config, service_name=service_name)
5756

5857
tracer = config.new_tracer()
59-
opentracing.tracer = tracer
58+
global _tracer
59+
_tracer = opentracing.tracer = tracer
6060

6161
return tracer
62+
63+
64+
class create_span(object):
65+
def __init__(self, event, context, auto_add_tags=True, operation_name=None):
66+
if not _tracer:
67+
raise RuntimeError((
68+
'tracing has not been initialized. Use signalfx_lambda.is_tracer'
69+
' decorator to initialize tracing'))
70+
self.event = event
71+
self.context = context
72+
self.auto_add_tags = auto_add_tags
73+
self.operation_name = operation_name
74+
self.tracer = _tracer
75+
self.scope = None
76+
77+
def __enter__(self):
78+
headers = self.event.get('headers', self.event.get('attributes', {}))
79+
parent_span = self.tracer.extract(opentracing.Format.HTTP_HEADERS, headers)
80+
81+
span_tags = {}
82+
if self.auto_add_tags:
83+
span_tags = utils.get_tracing_fields(self.context)
84+
span_tags['component'] = 'python-lambda-wrapper'
85+
span_tags[ext_tags.SPAN_KIND] = span_kind_mapping.get(
86+
self.event.get('eventSource'),
87+
ext_tags.SPAN_KIND_RPC_SERVER
88+
)
89+
90+
op_name = self.operation_name
91+
if not op_name:
92+
span_prefix = os.getenv('SIGNALFX_SPAN_PREFIX', 'lambda_python_')
93+
op_name = span_prefix + self.context.function_name
94+
95+
self.scope = self.tracer.start_active_span(
96+
op_name,
97+
tags=span_tags,
98+
child_of=parent_span
99+
)
100+
return self.scope
101+
102+
def __exit__(self, exc_type, exc_val, exc_tb):
103+
if not self.scope:
104+
return
105+
106+
if exc_val:
107+
span = self.scope.span
108+
span.set_tag(ext_tags.ERROR, True)
109+
span.set_tag("sfx.error.message", str(exc_val))
110+
span.set_tag("sfx.error.object", str(exc_val.__class__))
111+
span.set_tag("sfx.error.kind", exc_val.__class__.__name__)
112+
self.scope.close()

signalfx_lambda/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (C) 2017 SignalFx, Inc. All rights reserved.
22

33
name = 'signalfx_lambda'
4-
version = '0.2.1'
4+
version = '1.0.0beta1'
55

66
user_agent = 'signalfx_lambda/' + version

0 commit comments

Comments
 (0)