Skip to content

Commit 73dd506

Browse files
authored
Python AWS Lambda Updates (#237)
* Move secrets and extra headers to BaseAgent * Update options, support service env var * Handle None service_name * Add more Trigger lookup safeties * Fix metrics->plugins reporting * Span Kind, Service name updates with tests * Add AWS Lambda layer release instructions * A logger just for AWS * Fargate tests are for another PR * Remove infeasible test * Better status code logging * Linter updates
1 parent f1d814b commit 73dd506

File tree

11 files changed

+296
-117
lines changed

11 files changed

+296
-117
lines changed

RELEASE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Release Steps
22

3+
## PyPI
4+
35
_Note: To release a new Instana package, you must be a project member of the [Instana package project on Pypi](https://pypi.org/project/instana/).
46
Contact [Peter Giacomo Lombardo](https://github.com/pglombardo) to be added._
57

@@ -12,3 +14,11 @@ Contact [Peter Giacomo Lombardo](https://github.com/pglombardo) to be added._
1214
7. Validate the new release on https://pypi.org/project/instana/
1315
8. Update Python documentation with latest changes: https://docs.instana.io/ecosystem/python/
1416
9. Publish the draft release on [Github](https://github.com/instana/python-sensor/releases)
17+
18+
## AWS Lambda Layer
19+
20+
To release a new AWS Lambda layer, see `bin/lambda_build_publish_layer.py`.
21+
22+
./bin/lambda_build_publish_layer.py [-dev|-prod]
23+
24+
This script assumes you have the AWS CLI tools installed and credentials already configured.

instana/agent.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,15 @@ def __init__(self, **kwds):
3939

4040
class BaseAgent(object):
4141
""" Base class for all agent flavors """
42-
client = requests.Session()
42+
client = None
4343
sensor = None
44+
secrets_matcher = 'contains-ignore-case'
45+
secrets_list = ['key', 'pass', 'secret']
46+
extra_headers = None
47+
options = None
4448

4549
def __init__(self):
46-
pass
50+
self.client = requests.Session()
4751

4852

4953
class StandardAgent(BaseAgent):
@@ -68,9 +72,6 @@ class StandardAgent(BaseAgent):
6872
last_seen = None
6973
last_fork_check = None
7074
_boot_pid = os.getpid()
71-
extra_headers = None
72-
secrets_matcher = 'contains-ignore-case'
73-
secrets_list = ['key', 'password', 'secret']
7475
should_threads_shutdown = threading.Event()
7576

7677
def __init__(self):
@@ -147,7 +148,7 @@ def set_from(self, json_string):
147148
@param json_string: source identifiers
148149
@return: None
149150
"""
150-
if type(json_string) is bytes:
151+
if isinstance(json_string, bytes):
151152
raw_json = json_string.decode("UTF-8")
152153
else:
153154
raw_json = json_string
@@ -353,10 +354,7 @@ def __init__(self):
353354
self.options = AWSLambdaOptions()
354355
self.report_headers = None
355356
self._can_send = False
356-
self.extra_headers = None
357-
358-
if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ:
359-
self.extra_headers = str(os.environ["INSTANA_EXTRA_HTTP_HEADERS"]).lower().split(';')
357+
self.extra_headers = self.options.extra_http_headers
360358

361359
if self._validate_options():
362360
self._can_send = True
@@ -394,7 +392,7 @@ def report_data_payload(self, payload):
394392
self.report_headers["X-Instana-Key"] = self.options.agent_key
395393
self.report_headers["X-Instana-Time"] = str(round(time.time() * 1000))
396394

397-
logger.debug("using these headers: %s", self.report_headers)
395+
# logger.debug("using these headers: %s", self.report_headers)
398396

399397
if 'INSTANA_DISABLE_CA_CHECK' in os.environ:
400398
ssl_verify = False
@@ -407,7 +405,10 @@ def report_data_payload(self, payload):
407405
timeout=self.options.timeout,
408406
verify=ssl_verify)
409407

410-
logger.debug("report_data_payload: response.status_code is %s", response.status_code)
408+
if 200 <= response.status_code < 300:
409+
logger.debug("report_data_payload: Instana responded with status code %s", response.status_code)
410+
else:
411+
logger.info("report_data_payload: Instana responded with status code %s", response.status_code)
411412
except Exception as e:
412413
logger.debug("report_data_payload: connection error (%s)", type(e))
413414
finally:

instana/collector.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ def collect_snapshot(self, event, context):
8585
self.event = event
8686

8787
try:
88-
self.snapshot_data["plugins"]["name"] = "com.instana.plugin.aws.lambda"
89-
self.snapshot_data["plugins"]["entityId"] = self.context.invoked_function_arn
88+
plugin_data = dict()
89+
plugin_data["name"] = "com.instana.plugin.aws.lambda"
90+
plugin_data["entityId"] = self.context.invoked_function_arn
91+
self.snapshot_data["plugins"] = [plugin_data]
9092
except:
9193
logger.debug("collect_snapshot error", exc_info=True)
9294
finally:

instana/instrumentation/aws/triggers.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ def is_cloudwatch_trigger(event):
3535

3636

3737
def is_cloudwatch_logs_trigger(event):
38-
if "awslogs" in event and event["awslogs"] != None:
38+
if hasattr(event, 'get') and event.get("awslogs", False) is not False:
3939
return True
40-
return False
40+
else:
41+
return False
4142

4243

4344
def is_s3_trigger(event):
@@ -61,19 +62,26 @@ def read_http_query_params(event):
6162
@param event: lambda event dict
6263
@return: String in the form of "a=b&c=d"
6364
"""
64-
if event is None or type(event) is not dict:
65-
return ""
66-
6765
params = []
68-
if 'multiValueQueryStringParameters' in event and event['multiValueQueryStringParameters'] is not None:
69-
for key in event['multiValueQueryStringParameters']:
70-
params.append("%s=%s" % (key, event['multiValueQueryStringParameters'][key]))
71-
return "&".join(params)
72-
elif 'queryStringParameters' in event and event['queryStringParameters'] is not None:
73-
for key in event['queryStringParameters']:
74-
params.append("%s=%s" % (key, event['queryStringParameters'][key]))
75-
return "&".join(params)
76-
else:
66+
try:
67+
if event is None or type(event) is not dict:
68+
return ""
69+
70+
mvqsp = event.get('multiValueQueryStringParameters', None)
71+
qsp = event.get('queryStringParameters', None)
72+
73+
if mvqsp is not None and type(mvqsp) is dict:
74+
for key in mvqsp:
75+
params.append("%s=%s" % (key, mvqsp[key]))
76+
return "&".join(params)
77+
elif qsp is not None and type(qsp) is dict:
78+
for key in qsp:
79+
params.append("%s=%s" % (key, qsp[key]))
80+
return "&".join(params)
81+
else:
82+
return ""
83+
except:
84+
logger.debug("read_http_query_params: ", exc_info=True)
7785
return ""
7886

7987

@@ -87,10 +95,16 @@ def capture_extra_headers(event, span, extra_headers):
8795
@param extra_headers: a list of http headers to capture
8896
@return: None
8997
"""
90-
for custom_header in extra_headers:
91-
for key in event["headers"]:
92-
if key.lower() == custom_header.lower():
93-
span.set_tag("http.%s" % custom_header, event["headers"][key])
98+
try:
99+
event_headers = event.get("headers", None)
100+
101+
if event_headers is not None:
102+
for custom_header in extra_headers:
103+
for key in event_headers:
104+
if key.lower() == custom_header.lower():
105+
span.set_tag("http.%s" % custom_header, event_headers[key])
106+
except:
107+
logger.debug("capture_extra_headers: ", exc_info=True)
94108

95109

96110
def enrich_lambda_span(agent, span, event, context):
@@ -109,6 +123,10 @@ def enrich_lambda_span(agent, span, event, context):
109123
span.set_tag('lambda.name', context.function_name)
110124
span.set_tag('lambda.version', context.function_version)
111125

126+
if event is None or type(event) is not dict:
127+
logger.debug("enrich_lambda_span: bad event %s", type(event))
128+
return
129+
112130
if is_api_gateway_proxy_trigger(event):
113131
span.set_tag('lambda.trigger', 'aws:api.gateway')
114132
span.set_tag('http.method', event["httpMethod"])
@@ -204,6 +222,5 @@ def enrich_lambda_span(agent, span, event, context):
204222
for item in event["Records"][:3]:
205223
events.append({'queue': item['eventSourceARN']})
206224
span.set_tag('lambda.sqs.messages', events)
207-
208225
except:
209226
logger.debug("enrich_lambda_span: ", exc_info=True)

instana/log.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def get_standard_logger():
1010
"""
1111
Retrieves and configures a standard logger for the Instana package
1212
13-
:return: Logger
13+
@return: Logger
1414
"""
1515
standard_logger = logging.getLogger("instana")
1616

@@ -26,12 +26,28 @@ def get_standard_logger():
2626
return standard_logger
2727

2828

29+
def get_aws_lambda_logger():
30+
"""
31+
Retrieves the preferred logger for AWS Lambda
32+
33+
@return: Logger
34+
"""
35+
aws_lambda_logger = logging.getLogger()
36+
37+
if "INSTANA_DEBUG" in os.environ:
38+
aws_lambda_logger.setLevel(logging.DEBUG)
39+
else:
40+
aws_lambda_logger.setLevel(logging.WARN)
41+
42+
return aws_lambda_logger
43+
44+
2945
def running_in_gunicorn():
3046
"""
3147
Determines if we are running inside of a gunicorn process and that the gunicorn logging package
3248
is available.
3349
34-
:return: Boolean
50+
@return: Boolean
3551
"""
3652
process_check = False
3753
package_check = False
@@ -70,5 +86,7 @@ def running_in_gunicorn():
7086

7187
if running_in_gunicorn():
7288
logger = logging.getLogger("gunicorn.error")
89+
elif os.environ.get("INSTANA_ENDPOINT_URL", False):
90+
logger = get_aws_lambda_logger()
7391
else:
7492
logger = get_standard_logger()

instana/options.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,36 @@
44

55
from .util import determine_service_name
66

7-
class StandardOptions(object):
8-
""" Configurable option bits for this package """
9-
service = None
7+
8+
class BaseOptions(object):
109
service_name = None
11-
agent_host = None
12-
agent_port = None
10+
extra_http_headers = None
1311
log_level = logging.WARN
1412
debug = None
1513

14+
def __init__(self, **kwds):
15+
try:
16+
if "INSTANA_DEBUG" in os.environ:
17+
self.log_level = logging.DEBUG
18+
self.debug = True
19+
if "INSTANA_EXTRA_HTTP_HEADERS" in os.environ:
20+
self.extra_http_headers = str(os.environ["INSTANA_EXTRA_HTTP_HEADERS"]).lower().split(';')
21+
except:
22+
pass
23+
24+
self.__dict__.update(kwds)
25+
26+
27+
class StandardOptions(BaseOptions):
28+
""" Configurable option bits for this package """
1629
AGENT_DEFAULT_HOST = "localhost"
1730
AGENT_DEFAULT_PORT = 42699
1831

32+
agent_host = None
33+
agent_port = None
34+
1935
def __init__(self, **kwds):
20-
if "INSTANA_DEBUG" in os.environ:
21-
self.log_level = logging.DEBUG
22-
self.debug = True
36+
super(StandardOptions, self).__init__()
2337

2438
self.service_name = determine_service_name()
2539
self.agent_host = os.environ.get("INSTANA_AGENT_HOST", self.AGENT_DEFAULT_HOST)
@@ -28,22 +42,15 @@ def __init__(self, **kwds):
2842
if type(self.agent_port) is str:
2943
self.agent_port = int(self.agent_port)
3044

31-
self.debug = os.environ.get("INSTANA_DEBUG", False)
32-
self.__dict__.update(kwds)
33-
3445

35-
class AWSLambdaOptions:
46+
class AWSLambdaOptions(BaseOptions):
3647
endpoint_url = None
3748
agent_key = None
3849
extra_http_headers = None
3950
timeout = None
40-
log_level = logging.WARN
41-
debug = None
4251

4352
def __init__(self, **kwds):
44-
if "INSTANA_DEBUG" in os.environ:
45-
self.log_level = logging.DEBUG
46-
self.debug = True
53+
super(AWSLambdaOptions, self).__init__()
4754

4855
self.endpoint_url = os.environ.get("INSTANA_ENDPOINT_URL", None)
4956

@@ -52,9 +59,7 @@ def __init__(self, **kwds):
5259
self.endpoint_url = self.endpoint_url[:-1]
5360

5461
self.agent_key = os.environ.get("INSTANA_AGENT_KEY", None)
55-
56-
self.extra_http_headers = os.environ.get("INSTANA_EXTRA_HTTP_HEADERS", None)
62+
self.service_name = os.environ.get("INSTANA_SERVICE_NAME", None)
5763
self.timeout = os.environ.get("INSTANA_TIMEOUT", 0.5)
5864
self.log_level = os.environ.get("INSTANA_LOG_LEVEL", None)
5965

60-
self.__dict__.update(kwds)

instana/recorder.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def record_span(self, span):
104104
source = instana.singletons.agent.get_from_structure()
105105

106106
if span.operation_name in self.REGISTERED_SPANS:
107-
json_span = RegisteredSpan(span, source)
107+
json_span = RegisteredSpan(span, source, None)
108108
else:
109109
service_name = instana.singletons.agent.options.service_name
110110
json_span = SDKSpan(span, source, service_name)
@@ -122,11 +122,11 @@ def record_span(self, span):
122122
Convert the passed BasicSpan and add it to the span queue
123123
"""
124124
source = self.agent.get_from_structure()
125+
service_name = self.agent.options.service_name
125126

126127
if span.operation_name in self.REGISTERED_SPANS:
127-
json_span = RegisteredSpan(span, source)
128+
json_span = RegisteredSpan(span, source, service_name)
128129
else:
129-
service_name = self.agent.options.service_name
130130
json_span = SDKSpan(span, source, service_name)
131131

132132
# logger.debug("Recorded span: %s", json_span)

0 commit comments

Comments
 (0)