diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..8b84e389adfc97cfeba38ca6ad6c68f7e986438d
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+ignore = E402,W503
+
diff --git a/config.py.example b/config.py.example
index 581ca2258cc1f74ba69b02a9fdcafc4f7e33565d..5c4cd1502fe4f3799bffb79448207999fd7f031d 100644
--- a/config.py.example
+++ b/config.py.example
@@ -25,6 +25,9 @@ CELERY_BROKER_URL = "redis://localhost:6379/0" # change this if you do not use r
 CELERY_TASK_SERIALIZER = "pickle" # do not change
 CELERY_ACCEPT_CONTENT = ["pickle"] # do not change
 
+# Send exceptions to sentry (optional)
+# SENTRY_DSN = "https://********:********@sentry.example.com//1"
+
 # CUPS printserver (optional)
 PRINTING_ACTIVE = True
 PRINTING_SERVER = "printsrv.example.com:631"
diff --git a/requirements.txt b/requirements.txt
index a5143e52aaaae6b419859f9a008800d03e3121a7..3e871ca7560f6c9a1731e611d334dbafd71e9afb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -17,6 +17,7 @@ curtsies==0.3.0
 enum-compat==0.0.2
 eventlet==0.22.1
 feedgen==0.6.1
+flake8==3.5.0
 Flask==0.12.2
 Flask-Migrate==2.1.1
 Flask-Script==2.0.6
@@ -34,11 +35,14 @@ ldap3==2.4.1
 lxml==4.1.1
 Mako==1.0.7
 MarkupSafe==1.0
+mccabe==0.6.1
 nose==1.3.7
 packaging==16.8
 pathtools==0.1.2
 psycopg2==2.7.4
 pyasn1==0.4.2
+pycodestyle==2.3.1
+pyflakes==1.6.0
 Pygments==2.2.0
 pyldap==2.4.45
 pyparsing==2.2.0
@@ -50,6 +54,7 @@ python-pam==1.8.2
 python-socketio==1.8.4
 pytz==2018.3
 PyYAML==3.12
+raven==6.6.0
 redis==2.10.6
 regex==2018.2.8
 requests==2.18.4
diff --git a/server.py b/server.py
index e7010e132cf2cad3a604c882abe1ab5c95646f7d..8c10bcb9193a4631dd57f54afafc716a05ad8cba 100755
--- a/server.py
+++ b/server.py
@@ -31,7 +31,7 @@ from shared import (
 from utils import (
     get_first_unused_int, get_etherpad_text, split_terms, optional_int_arg,
     fancy_join, footnote_hash, get_git_revision, get_max_page_length_exp,
-    get_internal_filename, get_csrf_token)
+    get_internal_filename, get_csrf_token, get_current_ip)
 from decorators import (
     db_lookup, protect_csrf,
     require_private_view_right, require_modify_right, require_publish_right,
@@ -64,10 +64,37 @@ migrate = Migrate(app, db)
 manager = Manager(app)
 manager.add_command("db", MigrateCommand)
 
+try:
+    from raven.contrib.flask import Sentry
+    sentry = Sentry(app, dsn=config.SENTRY_DSN)
+
+    def get_user_info(request):
+        return {
+            "is_authenticated": check_login(),
+            "ip_address": get_current_ip(),
+            "release": get_git_revision(),
+        }
+    sentry.get_user_info = get_user_info
+except ModuleNotFoundError:
+    print("Raven not installed. Not sending issues to Sentry.")
+except AttributeError:
+    print("DSN not configured. Not sending issues to Sentry.")
+
 
 def make_celery(app, config):
     celery = Celery(app.import_name, broker=config.CELERY_BROKER_URL)
     celery.conf.update(app.config)
+    try:
+        from raven import Client as RavenClient
+        from raven.contrib.celery import (
+            register_signal, register_logger_signal)
+        raven_client = RavenClient(config.SENTRY_DSN)
+        register_logger_signal(raven_client)
+        register_signal(raven_client)
+    except ModuleNotFoundError:
+        print("Raven not installed. Not sending celery issues to Sentry.")
+    except AttributeError:
+        print("DSN not configured. Not sending celery issues to Sentry.")
     return celery
 
 
diff --git a/utils.py b/utils.py
index 5e80b9cbebc40ae8cacf6cff9201fa12c1577e17..116a2f157c623617bc501c5fb089c35bdf54fde2 100644
--- a/utils.py
+++ b/utils.py
@@ -193,11 +193,16 @@ def add_line_numbers(text):
     return "\n".join(lines)
 
 
-def check_ip_in_networks(networks_string):
+def get_current_ip():
     address = ipaddress.ip_address(request.remote_addr)
     if (address == ipaddress.ip_address("127.0.0.1")
             and "X-Real-Ip" in request.headers):
         address = ipaddress.ip_address(request.headers["X-Real-Ip"])
+    return address
+
+
+def check_ip_in_networks(networks_string):
+    address = get_current_ip()
     try:
         for network_string in networks_string.split(","):
             network = ipaddress.ip_network(network_string.strip())