Skip to content

Commit f045244

Browse files
saartochner-lumigoCircleCI
andauthored
Feature: send execution tags on timeout (#258)
* new span type: enrichment_span (send execution_tags on timeouted lambda) Co-authored-by: CircleCI <[email protected]>
1 parent 7e0363b commit f045244

File tree

7 files changed

+86
-40
lines changed

7 files changed

+86
-40
lines changed

.secrets.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
"filename": "src/test/unit/test_tracer.py",
208208
"hashed_secret": "dabe695c9892b5cb937379817679c0fb0be6a3df",
209209
"is_verified": false,
210-
"line_number": 422
210+
"line_number": 427
211211
}
212212
],
213213
"src/test/unit/wrappers/http/test_http_parser.py": [
@@ -220,5 +220,5 @@
220220
}
221221
]
222222
},
223-
"generated_at": "2022-08-22T12:01:52Z"
223+
"generated_at": "2022-08-25T09:52:57Z"
224224
}

src/lumigo_tracer/spans_container.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import time
77
import uuid
88
import signal
9-
from typing import List, Dict, Optional, Callable, Set
9+
from typing import List, Dict, Optional, Callable, Set, Union
1010

1111
from lumigo_tracer.event.event_dumper import EventDumper
1212
from lumigo_tracer.lumigo_utils import (
@@ -36,6 +36,7 @@
3636
_VERSION_PATH = os.path.join(os.path.dirname(__file__), "VERSION")
3737
MAX_LAMBDA_TIME = 15 * 60 * 1000
3838
FUNCTION_TYPE = "function"
39+
ENRICHMENT_TYPE = "enrichment"
3940
MALFORMED_TXID = "000000000000000000000000"
4041

4142

@@ -103,11 +104,17 @@ def __init__(
103104
**(trigger_by or {}),
104105
},
105106
"isMalformedTransactionId": malformed_txid,
106-
EXECUTION_TAGS_KEY: [],
107107
MANUAL_TRACES_KEY: [],
108108
},
109109
self.base_msg,
110110
)
111+
self.base_enrichment_span = {
112+
"type": ENRICHMENT_TYPE,
113+
"token": Configuration.token,
114+
"invocation_id": request_id,
115+
"transaction_id": transaction_id,
116+
}
117+
self.execution_tags: List[Dict[str, str]] = []
111118
self.span_ids_to_send: Set[str] = set()
112119
self.spans: Dict[str, Dict] = {}
113120
self.manual_trace_start_times: Dict[str, int] = {}
@@ -121,6 +128,14 @@ def _generate_start_span(self) -> dict:
121128
to_send["maxFinishTime"] = self.max_finish_time
122129
return to_send
123130

131+
def generate_enrichment_span(self) -> Optional[Dict[str, Union[str, int]]]:
132+
if not self.execution_tags:
133+
return None
134+
return recursive_json_join(
135+
{"sending_time": get_current_ms_time(), EXECUTION_TAGS_KEY: self.execution_tags.copy()},
136+
self.base_enrichment_span,
137+
)
138+
124139
def start(self, event=None, context=None):
125140
to_send = self._generate_start_span()
126141
if not Configuration.send_only_if_error:
@@ -137,6 +152,9 @@ def handle_timeout(self, *args):
137152
get_logger().info("The tracer reached the end of the timeout timer")
138153
spans_id_copy = self.span_ids_to_send.copy()
139154
to_send = [self.spans[span_id] for span_id in spans_id_copy]
155+
enrichment_span = self.generate_enrichment_span()
156+
if enrichment_span:
157+
to_send.insert(0, enrichment_span)
140158
self.span_ids_to_send.clear()
141159
if Configuration.send_only_if_error:
142160
to_send.append(self._generate_start_span())
@@ -244,10 +262,10 @@ def add_step_end_event(self, ret_val):
244262
get_logger().debug(f"Added key {LUMIGO_EVENT_KEY} to the user's return value")
245263

246264
def get_tags_len(self) -> int:
247-
return len(self.function_span[EXECUTION_TAGS_KEY])
265+
return len(self.execution_tags)
248266

249267
def add_tag(self, key: str, value: str) -> None:
250-
self.function_span[EXECUTION_TAGS_KEY].append({"key": key, "value": value})
268+
self.execution_tags.append({"key": key, "value": value})
251269

252270
def start_manual_trace(self, name: str) -> None:
253271
now = get_current_ms_time()
@@ -292,6 +310,9 @@ def end(self, ret_val=None, event: Optional[dict] = None, context=None) -> Optio
292310
to_send = [self.function_span] + [
293311
span for span_id, span in self.spans.items() if span_id in self.span_ids_to_send
294312
]
313+
enrichment_span = self.generate_enrichment_span()
314+
if enrichment_span:
315+
to_send.append(enrichment_span)
295316
reported_rtt = lumigo_utils.report_json(region=self.region, msgs=to_send)
296317
else:
297318
get_logger().debug(

src/test/unit/auto_tag/test_auto_tag_event.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
ConfigurationHandler,
1010
)
1111
from lumigo_tracer.spans_container import SpansContainer
12-
from lumigo_tracer.lumigo_utils import EXECUTION_TAGS_KEY, Configuration
12+
from lumigo_tracer.lumigo_utils import Configuration
1313

1414

1515
class ExceptionHandler(EventAutoTagHandler):
@@ -25,15 +25,15 @@ def auto_tag(event):
2525
def test_auto_tag_event_is_none():
2626
AutoTagEvent.auto_tag_event(event=None)
2727

28-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
28+
assert SpansContainer.get_span().execution_tags == []
2929

3030

3131
def test_auto_tag_exception():
3232
event = {"a": 1}
3333

3434
AutoTagEvent.auto_tag_event(event=event, handlers=[ExceptionHandler()])
3535

36-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
36+
assert SpansContainer.get_span().execution_tags == []
3737

3838

3939
def test_auto_tag_key_not_in_header(monkeypatch):
@@ -156,7 +156,7 @@ def test_auto_tag_key_not_in_header(monkeypatch):
156156

157157
AutoTagEvent.auto_tag_event(event=event)
158158

159-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
159+
assert SpansContainer.get_span().execution_tags == []
160160

161161

162162
def test_auto_tag_key_in_header(monkeypatch):
@@ -279,7 +279,7 @@ def test_auto_tag_key_in_header(monkeypatch):
279279

280280
AutoTagEvent.auto_tag_event(event=event)
281281

282-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
282+
assert SpansContainer.get_span().execution_tags == [
283283
{"key": "Accept", "value": "application/json, text/plain, */*"}
284284
]
285285

@@ -326,7 +326,7 @@ def test_configuration_handler_is_supported(config, event, expected):
326326
def test_configuration_handler_auto_tag(auto_tag_keys, event, result_tags):
327327
Configuration.auto_tag = auto_tag_keys
328328
ConfigurationHandler.auto_tag(event)
329-
tags = SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY]
329+
tags = SpansContainer.get_span().execution_tags
330330
assert len(tags) == len(result_tags)
331331
for tag in result_tags:
332332
assert tag in tags
@@ -335,7 +335,7 @@ def test_configuration_handler_auto_tag(auto_tag_keys, event, result_tags):
335335
def test_configuration_handler_auto_tag_failure(capsys):
336336
Configuration.auto_tag = [None, "key2"]
337337
ConfigurationHandler.auto_tag({"key1": datetime, "key2": "value"})
338-
tags = SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY]
338+
tags = SpansContainer.get_span().execution_tags
339339
assert tags == [{"key": "key2", "value": "value"}]
340340
assert "Failed to auto tag" in capsys.readouterr().out
341341

@@ -352,5 +352,5 @@ def test_configuration_handler_auto_tag_non_string(value, expected):
352352

353353
ConfigurationHandler.auto_tag({"key1": value})
354354

355-
tags = SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY]
355+
tags = SpansContainer.get_span().execution_tags
356356
assert {"key": "key1", "value": expected} in tags

src/test/unit/test_spans_container.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@
99

1010
import pytest
1111

12-
from lumigo_tracer import lumigo_utils
12+
from lumigo_tracer import lumigo_utils, add_execution_tag
1313
from lumigo_tracer.wrappers.http.http_parser import HTTP_TYPE
1414
from lumigo_tracer.spans_container import (
1515
SpansContainer,
1616
TimeoutMechanism,
1717
FUNCTION_TYPE,
1818
MALFORMED_TXID,
19+
ENRICHMENT_TYPE,
1920
)
2021
from lumigo_tracer.lumigo_utils import (
2122
Configuration,
22-
EXECUTION_TAGS_KEY,
2323
MANUAL_TRACES_KEY,
2424
get_current_ms_time,
25+
EXECUTION_TAGS_KEY,
2526
)
2627

2728

@@ -200,13 +201,35 @@ def test_timeout_mechanism_timeout_occurred_send_new_spans(monkeypatch, context,
200201
assert SpansContainer.get_span().span_ids_to_send
201202

202203

204+
def test_timeout_mechanism_timeout_occurred_but_finish_check_enrichment(
205+
monkeypatch, context, dummy_span, reporter_mock
206+
):
207+
SpansContainer.create_span()
208+
SpansContainer.get_span().start(context=context)
209+
SpansContainer.get_span().add_span(dummy_span)
210+
add_execution_tag("key", "value")
211+
SpansContainer.get_span().handle_timeout()
212+
213+
add_execution_tag("new_key", "new_value")
214+
SpansContainer.get_span().end(ret_val={"hello": "world"})
215+
216+
first_send = reporter_mock.call_args_list[1][1]["msgs"]
217+
enrichment_span = next(s for s in first_send if s["type"] == ENRICHMENT_TYPE)
218+
assert enrichment_span[EXECUTION_TAGS_KEY] == [{"key": "key", "value": "value"}]
219+
220+
final_send = reporter_mock.call_args_list[-1][1]["msgs"]
221+
enrichment_span = next(s for s in final_send if s["type"] == ENRICHMENT_TYPE)
222+
assert enrichment_span[EXECUTION_TAGS_KEY] == [
223+
{"key": "key", "value": "value"},
224+
{"key": "new_key", "value": "new_value"},
225+
]
226+
227+
203228
def test_add_tag():
204229
key = "my_key"
205230
value = "my_value"
206231
SpansContainer.get_span().add_tag(key, value)
207-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
208-
{"key": key, "value": value}
209-
]
232+
assert SpansContainer.get_span().execution_tags == [{"key": key, "value": value}]
210233

211234

212235
def test_start_manual_trace_simple_flow():

src/test/unit/test_tracer.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
SKIP_COLLECTING_HTTP_BODY_KEY,
2626
)
2727

28-
from lumigo_tracer.spans_container import SpansContainer
28+
from lumigo_tracer.spans_container import SpansContainer, ENRICHMENT_TYPE
2929
from moto import mock_kinesis
3030

3131
TOKEN = "t_10faa5e13e7844aaa1234"
@@ -365,7 +365,12 @@ def lambda_test_function(event, context):
365365
assert reporter_mock.call_count == 2
366366

367367

368-
def test_wrapping_with_tags(context):
368+
def get_enrichment_spans(reporter_mock):
369+
final_send = reporter_mock.call_args_list[-1][1]["msgs"]
370+
return [s for s in final_send if s["type"] == ENRICHMENT_TYPE]
371+
372+
373+
def test_wrapping_with_tags(context, reporter_mock):
369374
key = "my_key"
370375
value = "my_value"
371376

@@ -376,25 +381,25 @@ def lambda_test_function(event, context):
376381

377382
result = lambda_test_function({}, context)
378383
assert result == "ret_value"
379-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
380-
{"key": key, "value": value}
381-
]
384+
enrichment_spans = get_enrichment_spans(reporter_mock)
385+
assert len(enrichment_spans) == 1
386+
assert enrichment_spans[0][EXECUTION_TAGS_KEY] == [{"key": key, "value": value}]
382387

383388

384389
@pytest.mark.parametrize(
385390
"key, event",
386391
[("my_key", {"my_key": "my_value"}), ("my_key.key2", {"my_key": {"key2": "my_value"}})],
387392
)
388-
def test_wrapping_with_auto_tags(context, key, event):
393+
def test_wrapping_with_auto_tags(context, key, event, reporter_mock):
389394
@lumigo_tracer(auto_tag=[key])
390395
def lambda_test_function(event, context):
391396
return "ret_value"
392397

393398
result = lambda_test_function(event, context)
394399
assert result == "ret_value"
395-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
396-
{"key": key, "value": "my_value"}
397-
]
400+
enrichment_spans = get_enrichment_spans(reporter_mock)
401+
assert len(enrichment_spans) == 1
402+
assert enrichment_spans[0][EXECUTION_TAGS_KEY] == [{"key": key, "value": "my_value"}]
398403

399404

400405
def test_not_jsonable_return(monkeypatch, context):

src/test/unit/test_user_utils.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
manual_trace,
1717
manual_trace_sync,
1818
)
19-
from lumigo_tracer.lumigo_utils import EXECUTION_TAGS_KEY, MANUAL_TRACES_KEY
19+
from lumigo_tracer.lumigo_utils import MANUAL_TRACES_KEY
2020

2121

2222
def test_manual_traces_context_manager():
@@ -107,9 +107,7 @@ def test_add_execution_tag():
107107
key = "my_key"
108108
value = "my_value"
109109
assert add_execution_tag(key, value) is True
110-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
111-
{"key": key, "value": value}
112-
]
110+
assert SpansContainer.get_span().execution_tags == [{"key": key, "value": value}]
113111

114112

115113
def test_start_manual_trace_simple_flow():
@@ -121,25 +119,25 @@ def test_start_manual_trace_simple_flow():
121119
def test_add_execution_key_tag_empty(capsys):
122120
assert add_execution_tag("", "value") is False
123121
assert "Unable to add tag: key length" in capsys.readouterr().out
124-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
122+
assert SpansContainer.get_span().execution_tags == []
125123

126124

127125
def test_add_execution_value_tag_empty(capsys):
128126
assert add_execution_tag("key", "") is False
129127
assert "Unable to add tag: value length" in capsys.readouterr().out
130-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
128+
assert SpansContainer.get_span().execution_tags == []
131129

132130

133131
def test_add_execution_tag_key_pass_max_chars(capsys):
134132
assert add_execution_tag("k" * (MAX_TAG_KEY_LEN + 1), "value") is False
135133
assert "Unable to add tag: key length" in capsys.readouterr().out
136-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
134+
assert SpansContainer.get_span().execution_tags == []
137135

138136

139137
def test_add_execution_tag_value_pass_max_chars(capsys):
140138
assert add_execution_tag("key", "v" * (MAX_TAG_VALUE_LEN + 1)) is False
141139
assert "Unable to add tag: value length" in capsys.readouterr().out
142-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
140+
assert SpansContainer.get_span().execution_tags == []
143141

144142

145143
def test_add_execution_tag_pass_max_tags(capsys):
@@ -155,7 +153,7 @@ def test_add_execution_tag_pass_max_tags(capsys):
155153

156154
assert "Unable to add tag: maximum number of tags" in capsys.readouterr().out
157155
assert (
158-
SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY]
156+
SpansContainer.get_span().execution_tags
159157
== [{"key": key, "value": value}] * MAX_TAGS # noqa
160158
)
161159

@@ -167,4 +165,4 @@ def __str__(self):
167165

168166
assert add_execution_tag("key", ExceptionOnStr()) is False
169167
assert "Unable to add tag" in capsys.readouterr().out
170-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == []
168+
assert SpansContainer.get_span().execution_tags == []

src/test/unit/wrappers/http/test_sync_http_wrappers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from lumigo_tracer import lumigo_utils
1919
from lumigo_tracer.auto_tag import auto_tag_event
2020
from lumigo_tracer.lumigo_utils import (
21-
EXECUTION_TAGS_KEY,
2221
DEFAULT_MAX_ENTRY_SIZE,
2322
Configuration,
2423
TRUNCATE_SUFFIX,
@@ -355,7 +354,7 @@ def lambda_test_function(event, context):
355354
result = lambda_test_function(api_gw_event(), context)
356355

357356
assert result == "ret_value"
358-
assert SpansContainer.get_span().function_span[EXECUTION_TAGS_KEY] == [
357+
assert SpansContainer.get_span().execution_tags == [
359358
{"key": "Accept", "value": "application/json, text/plain, */*"}
360359
]
361360

0 commit comments

Comments
 (0)