def test_requests_exception_without_response(self, *_, **__): with self.assertRaises(requests.RequestException): self.perform_request(self.URL) span = self.assert_span() self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL }, ) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.UNKNOWN) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.metrics), 1) recorder = RequestsInstrumentor().meter.metrics.pop() match_key = get_dict_as_key({ "http.method": "GET", "http.url": "http://httpbin.org/status/200", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def test_requests_exception_with_response(self, *_, **__): with self.assertRaises(requests.RequestException): self.perform_request(self.URL) span = self.assert_span() self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL, "http.status_code": 500, "http.status_text": "Internal Server Error", }, ) self.assertEqual(span.status.status_code, StatusCode.ERROR) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.instruments), 1) recorder = list(RequestsInstrumentor().meter.instruments.values())[0] match_key = get_dict_as_key( { "http.method": "GET", "http.status_code": "500", "http.url": "http://httpbin.org/status/200", } ) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1)
def test_uninstrument(self): RequestsInstrumentor().uninstrument() result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") self.assert_span(num_spans=0) # instrument again to avoid annoying warning message RequestsInstrumentor().instrument()
def test_span_callback(self): RequestsInstrumentor().uninstrument() def span_callback(span, result: requests.Response): span.set_attribute("http.response.body", result.content.decode("utf-8")) RequestsInstrumentor().instrument( tracer_provider=self.tracer_provider, span_callback=span_callback, ) result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL, "http.status_code": 200, "http.status_text": "OK", "http.response.body": "Hello!", }, )
def test_span_callback(self): RequestsInstrumentor().uninstrument() def span_callback(span, result: requests.Response): span.set_attribute("http.response.body", result.content.decode("utf-8")) RequestsInstrumentor().instrument( tracer_provider=self.tracer_provider, span_callback=span_callback, ) result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertEqual( span.attributes, { SpanAttributes.HTTP_METHOD: "GET", SpanAttributes.HTTP_URL: self.URL, SpanAttributes.HTTP_STATUS_CODE: 200, "http.response.body": "Hello!", }, )
def test_uninstrument(self): RequestsInstrumentor().uninstrument() result = requests.get(self.URL) self.assertEqual(result.text, "Hello!") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) # instrument again to avoid annoying warning message RequestsInstrumentor().instrument()
def test_name_callback_default(self): def name_callback(): return 123 RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument(name_callback=name_callback) result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertEqual(span.name, "HTTP GET")
def test_name_callback(self): def name_callback(method, url): return "GET" + url RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument(name_callback=name_callback) result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertEqual(span.name, "GET" + self.URL)
def test_custom_tracer_provider(self): resource = resources.Resource.create({}) result = self.create_tracer_provider(resource=resource) tracer_provider, exporter = result RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument(tracer_provider=tracer_provider) result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span(exporter=exporter) self.assertIs(span.resource, resource)
def test_not_recording(self): with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span: RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument( tracer_provider=trace._DefaultTracerProvider()) mock_span.is_recording.return_value = False result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") self.assert_span(None, 0) self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called)
def test_excluded_urls_from_env(self): url = "http://localhost/env_excluded_arg/123" httpretty.register_uri( httpretty.GET, url, status=200, ) RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument() self.perform_request(self.URL) self.perform_request(url) self.assert_span(num_spans=1)
def test_excluded_urls_explicit(self): url_404 = "http://httpbin.org/status/404" httpretty.register_uri( httpretty.GET, url_404, status=404, ) RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument(excluded_urls=".*/404") self.perform_request(self.URL) self.perform_request(url_404) self.assert_span(num_spans=1)
def test_custom_tracer_provider(self): resource = resources.Resource.create({}) result = self.create_tracer_provider(resource=resource) tracer_provider, exporter = result RequestsInstrumentor().uninstrument() RequestsInstrumentor().instrument(tracer_provider=tracer_provider) result = requests.get(self.URL) self.assertEqual(result.text, "Hello!") span_list = exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertIs(span.resource, resource)
def test_not_recording(self): with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span: RequestsInstrumentor().uninstrument() # original_tracer_provider returns a default tracer provider, which # in turn will return an INVALID_SPAN, which is always not recording RequestsInstrumentor().instrument( tracer_provider=self.original_tracer_provider) mock_span.is_recording.return_value = False result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") self.assert_span(None, 0) self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called)
def test_basic(self): result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") span = self.assert_span() self.assertIs(span.kind, trace.SpanKind.CLIENT) self.assertEqual(span.name, "HTTP GET") self.assertEqual( span.attributes, { "component": "http", "http.method": "GET", "http.url": self.URL, "http.status_code": 200, "http.status_text": "OK", }, ) self.assertIs(span.status.canonical_code, trace.status.StatusCanonicalCode.OK) self.check_span_instrumentation_info( span, opentelemetry.instrumentation.requests) self.assertIsNotNone(RequestsInstrumentor().meter) self.assertEqual(len(RequestsInstrumentor().meter.metrics), 1) recorder = RequestsInstrumentor().meter.metrics.pop() match_key = get_dict_as_key({ "http.flavor": "1.1", "http.method": "GET", "http.status_code": "200", "http.status_text": "OK", "http.url": "http://httpbin.org/status/200", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) self.assertGreater(view_data.aggregator.current.sum, 0)
def test_third_party_instrumentor(self, telemetry: TelemetryFixture): import requests from telemetry.api.listeners.span import LabelAttributes, InstrumentorSpanListener RequestsInstrumentor().instrument() telemetry.initialize() telemetry.add_span_processor(InstrumentorSpanListener( LabelAttributes('component', 'http.status_code', 'http.method'), 'requests')) responses.add_passthru('http://localhost:1234/does_not_exist') with telemetry.span('test_category', 'span1', attributes={TestAttributes.LABEL1: 'l1'}) as span: try: with requests.get('http://localhost:1234/does_not_exist') as response: pass except: pass telemetry.collect() assert telemetry.get_value_recorder(name='trace.duration', labels={'component': 'http', 'http.method': 'GET', TestAttributes.LABEL1.name: 'l1', Attributes.TRACE_CATEGORY.name: 'requests', Attributes.TRACE_NAME.name: 'requests.HTTP GET', Attributes.TRACE_STATUS.name: 'ERROR'}).count == 1
def setup_instrumentation(app): settings: TracingSettings = _get_settings() _TRACE_PROVIDER = TracerProvider( resource=Resource.create({"service.name": settings.jaeger_service}) ) trace.set_tracer_provider(_TRACE_PROVIDER) if settings.jaeger_hostname: # pragma: no cover _JAEGER_EXPORTER = JaegerExporter( agent_host_name=settings.jaeger_hostname, agent_port=settings.jaeger_port, ) _TRACE_PROVIDER.add_span_processor(BatchSpanProcessor(_JAEGER_EXPORTER)) AioHttpClientInstrumentor().instrument() RequestsInstrumentor().instrument() # Register logging middleware app.middleware("http")(_log_requests_middleware) app.middleware("http")(_bind_logger_tracecontext_middleware) FastAPIInstrumentor.instrument_app(app) return app
def setUp(self): super().setUp() self.assert_ip = self.server.server_address[0] self.http_host = ":".join(map(str, self.server.server_address[:2])) self.http_url_base = "http://" + self.http_host self.http_url = self.http_url_base + "/status/200" HttpClientInstrumentor().instrument() RequestsInstrumentor().instrument()
def setUp(self): super().setUp() RequestsInstrumentor().instrument() httpretty.enable() httpretty.register_uri( httpretty.GET, self.URL, body="Hello!", )
def instrument_app(self) -> None: logger.info("Activating Opentelemetry tracing to app", app=self.title) trace.set_tracer_provider(tracer_provider) FastAPIInstrumentor.instrument_app(self) RequestsInstrumentor().instrument() HTTPXClientInstrumentor().instrument() RedisInstrumentor().instrument() Psycopg2Instrumentor().instrument() SQLAlchemyInstrumentor().instrument(engine=db.engine, tracer_provider=tracer_provider)
def create_app(): """Create Flask app, configure from env vars, register blueprints.""" app = Flask(__name__) app.config.update({key: os.environ[key] for key in ENV_SECRETS}) app.before_first_request(init_tracing) app.teardown_request(flush_tracing) RequestsInstrumentor().instrument() FlaskInstrumentor().instrument_app(app) oauth.init_app(app) app.register_blueprint(samplescreennames.bp) app.register_blueprint(stockprofile.bp) app.register_blueprint(twittercallback.bp) app.register_blueprint(twittercheck.bp) app.register_blueprint(twitterlink.bp) return app
def _get_tracer(jaeger_endpoint: str, service_name: str) -> trace.Tracer: set_global_textmap(B3MultiFormat()) RequestsInstrumentor().instrument() trace.set_tracer_provider( TracerProvider( resource=Resource.create({SERVICE_NAME: service_name}) ) ) jaeger_exporter = JaegerExporter( collector_endpoint=jaeger_endpoint + '?format=jaeger.thrift', ) span_processor = BatchSpanProcessor(jaeger_exporter) trace.get_tracer_provider().add_span_processor(span_processor) return trace.get_tracer(__name__)
def send_requests(): RequestsInstrumentor().instrument() configure_opentelemetry( service_name="client_service_name", service_version="client_version", # optional ) tracer = get_tracer(__name__) def request(url): with tracer.start_as_current_span("request to {}".format(url)) as span: try: get(url) except Exception as error: span.set_attribute("error", "true") span.record_exception(error) attempts = 10 for attempt in range(attempts): debug("Sending requests %s/%s", attempt + 1, attempts) with tracer.start_as_current_span("foo"): debug("Current span: %s", get_current_span()) with tracer.start_as_current_span("add-attribute") as span: span.set_attribute("attr1", "valu1") debug("Current span: %s", get_current_span()) debug( "Baggage: %s", get_baggage("example", set_baggage("example", "value")), ) with tracer.start_as_current_span("bar"): debug("Hello, server!") request("http://localhost:8000/hello") request("http://doesnotexist:8000") sleep(1) request("http://localhost:8000/shutdown")
def test_uninstrument_session(self): session1 = requests.Session() RequestsInstrumentor().uninstrument_session(session1) result = self.perform_request(self.URL, session1) self.assertEqual(result.text, "Hello!") self.assert_span(num_spans=0) # Test that other sessions as well as global requests is still # instrumented session2 = requests.Session() result = self.perform_request(self.URL, session2) self.assertEqual(result.text, "Hello!") self.assert_span() self.memory_exporter.clear() result = self.perform_request(self.URL) self.assertEqual(result.text, "Hello!") self.assert_span()
def setUp(self): super().setUp() self.env_patch = mock.patch.dict( "os.environ", { "OTEL_PYTHON_REQUESTS_EXCLUDED_URLS": "http://localhost/env_excluded_arg/123,env_excluded_noarg" }, ) self.env_patch.start() self.exclude_patch = mock.patch( "opentelemetry.instrumentation.requests._excluded_urls_from_env", get_excluded_urls("REQUESTS"), ) self.exclude_patch.start() RequestsInstrumentor().instrument() httpretty.enable() httpretty.register_uri(httpretty.GET, self.URL, body="Hello!")
def test_uninstrument_session(self): session1 = requests.Session() RequestsInstrumentor().uninstrument_session(session1) result = session1.get(self.URL) self.assertEqual(result.text, "Hello!") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) # Test that other sessions as well as global requests is still # instrumented session2 = requests.Session() result = session2.get(self.URL) self.assertEqual(result.text, "Hello!") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.memory_exporter.clear() result = requests.get(self.URL) self.assertEqual(result.text, "Hello!") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1)
def __init__(self, app, service='atlas-api', sqlalchemy_engine=None, datadog_agent=None, span_callback=None, ignored_paths: List[str] = None, sql_service=None): self.app = app trace.set_tracer_provider(TracerProvider()) self.tracer = trace.get_tracer(__name__) if datadog_agent: from ddtrace.internal.writer import AgentWriter from opentelemetry.exporter.datadog import DatadogExportSpanProcessor, \ DatadogSpanExporter exporter = DatadogSpanExporter(agent_url=datadog_agent, service=service) exporter._agent_writer = AgentWriter(datadog_agent) span_processor = DatadogExportSpanProcessor(exporter) trace.get_tracer_provider().add_span_processor(span_processor) AtlasFastAPIInstrumentor.instrument_app(app, span_callback=span_callback, ignored_paths=ignored_paths) RequestsInstrumentor().instrument() BotocoreInstrumentor().instrument(tracer_provider=trace.get_tracer_provider()) BotoInstrumentor().instrument(tracer_provider=trace.get_tracer_provider()) RedisInstrumentor().instrument(tracer_provider=trace.get_tracer_provider()) if sqlalchemy_engine: sqlalch_service_name = service if not sql_service else sql_service SQLAlchemyInstrumentor().instrument(engine=sqlalchemy_engine, service=sqlalch_service_name)
def tearDown(self): super().tearDown() RequestsInstrumentor().uninstrument() httpretty.disable()
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. # pylint: disable=import-error # pylint: disable=no-member # pylint: disable=no-name-in-module import os import requests from opentelemetry import trace from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor from microsoft.opentelemetry.exporter.azuremonitor import AzureMonitorTraceExporter trace.set_tracer_provider(TracerProvider()) RequestsInstrumentor().instrument() span_processor = SimpleExportSpanProcessor( AzureMonitorTraceExporter( connection_string=os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"])) trace.get_tracer_provider().add_span_processor(span_processor) tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("parent"): response = requests.get("https://azure.microsoft.com/", timeout=5) input("Press any key to exit...")
def create_app(): """Flask application factory to create instances of the Frontend Flask App """ app = Flask(__name__) # Disabling unused-variable for lines with route decorated functions # as pylint thinks they are unused # pylint: disable=unused-variable @app.route('/version', methods=['GET']) def version(): """ Service version endpoint """ return os.environ.get('VERSION'), 200 @app.route('/ready', methods=['GET']) def readiness(): """ Readiness probe """ return 'ok', 200 @app.route("/") def root(): """ Renders home page or login page, depending on authentication status. """ token = request.cookies.get(app.config['TOKEN_NAME']) if not verify_token(token): return login_page() return home() @app.route("/home") def home(): """ Renders home page. Redirects to /login if token is not valid """ token = request.cookies.get(app.config['TOKEN_NAME']) if not verify_token(token): # user isn't authenticated app.logger.debug( 'User isn\'t authenticated. Redirecting to login page.') return redirect( url_for('login_page', _external=True, _scheme=app.config['SCHEME'])) token_data = jwt.decode(token, verify=False) display_name = token_data['name'] username = token_data['user'] account_id = token_data['acct'] hed = {'Authorization': 'Bearer ' + token} # get balance balance = None try: url = '{}/{}'.format(app.config["BALANCES_URI"], account_id) app.logger.debug('Getting account balance.') response = requests.get(url=url, headers=hed, timeout=app.config['BACKEND_TIMEOUT']) if response: balance = response.json() except (requests.exceptions.RequestException, ValueError) as err: app.logger.error('Error getting account balance: %s', str(err)) # get history transaction_list = None try: url = '{}/{}'.format(app.config["HISTORY_URI"], account_id) app.logger.debug('Getting transaction history.') response = requests.get(url=url, headers=hed, timeout=app.config['BACKEND_TIMEOUT']) if response: transaction_list = response.json() except (requests.exceptions.RequestException, ValueError) as err: app.logger.error('Error getting transaction history: %s', str(err)) # get contacts contacts = [] try: url = '{}/{}'.format(app.config["CONTACTS_URI"], username) app.logger.debug('Getting contacts.') response = requests.get(url=url, headers=hed, timeout=app.config['BACKEND_TIMEOUT']) if response: contacts = response.json() except (requests.exceptions.RequestException, ValueError) as err: app.logger.error('Error getting contacts: %s', str(err)) _populate_contact_labels(account_id, transaction_list, contacts) return render_template('index.html', cymbal_logo=os.getenv('CYMBAL_LOGO', 'false'), history=transaction_list, balance=balance, name=display_name, account_id=account_id, contacts=contacts, message=request.args.get('msg', None), bank_name=os.getenv('BANK_NAME', 'Bank of Anthos')) def _populate_contact_labels(account_id, transactions, contacts): """ Populate contact labels for the passed transactions. Side effect: Take each transaction and set the 'accountLabel' field with the label of the contact each transaction was associated with. If there was no associated contact, set 'accountLabel' to None. If any parameter is None, nothing happens. Params: account_id - the account id for the user owning the transaction list transactions - a list of transactions as key/value dicts [{transaction1}, {transaction2}, ...] contacts - a list of contacts as key/value dicts [{contact1}, {contact2}, ...] """ app.logger.debug('Populating contact labels.') if account_id is None or transactions is None or contacts is None: return # Map contact accounts to their labels. If no label found, default to None. contact_map = {c['account_num']: c.get('label') for c in contacts} # Populate the 'accountLabel' field. If no match found, default to None. for trans in transactions: if trans['toAccountNum'] == account_id: trans['accountLabel'] = contact_map.get( trans['fromAccountNum']) elif trans['fromAccountNum'] == account_id: trans['accountLabel'] = contact_map.get(trans['toAccountNum']) @app.route('/payment', methods=['POST']) def payment(): """ Submits payment request to ledgerwriter service Fails if: - token is not valid - basic validation checks fail - response code from ledgerwriter is not 201 """ token = request.cookies.get(app.config['TOKEN_NAME']) if not verify_token(token): # user isn't authenticated app.logger.error( 'Error submitting payment: user is not authenticated.') return abort(401) try: account_id = jwt.decode(token, verify=False)['acct'] recipient = request.form['account_num'] if recipient == 'add': recipient = request.form['contact_account_num'] label = request.form.get('contact_label', None) if label: # new contact. Add to contacts list _add_contact(label, recipient, app.config['LOCAL_ROUTING'], False) transaction_data = { "fromAccountNum": account_id, "fromRoutingNum": app.config['LOCAL_ROUTING'], "toAccountNum": recipient, "toRoutingNum": app.config['LOCAL_ROUTING'], "amount": int(Decimal(request.form['amount']) * 100), "uuid": request.form['uuid'] } _submit_transaction(transaction_data) app.logger.info('Payment initiated successfully.') return redirect( url_for('home', msg='Payment successful', _external=True, _scheme=app.config['SCHEME'])) except requests.exceptions.RequestException as err: app.logger.error('Error submitting payment: %s', str(err)) except UserWarning as warn: app.logger.error('Error submitting payment: %s', str(warn)) msg = 'Payment failed: {}'.format(str(warn)) return redirect( url_for('home', msg=msg, _external=True, _scheme=app.config['SCHEME'])) return redirect( url_for('home', msg='Payment failed', _external=True, _scheme=app.config['SCHEME'])) @app.route('/deposit', methods=['POST']) def deposit(): """ Submits deposit request to ledgerwriter service Fails if: - token is not valid - routing number == local routing number - response code from ledgerwriter is not 201 """ token = request.cookies.get(app.config['TOKEN_NAME']) if not verify_token(token): # user isn't authenticated app.logger.error( 'Error submitting deposit: user is not authenticated.') return abort(401) try: # get account id from token account_id = jwt.decode(token, verify=False)['acct'] if request.form['account'] == 'add': external_account_num = request.form['external_account_num'] external_routing_num = request.form['external_routing_num'] if external_routing_num == app.config['LOCAL_ROUTING']: raise UserWarning("invalid routing number") external_label = request.form.get('external_label', None) if external_label: # new contact. Add to contacts list _add_contact(external_label, external_account_num, external_routing_num, True) else: account_details = json.loads(request.form['account']) external_account_num = account_details['account_num'] external_routing_num = account_details['routing_num'] transaction_data = { "fromAccountNum": external_account_num, "fromRoutingNum": external_routing_num, "toAccountNum": account_id, "toRoutingNum": app.config['LOCAL_ROUTING'], "amount": int(Decimal(request.form['amount']) * 100), "uuid": request.form['uuid'] } _submit_transaction(transaction_data) app.logger.info('Deposit submitted successfully.') return redirect( url_for('home', msg='Deposit successful', _external=True, _scheme=app.config['SCHEME'])) except requests.exceptions.RequestException as err: app.logger.error('Error submitting deposit: %s', str(err)) except UserWarning as warn: app.logger.error('Error submitting deposit: %s', str(warn)) msg = 'Deposit failed: {}'.format(str(warn)) return redirect( url_for('home', msg=msg, _external=True, _scheme=app.config['SCHEME'])) return redirect( url_for('home', msg='Deposit failed', _external=True, _scheme=app.config['SCHEME'])) def _submit_transaction(transaction_data): app.logger.debug('Submitting transaction.') token = request.cookies.get(app.config['TOKEN_NAME']) hed = { 'Authorization': 'Bearer ' + token, 'content-type': 'application/json' } resp = requests.post(url=app.config["TRANSACTIONS_URI"], data=jsonify(transaction_data).data, headers=hed, timeout=app.config['BACKEND_TIMEOUT']) try: resp.raise_for_status() # Raise on HTTP Status code 4XX or 5XX except requests.exceptions.HTTPError: raise UserWarning(resp.text) def _add_contact(label, acct_num, routing_num, is_external_acct=False): """ Submits a new contact to the contact service. Raise: UserWarning if the response status is 4xx or 5xx. """ app.logger.debug('Adding new contact.') token = request.cookies.get(app.config['TOKEN_NAME']) hed = { 'Authorization': 'Bearer ' + token, 'content-type': 'application/json' } contact_data = { 'label': label, 'account_num': acct_num, 'routing_num': routing_num, 'is_external': is_external_acct } token_data = jwt.decode(token, verify=False) url = '{}/{}'.format(app.config["CONTACTS_URI"], token_data['user']) resp = requests.post(url=url, data=jsonify(contact_data).data, headers=hed, timeout=app.config['BACKEND_TIMEOUT']) try: resp.raise_for_status() # Raise on HTTP Status code 4XX or 5XX except requests.exceptions.HTTPError: raise UserWarning(resp.text) @app.route("/login", methods=['GET']) def login_page(): """ Renders login page. Redirects to /home if user already has a valid token """ token = request.cookies.get(app.config['TOKEN_NAME']) if verify_token(token): # already authenticated app.logger.debug( 'User already authenticated. Redirecting to /home') return redirect( url_for('home', _external=True, _scheme=app.config['SCHEME'])) return render_template('login.html', cymbal_logo=os.getenv('CYMBAL_LOGO', 'false'), message=request.args.get('msg', None), default_user=os.getenv('DEFAULT_USERNAME', ''), default_password=os.getenv( 'DEFAULT_PASSWORD', ''), bank_name=os.getenv('BANK_NAME', 'Bank of Anthos')) @app.route('/login', methods=['POST']) def login(): """ Submits login request to userservice and saves resulting token Fails if userservice does not accept input username and password """ return _login_helper(request.form['username'], request.form['password']) def _login_helper(username, password): try: app.logger.debug('Logging in.') req = requests.get(url=app.config["LOGIN_URI"], params={ 'username': username, 'password': password }) req.raise_for_status() # Raise on HTTP Status code 4XX or 5XX # login success token = req.json()['token'].encode('utf-8') claims = jwt.decode(token, verify=False) max_age = claims['exp'] - claims['iat'] resp = make_response( redirect( url_for('home', _external=True, _scheme=app.config['SCHEME']))) resp.set_cookie(app.config['TOKEN_NAME'], token, max_age=max_age) app.logger.info('Successfully logged in.') return resp except (RequestException, HTTPError) as err: app.logger.error('Error logging in: %s', str(err)) return redirect( url_for('login', msg='Login Failed', _external=True, _scheme=app.config['SCHEME'])) @app.route("/signup", methods=['GET']) def signup_page(): """ Renders signup page. Redirects to /login if token is not valid """ token = request.cookies.get(app.config['TOKEN_NAME']) if verify_token(token): # already authenticated app.logger.debug( 'User already authenticated. Redirecting to /home') return redirect( url_for('home', _external=True, _scheme=app.config['SCHEME'])) return render_template('signup.html', cymbal_logo=os.getenv('CYMBAL_LOGO', 'false'), bank_name=os.getenv('BANK_NAME', 'Bank of Anthos')) @app.route("/signup", methods=['POST']) def signup(): """ Submits signup request to userservice Fails if userservice does not accept input form data """ try: # create user app.logger.debug('Creating new user.') resp = requests.post(url=app.config["USERSERVICE_URI"], data=request.form, timeout=app.config['BACKEND_TIMEOUT']) if resp.status_code == 201: # user created. Attempt login app.logger.info('New user created.') return _login_helper(request.form['username'], request.form['password']) except requests.exceptions.RequestException as err: app.logger.error('Error creating new user: %s', str(err)) return redirect( url_for('login', msg='Error: Account creation failed', _external=True, _scheme=app.config['SCHEME'])) @app.route('/logout', methods=['POST']) def logout(): """ Logs out user by deleting token cookie and redirecting to login page """ app.logger.info('Logging out.') resp = make_response( redirect( url_for('login_page', _external=True, _scheme=app.config['SCHEME']))) resp.delete_cookie(app.config['TOKEN_NAME']) return resp def verify_token(token): """ Validates token using userservice public key """ app.logger.debug('Verifying token.') if token is None: return False try: jwt.decode(token, key=app.config['PUBLIC_KEY'], algorithms='RS256', verify=True) app.logger.debug('Token verified.') return True except jwt.exceptions.InvalidTokenError as err: app.logger.error('Error validating token: %s', str(err)) return False # register html template formatters def format_timestamp_day(timestamp): """ Format the input timestamp day in a human readable way """ # TODO: time zones? date = datetime.datetime.strptime(timestamp, app.config['TIMESTAMP_FORMAT']) return date.strftime('%d') def format_timestamp_month(timestamp): """ Format the input timestamp month in a human readable way """ # TODO: time zones? date = datetime.datetime.strptime(timestamp, app.config['TIMESTAMP_FORMAT']) return date.strftime('%b') def format_currency(int_amount): """ Format the input currency in a human readable way """ if int_amount is None: return '$---' amount_str = '${:0,.2f}'.format(abs(Decimal(int_amount) / 100)) if int_amount < 0: amount_str = '-' + amount_str return amount_str # set up global variables app.config["TRANSACTIONS_URI"] = 'http://{}/transactions'.format( os.environ.get('TRANSACTIONS_API_ADDR')) app.config["USERSERVICE_URI"] = 'http://{}/users'.format( os.environ.get('USERSERVICE_API_ADDR')) app.config["BALANCES_URI"] = 'http://{}/balances'.format( os.environ.get('BALANCES_API_ADDR')) app.config["HISTORY_URI"] = 'http://{}/transactions'.format( os.environ.get('HISTORY_API_ADDR')) app.config["LOGIN_URI"] = 'http://{}/login'.format( os.environ.get('USERSERVICE_API_ADDR')) app.config["CONTACTS_URI"] = 'http://{}/contacts'.format( os.environ.get('CONTACTS_API_ADDR')) app.config['PUBLIC_KEY'] = open(os.environ.get('PUB_KEY_PATH'), 'r').read() app.config['LOCAL_ROUTING'] = os.getenv('LOCAL_ROUTING_NUM') app.config[ 'BACKEND_TIMEOUT'] = 4 # timeout in seconds for calls to the backend app.config['TOKEN_NAME'] = 'token' app.config['TIMESTAMP_FORMAT'] = '%Y-%m-%dT%H:%M:%S.%f%z' app.config['SCHEME'] = os.environ.get('SCHEME', 'http') # register formater functions app.jinja_env.globals.update(format_currency=format_currency) app.jinja_env.globals.update(format_timestamp_month=format_timestamp_month) app.jinja_env.globals.update(format_timestamp_day=format_timestamp_day) # Set up logging app.logger.handlers = logging.getLogger('gunicorn.error').handlers app.logger.setLevel(logging.getLogger('gunicorn.error').level) app.logger.info('Starting frontend service.') # Set up tracing and export spans to Cloud Trace. if os.environ['ENABLE_TRACING'] == "true": app.logger.info("✅ Tracing enabled.") trace.set_tracer_provider(TracerProvider()) cloud_trace_exporter = CloudTraceSpanExporter() trace.get_tracer_provider().add_span_processor( BatchExportSpanProcessor(cloud_trace_exporter)) set_global_textmap(CloudTraceFormatPropagator()) # Add tracing auto-instrumentation for Flask, jinja and requests FlaskInstrumentor().instrument_app(app) RequestsInstrumentor().instrument() Jinja2Instrumentor().instrument() else: app.logger.info("🚫 Tracing disabled.") return app