-
-
- Reach people where they are with government-powered text messages
-
-
- Notify.gov is a text messaging service that helps federal, state,
- local, tribal and territorial governments more effectively
- communicate with the people they serve.
-
-
-
-
-
-
- Government texting made easy
-
-
- Notify.gov is a text messaging platform built for government agencies.
- With minimal set-up and secure, personalized messaging, you can make
- one-way texting a part of your outreach program.
-
-
-
-
- Key features
-
-
- -
-
-
-
 }})
-
-
-
Nothing to download or install
-
-
-
- -
-
-
-
 }})
-
-
-
No technical expertise required
-
-
-
- -
-
-
-
 }})
-
-
-
See which messages were received
-
-
-
-
-
- -
-
-
-
 }})
-
-
-
Notify.gov has support for more than 30 character sets
-
-
-
- -
-
-
-
 }})
-
-
-
- Limited data retention, encryption, and multi-factor
- authentication protect user data and manage risk with
our security efforts
-
-
-
-
- -
-
-
-
 }})
-
-
-
- Send hundreds or thousands of individually customized messages
- with just a few clicks
-
-
-
-
-
-
-
-
-
+
+ Government texting made easy
+
+
+ Notify.gov is a text messaging platform built for government agencies.
+ With minimal set-up and secure, personalized messaging, you can make
+ one-way texting a part of your outreach program.
+
+
+
+ Key features
+
+
+ -
+
+
+
 }})
+
+
+
Nothing to download or install
+
+
+
+ -
+
+
+
 }})
+
+
+
No technical expertise required
+
+
+
+ -
+
+
+
 }})
+
+
+
See which messages were received
+
+
+
+
+
+ -
+
+
+
 }})
+
+
+
Notify.gov has support for more than 30 character sets
+
+
+
+ -
+
+
+
 }})
+
+
+
+ Limited data retention, encryption, and multi-factor
+ authentication protect user data and manage risk with
our security efforts
+
+
+
+
+ -
+
+
+
 }})
+
+
+
+ Send hundreds or thousands of individually customized messages
+ with just a few clicks
+
+
+
+
+
+
+
+
+
+ {% endif %}
{% endblock %}
{% endblock %}
diff --git a/app/utils/api_health.py b/app/utils/api_health.py
new file mode 100644
index 000000000..b476db2c4
--- /dev/null
+++ b/app/utils/api_health.py
@@ -0,0 +1,20 @@
+import logging
+import os
+
+import requests
+from requests.exceptions import RequestException
+
+logger = logging.getLogger(__name__)
+
+
+def is_api_down():
+ api_base_url = os.getenv("API_HOST_NAME")
+ try:
+ response = requests.get(api_base_url, timeout=2)
+ is_down = response.status_code != 200
+ if is_down:
+ logger.warning(f"API responded with status {response.status_code} at {api_base_url}")
+ return is_down
+ except RequestException as e:
+ logger.error(f"API down when loading homepage {e}")
+ return True
diff --git a/notifications_utils/logging.py b/notifications_utils/logging.py
index 99c349cdd..3e8f5c651 100644
--- a/notifications_utils/logging.py
+++ b/notifications_utils/logging.py
@@ -1,5 +1,4 @@
import logging
-import logging.handlers
import re
import sys
from itertools import product
@@ -10,6 +9,21 @@ from flask.ctx import has_app_context, has_request_context
from flask.logging import default_handler
from pythonjsonlogger.jsonlogger import JsonFormatter as BaseJSONFormatter
+
+class ColorFormatter(logging.Formatter):
+ COLOR_MAP = {
+ "WARNING": "\033[93m",
+ "ERROR": "\033[91m",
+ "CRITICAL": "\033[95m",
+ }
+ RESET = "\033[0m"
+
+ def format(self, record: logging.LogRecord) -> str:
+ color = self.COLOR_MAP.get(record.levelname, self.RESET)
+ base_message = super().format(record)
+ return f"{color}{base_message}{self.RESET}"
+
+
LOG_FORMAT = (
"%(asctime)s %(app_name)s %(name)s %(levelname)s "
'%(request_id)s %(service_id)s "%(message)s" [in %(pathname)s:%(lineno)d]'
@@ -79,7 +93,6 @@ def init_app(app):
def get_handlers(app):
handlers = []
- standard_formatter = logging.Formatter(LOG_FORMAT, TIME_FORMAT)
json_formatter = JSONFormatter(LOG_FORMAT, TIME_FORMAT)
stream_handler = logging.StreamHandler(sys.stdout)
@@ -92,9 +105,9 @@ def get_handlers(app):
return not ("GET /static/" in msg and " 200 " in msg)
logging.getLogger("werkzeug").addFilter(is_200_static_log)
+ color_formatter = ColorFormatter(LOG_FORMAT, TIME_FORMAT)
- # human readable stdout logs
- handlers.append(configure_handler(stream_handler, app, standard_formatter))
+ handlers.append(configure_handler(stream_handler, app, color_formatter))
return handlers
diff --git a/poetry.lock b/poetry.lock
index f298ef7fd..39f8fcef3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -17,6 +17,7 @@ version = "5.0.1"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
@@ -1473,6 +1474,7 @@ version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
+groups = ["main", "dev"]
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py
index 51f1a5c83..bbd2c9682 100644
--- a/tests/app/main/views/test_index.py
+++ b/tests/app/main/views/test_index.py
@@ -16,16 +16,22 @@ def test_non_logged_in_user_can_see_homepage(
client_request.logout()
page = client_request.get("main.index", _test_page_title=False)
- assert page.h1.text.strip() == (
- "Reach people where they are with government-powered text messages"
- )
+ heading = page.h1.text.strip()
+ assert heading in [
+ "Reach people where they are with government-powered text messages",
+ "There's currently a technical issue.",
+ ]
- # Assert the entire HTML of the button to include the image
button = page.select_one(
"a.usa-button.login-button.login-button--primary.margin-right-2"
)
- assert "Sign in with" in button.text.strip() # Assert button text
- assert button.find("img")["alt"] == "Login.gov logo" # Assert image presence
+
+ if heading == "There's currently a technical issue.":
+ assert button is None
+ else:
+ assert button is not None
+ assert "Sign in with" in button.text.strip()
+ assert button.find("img")["alt"] == "Login.gov logo"
assert page.select_one("meta[name=description]") is not None
assert page.select_one("#whos-using-notify a") is None