def test_prepare(): trace = trace_factory.get_or_create_trace() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') trace.prepare() assert not trace.events assert trace.exceptions == [] assert len(w) == 1 trace.clear_events() trace.add_event(EventMock()) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') trace.prepare() assert not trace.events assert trace.exceptions == [] assert len(w) == 1 trace.clear_events() trace.add_event(EventMock()) with warnings.catch_warnings(record=True) as w: trace.prepare() trace.prepare() # this call should NOT trigger a warning assert not trace.events assert trace.exceptions == [] assert len(w) == 1
def test_add_event(): event = EventMock() trace = trace_factory.get_or_create_trace() trace.clear_events() for i in range(10): # verify we can add more then 1 event trace.add_event(event) assert event is trace.events[i] assert event.terminated
def test_send_with_sample_good_rate_with_error(wrapped_post, _): trace = trace_factory.get_or_create_trace() trace.runner = RunnerEventMock() trace.sample_rate = 0.6 trace.runner.error_code = ErrorCode.ERROR trace.add_event(trace.runner) trace.token = 'a' trace.add_event(EventMock()) trace_factory.send_traces() wrapped_post.assert_called_once()
def test_send_on_error_only_off_no_error(wrapped_post): trace = trace_factory.get_or_create_trace() trace.token = 'a' trace.runner = RunnerEventMock() trace.runner.error_code = ErrorCode.OK event = EventMock() event.resource['metadata'] = datetime.fromtimestamp(1000) trace.add_event(event) trace_factory.send_traces() wrapped_post.assert_called_once()
def test_send_on_error_only_with_error(wrapped_post): trace = trace_factory.get_or_create_trace() trace.send_trace_only_on_error = True trace.runner = mock.MagicMock() trace.runner.error_code = ErrorCode.ERROR trace.token = 'a' event = EventMock() event.resource['metadata'] = datetime.fromtimestamp(1000) trace.add_event(event) trace.send_traces() wrapped_post.assert_called_once()
def test_send_with_split_on_small_trace(wrapped_post, monkeypatch): # Should be low enough to force trace split. monkeypatch.setenv('EPSAGON_MAX_TRACE_SIZE', '500') trace = trace_factory.get_or_create_trace() trace.runner = RunnerEventMock() trace.add_event(trace.runner) trace.token = 'a' trace.split_on_send = True event = EventMock() trace.add_event(event) trace_factory.send_traces() wrapped_post.assert_called_once()
def test_send_with_split_on_small_trace(wrapped_post): # Should be low enough to force trace split. os.environ['EPSAGON_MAX_TRACE_SIZE'] = '500' trace = trace_factory.get_or_create_trace() trace.runner = mock.MagicMock() trace.add_event(trace.runner) trace.token = 'a' trace.split_on_send = True event = EventMock() trace.add_event(event) trace.send_traces() wrapped_post.assert_called_once() os.environ.pop('EPSAGON_MAX_TRACE_SIZE')
def test_send_with_split_off(wrapped_post): # Should be low enough to force trace split. os.environ['EPSAGON_MAX_TRACE_SIZE'] = '500' trace = trace_factory.get_or_create_trace() trace.runner = RunnerEventMock() trace.add_event(trace.runner) trace.token = 'a' trace.split_on_send = False for _ in range(10): event = EventMock() trace.add_event(event) trace_factory.send_traces() wrapped_post.assert_called_once()
def test_event_with_datetime(wrapped_post): trace = trace_factory.get_or_create_trace() trace.token = 'a' event = EventMock() event.resource['metadata'] = datetime.fromtimestamp(1000) trace.add_event(event) trace_factory.send_traces() wrapped_post.assert_called_with( '', data=json.dumps(trace.to_dict(), cls=TraceEncoder), timeout=epsagon.constants.SEND_TIMEOUT, headers={'Authorization': 'Bearer {}'.format(trace.token)})
def test_event_with_datetime(wrapped_post): epsagon.utils.init(token='token', app_name='app-name', collector_url='collector') trace = trace_factory.get_or_create_trace() event = EventMock() event.resource['metadata'] = datetime.fromtimestamp(1000) trace.add_event(event) trace_factory.send_traces() wrapped_post.assert_called_with( 'POST', 'collector', body=json.dumps(trace.to_dict(), cls=TraceEncoder), timeout=epsagon.constants.SEND_TIMEOUT, )
def _gcp_wrapper(*args, **kwargs): """ Generic google function wrapper """ trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: runner = GoogleFunctionRunner(time.time(), ) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'GCP environment is invalid, using simple python wrapper', EpsagonWarning) trace.add_exception(exception, traceback.format_exc()) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs) constants.COLD_START = False result = None try: result = func(*args, **kwargs) return result # pylint: disable=W0703 except Exception as exception: runner.set_exception(exception, traceback.format_exc()) raise finally: try: if not trace.metadata_only: runner.resource['metadata']['return_value'] = result # pylint: disable=W0703 except Exception as exception: trace.add_exception(exception, traceback.format_exc()) try: trace.add_event(runner) epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: pass
def test_to_dict(): trace = epsagon.trace.Trace() expected_dict = { 'token': 'token', 'app_name': 'app_name', 'events': [EventMockWithCounter(i) for i in range(10)], 'exceptions': 'exceptions', 'version': 'version', 'platform': 'platform' } trace.token = expected_dict['token'] trace.app_name = expected_dict['app_name'] for event in [EventMockWithCounter(i) for i in range(10)]: trace.add_event(event) trace.exceptions = expected_dict['exceptions'] trace.version = expected_dict['version'] trace.platform = expected_dict['platform'] trace_dict = trace.to_dict() assert trace_dict == trace.to_dict()
def _before_request(self): """ Runs when new request comes in. :return: None. """ # Ignoring non relevant content types. self.ignored_request = ignore_request('', request.path.lower()) if self.ignored_request: return trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() # Create flask runner with current request. try: self.runner = epsagon.runners.flask.FlaskRunner( time.time(), self.app, request) trace.set_runner(self.runner) # Collect metadata in case this is a container. collect_container_metadata(self.runner.resource['metadata']) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn('Could not extract request', EpsagonWarning) trace.add_exception(exception, traceback.format_exc()) # Extract HTTP trigger data. try: trigger = epsagon.triggers.http.HTTPTriggerFactory.factory( time.time(), request) if trigger: trace.add_event(trigger) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), )
def test_send_big_trace(wrapped_post): trace = trace_factory.get_or_create_trace() runner = RunnerEventMock() trace.set_runner(runner) for _ in range(2): trace.add_event(BigEventMock()) trace_factory.send_traces() assert len(trace.to_dict()['events']) == 3 for event in trace.to_dict()['events']: if event['origin'] == 'runner': assert event['resource']['metadata']['is_trimmed'] wrapped_post.assert_called_with( 'POST', 'collector', body=json.dumps(trace.to_dict()), timeout=epsagon.constants.SEND_TIMEOUT, )
def test_send_big_trace(wrapped_post): trace = trace_factory.get_or_create_trace() runner = RunnerEventMock() trace.set_runner(runner) trace.token = 'a' for _ in range(2): trace.add_event(BigEventMock()) trace_factory.send_traces() assert len(trace.to_dict()['events']) == 3 for event in trace.to_dict()['events']: if event['origin'] == 'runner': assert event['resource']['metadata']['is_trimmed'] wrapped_post.assert_called_with( '', data=json.dumps(trace.to_dict()), timeout=epsagon.constants.SEND_TIMEOUT, headers={'Authorization': 'Bearer {}'.format(trace.token)})
def _tencent_function_wrapper(*args, **kwargs): """ Generic SCF function wrapper """ start_time = time.time() cold_start_duration = start_time - constants.COLD_START_TIME trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: event, context = args except ValueError: # This can happen when someone manually calls handler without # parameters / sends kwargs. In such case we ignore this trace. return func(*args, **kwargs) try: runner = epsagon.runners.tencent_function.TencentFunctionRunner( start_time, context) trace.set_runner(runner) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'SCF context is invalid, using simple python wrapper', EpsagonWarning) trace.add_exception(exception, traceback.format_exc()) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs) if constants.COLD_START: runner.resource['metadata'][ 'tencent.scf.cold_start_duration'] = cold_start_duration constants.COLD_START = False try: trace.add_event( TencentFunctionTriggerFactory.factory(start_time, event, context, runner)) # pylint: disable=W0703 except Exception as exception: trace.add_exception(exception, traceback.format_exc(), additional_data={'event': event}) result = None try: result = func(*args, **kwargs) return result # pylint: disable=W0703 except Exception as exception: runner.set_exception(exception, traceback.format_exc(), handled=False) raise finally: try: _add_status_code(runner, result) if not trace.metadata_only: runner.resource['metadata']['tencent.scf.return_data'] = ( result) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), ) try: epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: epsagon.utils.print_debug('Failed to send SCF trace')
def _lambda_wrapper(*args, **kwargs): """ Generic Step Function wrapper """ cold_start_duration = time.time() - constants.COLD_START_TIME trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: event, context = args except ValueError: # This can happen when someone manually calls handler without # parameters / sends kwargs. In such case we ignore this trace. return func(*args, **kwargs) try: runner = epsagon.runners.aws_lambda.StepLambdaRunner( time.time(), context ) trace.set_runner(runner) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'Lambda context is invalid, using simple python wrapper', EpsagonWarning ) trace.add_exception( exception, traceback.format_exc() ) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs ) if constants.COLD_START: runner.resource['metadata'][ 'aws.lambda.cold_start_duration' ] = cold_start_duration constants.COLD_START = False try: trace.add_event( epsagon.triggers.aws_lambda.LambdaTriggerFactory.factory( time.time(), event, context ) ) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), additional_data={'event': event} ) trace.set_timeout_handler(context) result = None try: result = func(*args, **kwargs) steps_data = epsagon.utils.find_in_object( event, STEP_DICT_NAME ) if isinstance(result, dict): epsagon.utils.print_debug( 'Step function result type is dict, steps_data={}'.format( steps_data ) ) # If the step functions data is not present, then this is the # First step. if steps_data is None: epsagon.utils.print_debug( 'Could not find existing steps data' ) steps_dict = {'id': str(uuid4()), 'step_num': 0} path = [] # Otherwise, just advance the steps number by one. else: # don't change trigger data steps_dict, path = steps_data steps_dict = copy.deepcopy(steps_dict) if 'step_num' in steps_dict: steps_dict['step_num'] += 1 epsagon.utils.print_debug( 'Steps data found, new dict={}'.format(steps_dict) ) else: steps_dict = {'id': str(uuid4()), 'step_num': 0} epsagon.utils.print_debug( 'Steps data not found, new dict={}'.format( steps_dict ) ) result_path = result # Tries to inject the steps data in the configured # or same path where it was found if isinstance(trace.step_dict_output_path, list): path = trace.step_dict_output_path try: for sub_path in path: result_path = result_path.get(sub_path) except Exception as exception: # pylint: disable=broad-except epsagon.utils.print_debug( 'Could not put steps in path={}'.format(path) ) if result_path: epsagon.utils.print_debug( 'Adding steps dict to result_path={}'.format( result_path ) ) result_path[STEP_DICT_NAME] = steps_dict else: epsagon.utils.print_debug( 'Adding steps dict to root result' ) result[STEP_DICT_NAME] = steps_dict runner.add_step_data(steps_dict) return result # pylint: disable=W0703 except Exception as exception: runner.set_exception( exception, traceback.format_exc(), handled=False ) raise finally: try: _add_status_code(runner, result) if not trace.metadata_only: runner.resource['metadata']['return_value'] = ( copy.deepcopy(result) ) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), ) try: epsagon.trace.Trace.reset_timeout_handler() # pylint: disable=W0703 except Exception: pass try: epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: pass
def _lambda_wrapper(*args, **kwargs): """ Generic Step Function wrapper """ trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: event, context = args except ValueError: # This can happen when someone manually calls handler without # parameters / sends kwargs. In such case we ignore this trace. return func(*args, **kwargs) try: runner = epsagon.runners.aws_lambda.StepLambdaRunner( time.time(), context) trace.set_runner(runner) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'Lambda context is invalid, using simple python wrapper', EpsagonWarning) trace.add_exception(exception, traceback.format_exc()) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs) constants.COLD_START = False try: trace.add_event( epsagon.triggers.aws_lambda.LambdaTriggerFactory.factory( time.time(), event, context)) # pylint: disable=W0703 except Exception as exception: trace.add_exception(exception, traceback.format_exc(), additional_data={'event': event}) trace.set_timeout_handler(context) result = None try: result = func(*args, **kwargs) steps_dict = epsagon.utils.find_in_object(event, STEP_DICT_NAME) if isinstance(result, dict): # If the step functions data is not present, then this is the # First step. if steps_dict is None: steps_dict = {'id': str(uuid4()), 'step_num': 0} # Otherwise, just advance the steps number by one. else: # don't change trigger data steps_dict = copy.deepcopy(steps_dict) steps_dict['step_num'] += 1 result[STEP_DICT_NAME] = steps_dict runner.add_step_data(steps_dict) return result # pylint: disable=W0703 except Exception as exception: runner.set_exception(exception, traceback.format_exc(), handled=False) raise finally: try: _add_status_code(runner, result) if not trace.metadata_only: runner.resource['metadata']['return_value'] = ( copy.deepcopy(result)) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), ) try: epsagon.trace.Trace.reset_timeout_handler() # pylint: disable=W0703 except Exception: pass try: epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: pass
def _lambda_wrapper(*args, **kwargs): """ Generic Lambda function wrapper """ trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: event, context = args except ValueError: # This can happen when someone manually calls handler without # parameters / sends kwargs. In such case we ignore this trace. return func(*args, **kwargs) try: runner = epsagon.runners.aws_lambda.LambdaRunner( time.time(), context) trace.set_runner(runner) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'Lambda context is invalid, using simple python wrapper', EpsagonWarning) trace.add_exception(exception, traceback.format_exc()) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs) constants.COLD_START = False try: trace.add_event( epsagon.triggers.aws_lambda.LambdaTriggerFactory.factory( time.time(), event, context)) # pylint: disable=W0703 except Exception as exception: trace.add_exception(exception, traceback.format_exc(), additional_data={'event': event}) if not trace.disable_timeout_send: trace.set_timeout_handler(context) result = None try: result = func(*args, **kwargs) if trace.propagate_lambda_id and isinstance(result, dict): result[EPSAGON_EVENT_ID_KEY] = runner.event_id runner.resource['metadata']['propagation_enabled'] = True return result # pylint: disable=W0703 except Exception as exception: runner.set_exception(exception, traceback.format_exc(), handled=False) raise finally: try: _add_status_code(runner, result) if not trace.metadata_only: runner.resource['metadata']['return_value'] = result # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), ) try: if not trace.disable_timeout_send: epsagon.trace.Trace.reset_timeout_handler() # pylint: disable=W0703 except Exception: pass try: epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: pass
def _lambda_wrapper(*args, **kwargs): """ Generic Lambda function wrapper """ cold_start_duration = time.time() - constants.COLD_START_TIME trace = epsagon.trace.trace_factory.get_or_create_trace() trace.prepare() try: event, context = args except ValueError: # This can happen when someone manually calls handler without # parameters / sends kwargs. In such case we ignore this trace. return func(*args, **kwargs) if isinstance(event, dict): ignored_payloads = _get_ignored_payloads() if ignored_payloads and event in ignored_payloads: return func(*args, **kwargs) if os.environ.get( 'AWS_LAMBDA_INITIALIZATION_TYPE' ) == 'provisioned-concurrency': constants.COLD_START = False try: runner = epsagon.runners.aws_lambda.LambdaRunner( time.time(), context ) trace.set_runner(runner) # pylint: disable=W0703 except Exception as exception: # Regress to python runner. warnings.warn( 'Lambda context is invalid, using simple python wrapper', EpsagonWarning ) trace.add_exception( exception, traceback.format_exc() ) return epsagon.wrappers.python_function.wrap_python_function( func, args, kwargs ) if constants.COLD_START: runner.resource['metadata'][ 'aws.lambda.cold_start_duration' ] = cold_start_duration constants.COLD_START = False try: trace.add_event( epsagon.triggers.aws_lambda.LambdaTriggerFactory.factory( time.time(), event, context ) ) # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), additional_data={'event': event} ) if not trace.disable_timeout_send: trace.set_timeout_handler(context) result = None try: result = func(*args, **kwargs) if trace.propagate_lambda_id and isinstance(result, dict): result[EPSAGON_EVENT_ID_KEY] = runner.event_id runner.resource['metadata']['propagation_enabled'] = True return result # pylint: disable=W0703 except Exception as exception: runner.set_exception( exception, traceback.format_exc(), handled=False ) raise finally: try: _add_status_code(runner, result) if not trace.metadata_only: runner.resource['metadata']['return_value'] = result # pylint: disable=W0703 except Exception as exception: trace.add_exception( exception, traceback.format_exc(), ) try: if not trace.disable_timeout_send: epsagon.trace.Trace.reset_timeout_handler() # pylint: disable=W0703 except Exception: pass try: epsagon.trace.trace_factory.send_traces() # pylint: disable=W0703 except Exception: epsagon.utils.print_debug('Failed to send Lambda trace')