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

Commit 4664a27

Browse files
committed
Add context propagation
New features and breaking changes: 1. HTTP context propagation The wrapper now automatically tries to extract B3 tracing headers from incoming HTTP requests and uses the extracted span context as the parent span when creating new spans. 2. Allows users to initialize the tracing without auto-creating the span The decorator now must be called and accepts an optional argument `with_span=<bool>` which defaults to `True`. When set to `False`, the wrapper still initializes and flushes the tracer but does not auto create spans. 3. Removed signalfx_lambda.wrapper Since we are breaking backward compatibility here and releaing a breaking version according to semver, we also removed the depracated wrapper decorator.
1 parent 583af57 commit 4664a27

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)