diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py
index f991388..9bd775d 100644
--- a/django/contrib/gis/gdal/libgdal.py
+++ b/django/contrib/gis/gdal/libgdal.py
@@ -1,9 +1,16 @@
+import logging
 import os
 import re
-from ctypes import c_char_p, CDLL
+from ctypes import c_char_p, c_int, CDLL, CFUNCTYPE
 from ctypes.util import find_library
 from django.contrib.gis.gdal.error import OGRException
 
+# Set module logging
+logger = logging.getLogger('django.contrib.gis')
+if not logger.handlers:
+    ch = logging.StreamHandler()
+    logger.addHandler(ch)
+
 # Custom library path set?
 try:
     from django.conf import settings
@@ -103,3 +110,16 @@ if GDAL_VERSION >= (1, 5):
 else:
     GEOJSON = False
 
+CPLErrorHandler = CFUNCTYPE(None, c_int, c_int, c_char_p)
+def err_handler(error_class, error_number, message):
+    logger.error('GDAL_ERROR %d: %s' % (error_number, message))
+err_handler = CPLErrorHandler(err_handler)
+
+def function(name, args, restype):
+    func = std_call(name)
+    func.argtypes = args
+    func.restype = restype
+    return func
+
+set_error_handler = function('CPLSetErrorHandler', [CPLErrorHandler], CPLErrorHandler)
+set_error_handler(err_handler)
diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py
index 16d6e86..11f5c01 100644
--- a/django/contrib/gis/gdal/tests/test_ds.py
+++ b/django/contrib/gis/gdal/tests/test_ds.py
@@ -3,6 +3,7 @@ import unittest
 from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION
 from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
 from django.contrib.gis.geometry.test_data import get_ds_file, TestDS, TEST_DATA
+from django.test.utils import capture_logging
 
 # List of acceptable data sources.
 ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
@@ -59,7 +60,6 @@ class DataSourceTest(unittest.TestCase):
 
     def test03a_layers(self):
         "Testing Data Source Layers."
-        print "\nBEGIN - expecting out of range feature id error; safe to ignore.\n"
         for source in ds_list:
             ds = DataSource(source.ds)
 
@@ -89,9 +89,11 @@ class DataSourceTest(unittest.TestCase):
                 flds = layer.fields
                 for f in flds: self.assertEqual(True, f in source.fields)
 
-                # Negative FIDs are not allowed.
-                self.assertRaises(OGRIndexError, layer.__getitem__, -1)
-                self.assertRaises(OGRIndexError, layer.__getitem__, 50000)
+                # Negative FIDs are not allowed. Capture logs as it is expected
+                # that an 'out of range' GDAL_ERROR will be emitted
+                with capture_logging('django.contrib.gis'):
+                    self.assertRaises(OGRIndexError, layer.__getitem__, -1)
+                    self.assertRaises(OGRIndexError, layer.__getitem__, 50000)
 
                 if hasattr(source, 'field_values'):
                     fld_names = source.field_values.keys()
@@ -108,7 +110,6 @@ class DataSourceTest(unittest.TestCase):
                         # the feature values here while in this loop.
                         for fld_name in fld_names:
                             self.assertEqual(source.field_values[fld_name][i], feat.get(fld_name))
-        print "\nEND - expecting out of range feature id error; safe to ignore."
 
     def test03b_layer_slice(self):
         "Test indexing and slicing on Layers."
diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py
index 656901b..ce8c0e7 100644
--- a/django/contrib/gis/gdal/tests/test_geom.py
+++ b/django/contrib/gis/gdal/tests/test_geom.py
@@ -8,6 +8,7 @@ from django.contrib.gis.gdal import (OGRGeometry, OGRGeomType, OGRException,
     OGRIndexError, SpatialReference, CoordTransform, GDAL_VERSION)
 from django.contrib.gis.gdal.prototypes.geom import GEOJSON
 from django.contrib.gis.geometry.test_data import TestDataMixin
+from django.test.utils import capture_logging
 from django.utils import unittest
 
 class OGRGeomTest(unittest.TestCase, TestDataMixin):
@@ -234,15 +235,11 @@ class OGRGeomTest(unittest.TestCase, TestDataMixin):
         # Both rings in this geometry are not closed.
         poly = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))')
         self.assertEqual(8, poly.point_count)
-        print "\nBEGIN - expecting IllegalArgumentException; safe to ignore.\n"
-        try:
-            c = poly.centroid
-        except OGRException:
-            # Should raise an OGR exception, rings are not closed
-            pass
-        else:
-            self.fail('Should have raised an OGRException!')
-        print "\nEND - expecting IllegalArgumentException; safe to ignore.\n"
+
+        # Capture logging as an IllegalArgumentException will be output by GDAL
+        with capture_logging('django.contrib.gis'):
+            # Rings are not closed
+            self.assertRaises(OGRException, lambda: poly.centroid)
 
         # Closing the rings -- doesn't work on GDAL versions 1.4.1 and below:
         # http://trac.osgeo.org/gdal/ticket/1673
diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py
index a4f5adf..672f2b9 100644
--- a/django/contrib/gis/geos/libgeos.py
+++ b/django/contrib/gis/geos/libgeos.py
@@ -6,13 +6,19 @@
  This module also houses GEOS Pointer utilities, including
  get_pointer_arr(), and GEOM_PTR.
 """
+import logging
 import os
 import re
-import sys
 from ctypes import c_char_p, Structure, CDLL, CFUNCTYPE, POINTER
 from ctypes.util import find_library
 from django.contrib.gis.geos.error import GEOSException
 
+# Set module logging
+logger = logging.getLogger('django.contrib.gis')
+if not logger.handlers:
+    ch = logging.StreamHandler()
+    logger.addHandler(ch)
+
 # Custom library path set?
 try:
     from django.conf import settings
@@ -56,21 +62,21 @@ lgeos = CDLL(lib_path)
 # Supposed to mimic the GEOS message handler (C below):
 #  typedef void (*GEOSMessageHandler)(const char *fmt, ...);
 NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
-def notice_h(fmt, lst, output_h=sys.stdout):
+def notice_h(fmt, lst):
     try:
         warn_msg = fmt % lst
     except:
         warn_msg = fmt
-    output_h.write('GEOS_NOTICE: %s\n' % warn_msg)
+    logger.warn('GEOS_NOTICE: %s' % warn_msg)
 notice_h = NOTICEFUNC(notice_h)
 
 ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
-def error_h(fmt, lst, output_h=sys.stderr):
+def error_h(fmt, lst):
     try:
         err_msg = fmt % lst
     except:
         err_msg = fmt
-    output_h.write('GEOS_ERROR: %s\n' % err_msg)
+    logger.error('GEOS_ERROR: %s' % err_msg)
 error_h = ERRORFUNC(error_h)
 
 #### GEOS Geometry C data structures, and utility functions. ####
diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py
index a8372b4..5d09be8 100644
--- a/django/contrib/gis/geos/tests/test_geos.py
+++ b/django/contrib/gis/geos/tests/test_geos.py
@@ -1,10 +1,13 @@
 import ctypes
 import random
 import unittest
+
 from django.contrib.gis.geos import *
 from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
 from django.contrib.gis.geos.libgeos import GEOS_PREPARE
 from django.contrib.gis.geometry.test_data import TestDataMixin
+from django.test.utils import capture_logging
+
 
 class GEOSTest(unittest.TestCase, TestDataMixin):
 
@@ -134,17 +137,17 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
     def test01d_errors(self):
         "Testing the Error handlers."
         # string-based
-        print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
-        for err in self.geometries.errors:
-            try:
-                g = fromstr(err.wkt)
-            except (GEOSException, ValueError):
-                pass
-
-        # Bad WKB
-        self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
-
-        print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
+        with capture_logging('django.contrib.gis') as out:
+            for err in self.geometries.errors:
+                try:
+                    g = fromstr(err.wkt)
+                except (GEOSException, ValueError):
+                    pass
+            # Two GEOS_ERRORs:
+            self.assertEqual(len(out), 2)
+
+            # Bad WKB
+            self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
 
         class NotAGeometry(object):
             pass
@@ -439,26 +442,26 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
 
     def test05b_multipolygons(self):
         "Testing MultiPolygon objects."
-        print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
         prev = fromstr('POINT (0 0)')
-        for mp in self.geometries.multipolygons:
-            mpoly = fromstr(mp.wkt)
-            self.assertEqual(mpoly.geom_type, 'MultiPolygon')
-            self.assertEqual(mpoly.geom_typeid, 6)
-            self.assertEqual(mp.valid, mpoly.valid)
-
-            if mp.valid:
-                self.assertEqual(mp.num_geom, mpoly.num_geom)
-                self.assertEqual(mp.n_p, mpoly.num_coords)
-                self.assertEqual(mp.num_geom, len(mpoly))
-                self.assertRaises(GEOSIndexError, mpoly.__getitem__, len(mpoly))
-                for p in mpoly:
-                    self.assertEqual(p.geom_type, 'Polygon')
-                    self.assertEqual(p.geom_typeid, 3)
-                    self.assertEqual(p.valid, True)
-                self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
-
-        print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
+        with capture_logging('django.contrib.gis') as out:
+            for mp in self.geometries.multipolygons:
+                mpoly = fromstr(mp.wkt)
+                self.assertEqual(mpoly.geom_type, 'MultiPolygon')
+                self.assertEqual(mpoly.geom_typeid, 6)
+                self.assertEqual(mp.valid, mpoly.valid)
+
+                if mp.valid:
+                    self.assertEqual(mp.num_geom, mpoly.num_geom)
+                    self.assertEqual(mp.n_p, mpoly.num_coords)
+                    self.assertEqual(mp.num_geom, len(mpoly))
+                    self.assertRaises(GEOSIndexError, mpoly.__getitem__, len(mpoly))
+                    for p in mpoly:
+                        self.assertEqual(p.geom_type, 'Polygon')
+                        self.assertEqual(p.geom_typeid, 3)
+                        self.assertEqual(p.valid, True)
+                    self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
+            self.assertTrue(out[0].msg,
+                'GEOS_NOTICE: Duplicate Rings at or near point 60 300')
 
     def test06a_memory_hijinks(self):
         "Testing Geometry __del__() on rings and polygons."
@@ -894,14 +897,10 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
         """ Testing `transform` method (no SRID) """
         # Raise a warning if SRID <0/None.
         import warnings
-        print "\nBEGIN - expecting Warnings; safe to ignore.\n"
 
         # Test for do-nothing behavior.
-        try:
-            # Keeping line-noise down by only printing the relevant
-            # warnings once.
-            warnings.simplefilter('once', UserWarning)
-            warnings.simplefilter('once', FutureWarning)
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore")
 
             g = GEOSGeometry('POINT (-104.609 38.255)', srid=None)
             g.transform(2774)
@@ -921,13 +920,6 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
             g1 = g.transform(2774, clone=True)
             self.assertTrue(g1 is None)
 
-        finally:
-            warnings.simplefilter('default', UserWarning)
-            warnings.simplefilter('default', FutureWarning)
-
-        print "\nEND - expecting Warnings; safe to ignore.\n"
-
-
         # test warning is raised
         try:
             warnings.simplefilter('error', FutureWarning)
@@ -1039,16 +1031,15 @@ class GEOSTest(unittest.TestCase, TestDataMixin):
         self.assertTrue(isinstance(g.valid_reason, basestring))
         self.assertEqual(g.valid_reason, "Valid Geometry")
 
-        print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
-
         g = GEOSGeometry("LINESTRING(0 0, 0 0)")
 
-        self.assertTrue(not g.valid)
+        with capture_logging('django.contrib.gis') as out:
+            self.assertTrue(not g.valid)
+            self.assertEqual(out[0].msg,
+                'GEOS_NOTICE: Too few points in geometry component at or near point 0 0')
         self.assertTrue(isinstance(g.valid_reason, basestring))
         self.assertTrue(g.valid_reason.startswith("Too few points in geometry component"))
 
-        print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
-
     def test28_geos_version(self):
         "Testing the GEOS version regular expression."
         from django.contrib.gis.geos.libgeos import version_regex
