Repository: rasterio/rasterio

Issue:
Handling GDAL Errors
I can catch a rasterio IOError:

``` python
try:
    rasterio.open('foo','r')
except IOError:
    pass
```

However, I still get the GDAL Error:

```
ERROR 4: `foo' does not exist in the file system,
and is not recognised as a supported dataset name.
```

The osgeo module needed a call, `gdal.UseExceptions()`. Is something similar needed for Rasterio to catch GDAL errors?

OS: Ubuntu 14.04, Python 3.5.1, Rasterio 0.34



Discussion:
@mccarthyryanc It's likely that the "ERROR 4" text is just GDAL logs. In other words rasterio caught the GDAL error and raised it as proper python Exception but libgdal may still log some messages. Those are just informational and distinct from the Exceptions you'll deal with at the python level.

Translating gdal errors into python exceptions is what `osgeo.gdal.UseExceptions()` does, but rasterio does it in a safer way and it's always on by default.

@perrygeo is there a good way to suppress this type of logging from GDAL?

@mccarthyryanc your application's logger is highly configurable. You can filter out the 'GDAL' log stream using https://docs.python.org/2/library/logging.html#filter-objects.

This might be my ignorance of the filter class logic, but when I try:

``` python
class GDALErrorFilter(logging.Filter):
    def filter(self, record):
        print('Filtering GDAL log message')
        return not record.args[0] == 'CPLE_OpenFailed'

stream_h = logging.StreamHandler()
stream_h.addFilter(GDALErrorFilter())
logger = logging.getLogger('GDAL')
logger.addHandler(stream_h)

for item in ['foo','bar','baz']:
    try:
        src_dataset = rasterio.open(item,'r')
    except IOError:
        print("Catch for IOError:", item)
```

I only get the first log record filtered:

```
Filtering GDAL log message
Catch for IOError: foo
ERROR 4: `bar' does not exist in the file system,
and is not recognised as a supported dataset name.

Catch for IOError: bar
ERROR 4: `baz' does not exist in the file system,
and is not recognised as a supported dataset name.

Catch for IOError: baz
```

I get the same behavior if I add the filter to the logger or the handler.

@mccarthyryanc I can confirm that behavior (first log filtered, the rest don't even get evaluated as log messages). I don't have any explanation for it.

A workaround might be to just turn off GDAL error logs all together using some [Config Options](https://trac.osgeo.org/gdal/wiki/ConfigOptions) - specifically if you set `CPL_LOG=/dev/null` it will ensure that all GDAL errors get redirected to the null file handle. You can do this with an environment var as suggested on that page or using rasterio context:

```
with rasterio.drivers(CPL_LOG="/dev/null"):
```

This should silence the chatter. It doesn't solve the logging issue but it's a reasonable workaround.

@perrygeo @mccarthyryanc I'm researching and think I've found the answer. PR coming soon.


Resolving patch:
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -3,6 +3,12 @@
 
 from collections import namedtuple
 import logging
+try:
+    from logging import NullHandler
+except ImportError:
+    class NullHandler(logging.Handler):
+        def emit(self, record):
+            pass
 
 from rasterio._base import (
     eval_window, window_shape, window_index, gdal_version)
@@ -26,11 +32,12 @@
 __version__ = "0.34.0"
 __gdal_version__ = gdal_version()
 
-log = logging.getLogger('rasterio')
-class NullHandler(logging.Handler):
-    def emit(self, record):
-        pass
-log.addHandler(NullHandler())
+# Rasterio attaches NullHandler to the 'rasterio' and 'GDAL' loggers.
+# See https://docs.python.org/2/howto/logging.html#configuring-logging-for-a-library
+# Applications will need to attach their own handlers in order to
+# see messages. See rasterio/rio/main.py for an example.
+log = logging.getLogger('rasterio').addHandler(NullHandler())
+log = logging.getLogger('GDAL').addHandler(NullHandler())
 
 
 def open(
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -16,9 +16,19 @@
 
 
 def configure_logging(verbosity):
-    log_level = max(10, 30 - 10*verbosity)
+    log_level = max(10, 30 - 10 * verbosity)
     logging.basicConfig(stream=sys.stderr, level=log_level)
 
+    # Rasterio has attached the 'rasterio' and 'GDAL' loggers to
+    # NullHandler. The CLI attaches a new StreamHandler to each
+    # so log messages go to sys.stderr.
+    log = logging.getLogger('rasterio')
+    log.setLevel(log_level)
+    log.addHandler(logging.StreamHandler())
+    log = logging.getLogger('GDAL')
+    log.setLevel(log_level)
+    log.addHandler(logging.StreamHandler())
+
 
 class FakeSession(object):
     """Fake AWS Session."""


Test patch:
diff --git a/tests/test_driver_management.py b/tests/test_driver_management.py
--- a/tests/test_driver_management.py
+++ b/tests/test_driver_management.py
@@ -4,43 +4,44 @@
 import rasterio
 from rasterio._drivers import driver_count, GDALEnv
 
-logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
 
 def test_drivers():
     with rasterio.drivers() as m:
         assert driver_count() > 0
         assert type(m) == GDALEnv
-        
         n = rasterio.drivers()
         assert driver_count() > 0
         assert type(n) == GDALEnv
 
-def test_options(tmpdir):
-    """Test that setting CPL_DEBUG=True results in GDAL debug messages.
-    """
-    logger = logging.getLogger('GDAL')
-    logger.setLevel(logging.DEBUG)
-    logfile1 = str(tmpdir.join('test_options1.log'))
-    fh = logging.FileHandler(logfile1)
-    logger.addHandler(fh)
-    
-    # With CPL_DEBUG=True, expect debug messages from GDAL in
-    # logfile1
+
+def test_cpl_debug_true(tmpdir):
+    """Setting CPL_DEBUG=True results in GDAL debug messages."""
+    log = logging.getLogger('GDAL')
+    log.setLevel(logging.DEBUG)
+    logfile = str(tmpdir.join('test.log'))
+    fh = logging.FileHandler(logfile)
+    log.addHandler(fh)
+
     with rasterio.drivers(CPL_DEBUG=True):
-        with rasterio.open("tests/data/RGB.byte.tif") as src:
+        with rasterio.open("tests/data/RGB.byte.tif"):
+            pass
+
+    log = open(logfile).read()
+    assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" in log
+
+
+def test_cpl_debug_false(tmpdir):
+    """Setting CPL_DEBUG=False results in no GDAL debug messages."""
+    log = logging.getLogger('GDAL')
+    log.setLevel(logging.DEBUG)
+    logfile = str(tmpdir.join('test.log'))
+    fh = logging.FileHandler(logfile)
+    log.addHandler(fh)
+
+    with rasterio.drivers(CPL_DEBUG=False):
+        with rasterio.open("tests/data/RGB.byte.tif"):
             pass
 
-    log = open(logfile1).read()
-    assert "Option CPL_DEBUG=True" in log
-    
-    # The GDAL env above having exited, CPL_DEBUG should be OFF.
-    logfile2 = str(tmpdir.join('test_options2.log'))
-    fh = logging.FileHandler(logfile2)
-    logger.addHandler(fh)
-
-    with rasterio.open("tests/data/RGB.byte.tif") as src:
-        pass
-    
     # Expect no debug messages from GDAL.
-    log = open(logfile2).read()
+    log = open(logfile).read()
     assert "GDAL: GDALOpen(tests/data/RGB.byte.tif" not in log