Skip to content

Commit

Permalink
Start using Sentry in our general stack (#143)
Browse files Browse the repository at this point in the history
* Create draft PR for #119

* fix type mismatch when queueing forecasting jobs from CLI

* add sentr SDK as dependency and use it if SENTRY_DSN is set

* add FLEXMEASURES_SENTRY_CONFIG setting

* fix typo

Co-authored-by: nhoening <nhoening@users.noreply.github.com>
Co-authored-by: Nicolas Höning <nicolas@seita.nl>
  • Loading branch information
3 people committed Jun 19, 2021
1 parent 8ea6a45 commit 24cd1ef
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 29 deletions.
2 changes: 2 additions & 0 deletions documentation/changelog.rst
Expand Up @@ -14,6 +14,8 @@ Bugfixes
Infrastructure / Support
----------------------

* Add possibility to send errors to Sentry [see `PR #143 <http://www.github.com/SeitaBV/flexmeasures/pull/143>`_]


v0.5.0 | June 7, 2021
===========================
Expand Down
48 changes: 38 additions & 10 deletions documentation/configuration.rst
Expand Up @@ -133,13 +133,13 @@ The horizon to use when making schedules.
Default: ``timedelta(hours=2 * 24)``


Tokens
------
Access Tokens
---------------

OPENWEATHERMAP_API_KEY
^^^^^^^^^^^^^^^^

Token for accessing the OPenWeatherMap weather forecasting service.
Token for accessing the OpenWeatherMap weather forecasting service.

Default: ``None``

Expand All @@ -152,13 +152,6 @@ Token for accessing the MapBox API (for displaying maps on the dashboard and ass

Default: ``None``

FLEXMEASURES_TASK_CHECK_AUTH_TOKEN
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Token which external services can use to check on the status of recurring tasks within FlexMeasures.

Default: ``None``


SQLAlchemy
----------
Expand Down Expand Up @@ -329,6 +322,41 @@ Password of mail system user.
Default: ``None``


.. _monitoring
Monitoring
-----------

Monitoring potential problems in FlexMeasure's operations.


SENTRY_DSN
^^^^^^^^^^^^

Set tokenized URL, so errors will be sent to Sentry when ``app.env`` is not in `debug` or `testing` mode.
E.g.: ``https://<examplePublicKey>@o<something>.ingest.sentry.io/<project-Id>``

Default: ``None``


FLEXMEASURES_SENTRY_CONFIG
^^^^^^^^^^^^^^^^^^^^^^^^^^^

A dictionary with values to configure reporting to Sentry. Some options are taken care of by FlexMeasures (e.g. environment and release), but not all.
See `here <https://docs.sentry.io/platforms/python/configuration/options/>_` for a complete list.

Default: ``{}``


FLEXMEASURES_TASK_CHECK_AUTH_TOKEN
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Token which external services can use to check on the status of recurring tasks within FlexMeasures.

Default: ``None``



.. _redis-config:

Redis
Expand Down
15 changes: 9 additions & 6 deletions flexmeasures/app.py
Expand Up @@ -26,7 +26,7 @@ def create(env: Optional[str] = None, path_to_config: Optional[str] = None) -> F

from flexmeasures.utils import config_defaults
from flexmeasures.utils.config_utils import read_config, configure_logging
from flexmeasures.utils.app_utils import set_secret_key
from flexmeasures.utils.app_utils import set_secret_key, init_sentry
from flexmeasures.utils.error_utils import add_basic_error_handlers

# Create app
Expand All @@ -36,17 +36,20 @@ def create(env: Optional[str] = None, path_to_config: Optional[str] = None) -> F
# as we need to know the ENV now (for it to be recognised by Flask()).
load_dotenv()
app = Flask("flexmeasures")

if env is not None: # overwrite
app.env = env
if env == "testing":
app.testing = True
if env == "development":
app.debug = config_defaults.DevelopmentConfig.DEBUG
if app.env == "testing":
app.testing = True
if app.env == "development":
app.debug = config_defaults.DevelopmentConfig.DEBUG

# App configuration

read_config(app, path_to_config=path_to_config)
read_config(app, custom_path_to_config=path_to_config)
add_basic_error_handlers(app)
if not app.env == "development" and not app.testing:
init_sentry(app)

app.mail = Mail(app)
FlaskJSON(app)
Expand Down
4 changes: 2 additions & 2 deletions flexmeasures/data/scripts/cli_tasks/data_add.py
Expand Up @@ -481,8 +481,8 @@ def create_forecasts(
asset_id=asset_id,
timed_value_type=value_type,
horizons=[horizon],
start_of_roll=from_date - timedelta(hours=horizon),
end_of_roll=to_date - timedelta(hours=horizon),
start_of_roll=from_date - horizon,
end_of_roll=to_date - horizon,
)
else:
from flexmeasures.data.scripts.data_gen import populate_time_series_forecasts
Expand Down
40 changes: 38 additions & 2 deletions flexmeasures/utils/app_utils.py
Expand Up @@ -4,17 +4,52 @@

import click
from flask import Flask
from flask.cli import FlaskGroup
from flask.cli import FlaskGroup, with_appcontext
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.rq import RqIntegration
from pkg_resources import get_distribution

from flexmeasures.app import create as create_app


@click.group(cls=FlaskGroup, create_app=create_app)
@with_appcontext
def flexmeasures_cli():
"""Management scripts for the FlexMeasures platform."""
"""
Management scripts for the FlexMeasures platform.
We use @app_context here so things from the app setup are initialised
only once. This is crucial for Sentry, for example.
"""
pass


def init_sentry(app: Flask):
"""
Configure Sentry.
We need the app to read the Sentry DSN from configuration, and also
to send some additional meta information.
"""
sentry_dsn = app.config.get("SENTRY_DSN")
if not sentry_dsn:
app.logger.info(
"[FLEXMEASURES] No SENTRY_DSN setting found, so initialising Sentry cannot happen ..."
)
return
app.logger.info("[FLEXMEASURES] Initialising Sentry ...")
sentry_sdk.init(
dsn=sentry_dsn,
integrations=[FlaskIntegration(), RqIntegration()],
debug=app.debug,
release=f"flexmeasures@{get_distribution('flexmeasures').version}",
send_default_pii=True, # user data (current user id, email address, username) is attached to the event.
environment=app.env,
**app.config["FLEXMEASURES_SENTRY_CONFIG"],
)
sentry_sdk.set_tag("mode", app.config.get("FLEXMEASURES_MODE"))
sentry_sdk.set_tag("platform-name", app.config.get("FLEXMEASURES_PLATFORM_NAME"))


def set_secret_key(app, filename="secret_key"):
"""Set the SECRET_KEY or exit.
Expand Down Expand Up @@ -104,3 +139,4 @@ def register_plugins(app: Flask):
plugin_version = getattr(plugin_blueprint, "__version__", "0.1")
app.config["LOADED_PLUGINS"][plugin_name] = plugin_version
app.logger.info(f"Loaded plugins: {app.config['LOADED_PLUGINS']}")
sentry_sdk.set_context("plugins", app.config.get("LOADED_PLUGINS", {}))
6 changes: 6 additions & 0 deletions flexmeasures/utils/config_defaults.py
Expand Up @@ -75,6 +75,12 @@ class Config(object):
3000 # Web interface poll period for updates in ms
)

SENTRY_DSN: Optional[str] = None
# Place additional Sentry config here.
# traces_sample_rate is for performance monitoring across all transactions,
# you probably want to adjust this.
FLEXMEASURES_SENTRY_CONFIG: dict = dict(traces_sample_rate=0.33)

FLEXMEASURES_PLATFORM_NAME: str = "FlexMeasures"
FLEXMEASURES_MODE: str = ""
FLEXMEASURES_TIMEZONE: str = "Asia/Seoul"
Expand Down
15 changes: 8 additions & 7 deletions flexmeasures/utils/config_utils.py
Expand Up @@ -50,7 +50,7 @@ def configure_logging():
loggingDictConfig(flexmeasures_logging_config)


def read_config(app: Flask, path_to_config: Optional[str]):
def read_config(app: Flask, custom_path_to_config: Optional[str]):
"""Read configuration from various expected sources, complain if not setup correctly. """

if app.env not in (
Expand All @@ -65,21 +65,22 @@ def read_config(app: Flask, path_to_config: Optional[str]):
)
sys.exit(2)

# Load default config settings
# First, load default config settings
app.config.from_object(
"flexmeasures.utils.config_defaults.%sConfig" % camelize(app.env)
)

# Now read user config, if possible. If no explicit path is given, try home dir first, then instance dir
# Now, potentially overwrite those from config file
# These two locations are possible (besides the custom path)
path_to_config_home = str(Path.home().joinpath(".flexmeasures.cfg"))
path_to_config_instance = os.path.join(app.instance_path, "flexmeasures.cfg")
if not app.testing:
if not app.testing: # testing runs completely on defaults
# If no custom path is given, this will try home dir first, then instance dir
used_path_to_config = read_custom_config(
app, path_to_config, path_to_config_home, path_to_config_instance
app, custom_path_to_config, path_to_config_home, path_to_config_instance
)

# Check for missing values.
# Testing might affect only specific functionality (-> dev's responsibility)
# Documentation runs fine without them.
if not app.testing and app.env != "documentation":
if not are_required_settings_complete(app):
Expand Down Expand Up @@ -126,7 +127,7 @@ def read_custom_config(
app.config.from_pyfile(path_to_config)
except FileNotFoundError:
pass
# Finally, all required varaiables can be set as env var:
# Finally, all required variables can be set as env var:
for req_var in required:
app.config[req_var] = os.getenv(req_var, app.config.get(req_var, None))
return path_to_config
Expand Down
2 changes: 1 addition & 1 deletion flexmeasures/utils/error_utils.py
Expand Up @@ -23,7 +23,7 @@ def log_error(exc: Exception, error_msg: str):
extra = dict(url=request.path, **get_err_source_info(last_traceback))

msg = (
'{error_name}:"{message}" [occured at {src_module}({src_func}):{src_linenr},'
'{error_name}:"{message}" [occurred at {src_module}({src_func}):{src_linenr},'
"URL was: {url}]".format(
error_name=exc.__class__.__name__, message=error_msg, **extra
)
Expand Down
1 change: 1 addition & 0 deletions requirements/app.in
Expand Up @@ -48,6 +48,7 @@ Flask-Security-Too>=4.0
Flask-Classful
Flask-Marshmallow
Flask-Cors
sentry-sdk[flask]
marshmallow-sqlalchemy>=0.23.1
webargs
# flask should be after all the flask plugins, because setup might find they ARE flask
Expand Down
9 changes: 8 additions & 1 deletion requirements/app.txt
Expand Up @@ -22,12 +22,15 @@ blinker==1.4
# via
# flask-mail
# flask-principal
# sentry-sdk
bokeh==1.0.4
# via
# -r requirements/app.in
# pandas-bokeh
certifi==2020.12.5
# via requests
# via
# requests
# sentry-sdk
cffi==1.14.5
# via bcrypt
cftime==1.4.1
Expand Down Expand Up @@ -99,6 +102,7 @@ flask==1.1.2
# flask-sslify
# flask-wtf
# rq-dashboard
# sentry-sdk
greenlet==1.0.0
# via sqlalchemy
humanize==3.3.0
Expand Down Expand Up @@ -280,6 +284,8 @@ scipy==1.6.2
# timetomodel
selenium==3.141.0
# via timely-beliefs
sentry-sdk[flask]==1.1.0
# via -r requirements/app.in
siphon==0.9
# via -r requirements/app.in
six==1.15.0
Expand Down Expand Up @@ -331,6 +337,7 @@ urllib3==1.26.4
# via
# requests
# selenium
# sentry-sdk
webargs==7.0.1
# via -r requirements/app.in
werkzeug==1.0.1
Expand Down

0 comments on commit 24cd1ef

Please sign in to comment.