diff --git a/.coveragerc b/.coveragerc
index 321e5ca09d0fec5a6ef31f33007b48e5b9491a93..3572e84b192e9b8071ba09f33e86ea4c61af093e 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,2 +1,3 @@
 [run]
+plugins = Cython.Coverage
 omit = */tests/*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9aed78e9c36a1e3bdde697d46e6bed4e06d95971..4e43f3ed9947bedf5ecd2623b02368ab7754ffc6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,6 @@
 job:
   script:
-    - python3 setup.py build
-    - python3 setup.py build_ext -i
+    - python3 setup.py build --cython-trace
+    - python3 setup.py build_ext --cython-trace -i
     - make -C doc clean && make -C doc html
     - py.test --cov=kwant --flakes kwant
diff --git a/setup.py b/setup.py
index 196030ccbf574799c92a39357f67913b6619583d..b5799ee084f53a8bfe92b82a37136ac76c4af94b 100755
--- a/setup.py
+++ b/setup.py
@@ -48,6 +48,7 @@ README_END_BEFORE = 'See also in this directory:'
 STATIC_VERSION_PATH = ('kwant', '_kwant_version.py')
 REQUIRED_CYTHON_VERSION = (0, 22)
 CYTHON_OPTION = '--cython'
+CYTHON_TRACE_OPTION = '--cython-trace'
 TUT_DIR = 'tutorial'
 TUT_GLOB = 'doc/source/tutorial/*.py'
 TUT_HIDDEN_PREFIX = '#HIDDEN'
@@ -80,6 +81,16 @@ try:
 except ValueError:
     use_cython = version_is_from_git
 
+try:
+    sys.argv.remove(CYTHON_TRACE_OPTION)
+    trace_cython = True
+    if not use_cython:
+        print('error: --cython-trace provided, but cython will not be run',
+              file=sys.stderr)
+        exit(1)
+except ValueError:
+    trace_cython = False
+
 if use_cython:
     try:
         import Cython
@@ -312,6 +323,13 @@ def extensions():
                       'kwant/graph/c_slicer/partitioner.h',
                       'kwant/graph/c_slicer/slicer.h']})]
 
+    #### Add cython tracing macro
+    if trace_cython:
+        for args, kwargs in result:
+            macros = kwargs.get('define_macros', [])
+            macros.append(('CYTHON_TRACE', '1'))
+            kwargs['define_macros'] = macros
+
     #### Add components of Kwant with external compile-time dependencies.
     config = configparser.ConfigParser()
     try:
@@ -383,7 +401,9 @@ def ext_modules(extensions):
     """
     if use_cython and cython_version >= REQUIRED_CYTHON_VERSION:
         return cythonize([Extension(*args, **kwrds)
-                          for args, kwrds in extensions], language_level=3)
+                          for args, kwrds in extensions], language_level=3,
+                         compiler_directives={'linetrace': trace_cython}
+                        )
 
     # Cython is not going to be run: replace pyx extension by that of
     # the shipped translated file.