Skip to content

Commit 27440fb

Browse files
authored
ENH: Added support for debugging internal PROJ (#696)
1 parent a894ba3 commit 27440fb

File tree

5 files changed

+112
-3
lines changed

5 files changed

+112
-3
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ matrix:
3030
- python: 3.6
3131
env:
3232
- PROJ_NETWORK=ON
33+
- PROJ_DEBUG=3
3334
- python: 3.6
3435
env:
3536
- PROJSYNC=ALL

docs/advanced_examples.rst

+39
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,42 @@ Here is an example where enabling the global context can help:
180180
181181
codes = pyproj.get_codes("EPSG", pyproj.enums.PJType.PROJECTED_CRS, False)
182182
crs_list = [pyproj.CRS.from_epsg(code) for code in codes]
183+
184+
185+
Debugging Internal PROJ:
186+
------------------------
187+
188+
.. versionadded:: 3.0.0
189+
190+
To get more debugging information from the internal PROJ code:
191+
192+
1. Set the `PROJ_DEBUG <https://proj.org/usage/environmentvars.html#envvar-PROJ_DEBUG>`__
193+
environment variable to the desired level.
194+
195+
2. Activate logging in `pyproj` with the devel `DEBUG`:
196+
197+
More information available here: https://docs.python.org/3/howto/logging.html
198+
199+
Here are examples to get started.
200+
201+
Add handler to the `pyproj` logger:
202+
203+
.. code-block:: python
204+
205+
import logging
206+
207+
console_handler = logging.StreamHandler()
208+
formatter = logging.Formatter("%(levelname)s:%(message)s")
209+
console_handler.setFormatter(formatter)
210+
logger = logging.getLogger("pyproj")
211+
logger.addHandler(console_handler)
212+
logger.setLevel(logging.DEBUG)
213+
214+
215+
Activate default logging config:
216+
217+
.. code-block:: python
218+
219+
import logging
220+
221+
logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG)

docs/history.rst

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Change Log
2323
* ENH: Add support for temporal CRS CF coordinate system (issue #672)
2424
* BUG: Fix handling of polygon holes when calculating area in Geod (pull #686)
2525
* ENH: Added :ref:`network` (#675, #691, #695)
26+
* ENH: Added support for debugging internal PROJ (pull #696)
2627

2728
2.6.1
2829
~~~~~

pyproj/_datadir.pyx

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23
import warnings
34
from distutils.util import strtobool
@@ -7,6 +8,10 @@ from libc.stdlib cimport free, malloc
78
from pyproj.compat import cstrencode, pystrdecode
89
from pyproj.exceptions import DataDirError, ProjError
910

11+
# for logging the internal PROJ messages
12+
# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
13+
_LOGGER = logging.getLogger("pyproj")
14+
_LOGGER.addHandler(logging.NullHandler())
1015
# default to False is the safest mode
1116
# as it supports multithreading
1217
_USE_GLOBAL_CONTEXT = strtobool(os.environ.get("PYPROJ_GLOBAL_CONTEXT", "OFF"))
@@ -70,12 +75,23 @@ def get_user_data_dir(create=False):
7075
return pystrdecode(proj_context_get_user_writable_directory(NULL, bool(create)))
7176

7277

73-
cdef void pyproj_log_function(void *user_data, int level, const char *error_msg):
78+
cdef void pyproj_log_function(void *user_data, int level, const char *error_msg) nogil:
7479
"""
7580
Log function for catching PROJ errors.
7681
"""
82+
# from pyproj perspective, everything from PROJ is for debugging.
83+
# The verbosity should be managed via the
84+
# PROJ_DEBUG environment variable.
7785
if level == PJ_LOG_ERROR:
78-
ProjError.internal_proj_error = pystrdecode(error_msg)
86+
with gil:
87+
ProjError.internal_proj_error = pystrdecode(error_msg)
88+
_LOGGER.debug(f"PROJ_ERROR: {ProjError.internal_proj_error}")
89+
elif level == PJ_LOG_DEBUG:
90+
with gil:
91+
_LOGGER.debug(f"PROJ_DEBUG: {pystrdecode(error_msg)}")
92+
elif level == PJ_LOG_TRACE:
93+
with gil:
94+
_LOGGER.debug(f"PROJ_TRACE: {pystrdecode(error_msg)}")
7995

8096

8197
cdef void set_context_data_dir(PJ_CONTEXT* context) except *:

test/test_datadir.py

+53-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import logging
12
import os
23
from contextlib import contextmanager
34

45
import pytest
56
from mock import patch
67

78
import pyproj._datadir
8-
from pyproj import CRS, get_codes, set_use_global_context
9+
from pyproj import CRS, Transformer, get_codes, set_use_global_context
910
from pyproj._datadir import _pyproj_global_context_initialize
1011
from pyproj.datadir import (
1112
DataDirError,
@@ -15,6 +16,7 @@
1516
set_data_dir,
1617
)
1718
from pyproj.enums import PJType
19+
from pyproj.exceptions import CRSError
1820
from test.conftest import proj_env
1921

2022

@@ -30,6 +32,23 @@ def proj_context_env():
3032
pyproj._datadir._USE_GLOBAL_CONTEXT = context
3133

3234

35+
@contextmanager
36+
def proj_logging_env():
37+
"""
38+
Ensure handler is added and then removed at end.
39+
"""
40+
console_handler = logging.StreamHandler()
41+
formatter = logging.Formatter("%(threadName)s:%(levelname)s:%(message)s")
42+
console_handler.setFormatter(formatter)
43+
logger = logging.getLogger("pyproj")
44+
logger.addHandler(console_handler)
45+
logger.setLevel(logging.DEBUG)
46+
try:
47+
yield
48+
finally:
49+
logger.removeHandler(console_handler)
50+
51+
3352
def create_projdb(tmpdir):
3453
with open(os.path.join(tmpdir, "proj.db"), "w") as pjdb:
3554
pjdb.write("DUMMY proj.db")
@@ -227,3 +246,36 @@ def test_set_use_global_context__off():
227246
with proj_context_env():
228247
set_use_global_context(False)
229248
assert pyproj._datadir._USE_GLOBAL_CONTEXT is False
249+
250+
251+
def test_proj_debug_logging(capsys):
252+
with proj_logging_env():
253+
with pytest.warns(FutureWarning):
254+
transformer = Transformer.from_proj("+init=epsg:4326", "+init=epsg:27700")
255+
transformer.transform(100000, 100000)
256+
captured = capsys.readouterr()
257+
if os.environ.get("PROJ_DEBUG") == "3":
258+
assert "PROJ_TRACE" in captured.err
259+
assert "PROJ_DEBUG" in captured.err
260+
elif os.environ.get("PROJ_DEBUG") == "2":
261+
assert "PROJ_TRACE" not in captured.err
262+
assert "PROJ_DEBUG" in captured.err
263+
else:
264+
assert captured.err == ""
265+
266+
267+
def test_proj_debug_logging__error(capsys):
268+
with proj_logging_env(), pytest.raises(CRSError):
269+
CRS("INVALID STRING")
270+
captured = capsys.readouterr()
271+
if os.environ.get("PROJ_DEBUG") == "3":
272+
assert "PROJ_TRACE" in captured.err
273+
assert "PROJ_DEBUG" in captured.err
274+
assert "PROJ_ERROR" in captured.err
275+
elif os.environ.get("PROJ_DEBUG") == "2":
276+
assert "PROJ_TRACE" not in captured.err
277+
assert "PROJ_DEBUG" in captured.err
278+
assert "PROJ_ERROR" in captured.err
279+
else:
280+
assert captured.err == ""
281+
assert captured.out == ""

0 commit comments

Comments
 (0)