diff --git a/requirements.txt b/requirements.txt
index 3947be027..435976b67 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,6 @@ wsgiref>=0.1.2
 pecan>=0.4.5
 WSME>=0.6
 six>=1.5.2
-structlog==0.4.1
 oslo.config==1.2.1
 Babel==1.3
 iso8601==0.1.10
diff --git a/teeth_agent/agent.py b/teeth_agent/agent.py
index fbc263951..783ac5e5d 100644
--- a/teeth_agent/agent.py
+++ b/teeth_agent/agent.py
@@ -21,7 +21,6 @@ import time
 
 import pkg_resources
 from stevedore import driver
-import structlog
 from wsgiref import simple_server
 
 from teeth_agent.api import app
@@ -29,9 +28,15 @@ from teeth_agent import base
 from teeth_agent import encoding
 from teeth_agent import errors
 from teeth_agent import hardware
+from teeth_agent.openstack.common import log
 from teeth_agent import overlord_agent_api
 
 
+def _time():
+    """Wraps time.time() for simpler testing."""
+    return time.time()
+
+
 class TeethAgentStatus(encoding.Serializable):
     def __init__(self, mode, started_at, version):
         self.mode = mode
@@ -66,7 +71,7 @@ class TeethAgentHeartbeater(threading.Thread):
         self.agent = agent
         self.hardware = hardware.get_manager()
         self.api = overlord_agent_api.APIClient(agent.api_url)
-        self.log = structlog.get_logger(api_url=agent.api_url)
+        self.log = log.getLogger(__name__)
         self.stop_event = threading.Event()
         self.error_delay = self.initial_delay
 
@@ -79,8 +84,9 @@ class TeethAgentHeartbeater(threading.Thread):
             next_heartbeat_by = self.do_heartbeat()
             interval_multiplier = random.uniform(self.min_jitter_multiplier,
                                                  self.max_jitter_multiplier)
-            interval = (next_heartbeat_by - time.time()) * interval_multiplier
-            self.log.info('sleeping before next heartbeat', interval=interval)
+            interval = (next_heartbeat_by - _time()) * interval_multiplier
+            log_msg = 'sleeping before next heartbeat, interval: {0}'
+            self.log.info(log_msg.format(interval))
 
     def do_heartbeat(self):
         try:
@@ -91,8 +97,9 @@ class TeethAgentHeartbeater(threading.Thread):
             self.error_delay = self.initial_delay
             self.log.info('heartbeat successful')
         except Exception as e:
-            self.log.error('error sending heartbeat', exception=e)
-            deadline = time.time() + self.error_delay
+            self.log.error('error sending heartbeat')
+            self.log.exception(e)
+            deadline = _time() + self.error_delay
             self.error_delay = min(self.error_delay * self.backoff_factor,
                                    self.max_delay)
             pass
@@ -117,7 +124,7 @@ class TeethAgent(object):
         self.heartbeater = TeethAgentHeartbeater(self)
         self.hardware = hardware.get_manager()
         self.command_lock = threading.Lock()
-        self.log = structlog.get_logger()
+        self.log = log.getLogger(__name__)
         self.started_at = None
 
     def get_mode_name(self):
@@ -197,7 +204,7 @@ class TeethAgent(object):
 
     def run(self):
         """Run the Teeth Agent."""
-        self.started_at = time.time()
+        self.started_at = _time()
         self.heartbeater.start()
         wsgi = simple_server.make_server(
             self.listen_address[0],
@@ -208,7 +215,8 @@ class TeethAgent(object):
         try:
             wsgi.serve_forever()
         except BaseException as e:
-            self.log.error('shutting down', exception=e)
+            self.log.error('shutting down')
+            self.log.exception(e)
 
         self.heartbeater.stop()
 
diff --git a/teeth_agent/base.py b/teeth_agent/base.py
index 8147c43d9..babed349f 100644
--- a/teeth_agent/base.py
+++ b/teeth_agent/base.py
@@ -17,10 +17,10 @@ limitations under the License.
 import threading
 import uuid
 
-import structlog
 
 from teeth_agent import encoding
 from teeth_agent import errors
+from teeth_agent.openstack.common import log
 
 
 class AgentCommandStatus(object):
@@ -117,7 +117,7 @@ class AsyncCommandResult(BaseCommandResult):
 class BaseAgentMode(object):
     def __init__(self, name):
         super(BaseAgentMode, self).__init__()
-        self.log = structlog.get_logger(agent_mode=name)
+        self.log = log.getLogger(__name__)
         self.name = name
         self.command_map = {}
 
diff --git a/teeth_agent/cmd/agent.py b/teeth_agent/cmd/agent.py
index 92f4ef386..7075693a5 100644
--- a/teeth_agent/cmd/agent.py
+++ b/teeth_agent/cmd/agent.py
@@ -17,7 +17,6 @@ limitations under the License.
 import argparse
 
 from teeth_agent import agent
-from teeth_agent import logging
 
 
 def run():
@@ -44,7 +43,6 @@ def run():
                         help='The external IP address to advertise to ironic')
 
     args = parser.parse_args()
-    logging.configure()
     agent.build_agent(args.api_url,
                       args.listen_host,
                       args.listen_port,
diff --git a/teeth_agent/hardware.py b/teeth_agent/hardware.py
index a8e4e0fd9..4f192a1d9 100644
--- a/teeth_agent/hardware.py
+++ b/teeth_agent/hardware.py
@@ -20,9 +20,9 @@ import os
 import subprocess
 
 import stevedore
-import structlog
 
 from teeth_agent import encoding
+from teeth_agent.openstack.common import log
 
 _global_manager = None
 
@@ -162,7 +162,7 @@ def get_manager():
     global _global_manager
 
     if not _global_manager:
-        log = structlog.get_logger()
+        LOG = log.getLogger()
         extension_manager = stevedore.ExtensionManager(
             namespace='teeth_agent.hardware_managers',
             invoke_on_load=True)
@@ -175,8 +175,8 @@ def get_manager():
         if preferred_manager.evaluate_hardware_support() <= 0:
             raise RuntimeError('No suitable HardwareManager could be found')
 
-        log.info('selected hardware manager',
-                 manager_name=preferred_extension.entry_point_target)
+        LOG.info('selected hardware manager {0}'.format(
+                 preferred_extension.entry_point_target))
 
         _global_manager = preferred_manager
 
diff --git a/teeth_agent/logging.py b/teeth_agent/logging.py
deleted file mode 100644
index 5c66bc900..000000000
--- a/teeth_agent/logging.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-Copyright 2013 Rackspace, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
-
-import string
-
-import structlog
-
-import traceback
-
-EXCEPTION_LOG_METHODS = ['error']
-
-
-def _capture_stack_trace(logger, method, event):
-    if method in EXCEPTION_LOG_METHODS:
-        event['exception'] = traceback.format_exc()
-
-    return event
-
-
-def _format_event(logger, method, event):
-    """Formats the log message using keyword args.
-    log('hello {keyword}', keyword='world') should log: "hello world"
-    Removes the keywords used for formatting from the logged message.
-    Throws a KeyError if the log message requires formatting but doesn't
-    have enough keys to format.
-    """
-    if 'event' not in event:
-        # nothing to format
-        return event
-    # Get a list of fields that need to be filled.
-    formatter = string.Formatter()
-    try:
-        formatted = formatter.format(event['event'], **event)
-    except KeyError:
-        keys = formatter.parse(event['event'])
-        # index 1 is the key name
-        keys = [item[1] for item in keys]
-        missing_keys = list(set(keys) - set(event))
-        raise KeyError("Log formatter missing keys: {}, cannot format."
-                       .format(missing_keys))
-    event['event'] = formatted
-    return event
-
-
-def configure(pretty=False):
-    processors = [
-        _capture_stack_trace,
-        _format_event,
-    ]
-
-    if pretty:
-        processors.append(structlog.processors.ExceptionPrettyPrinter())
-        processors.append(structlog.processors.KeyValueRenderer())
-    else:
-        processors.append(structlog.processors.JSONRenderer())
-
-    structlog.configure(
-        processors=processors
-    )
diff --git a/teeth_agent/standby.py b/teeth_agent/standby.py
index 43df5c621..7dc9a3eb4 100644
--- a/teeth_agent/standby.py
+++ b/teeth_agent/standby.py
@@ -17,7 +17,6 @@ limitations under the License.
 import hashlib
 import os
 import requests
-import structlog
 import subprocess
 import time
 
@@ -26,8 +25,9 @@ from teeth_agent import configdrive
 from teeth_agent import decorators
 from teeth_agent import errors
 from teeth_agent import hardware
+from teeth_agent.openstack.common import log
 
-log = structlog.get_logger()
+LOG = log.getLogger(__name__)
 
 
 def _configdrive_location():
@@ -49,29 +49,31 @@ def _write_image(image_info, device):
 
     script = _path_to_script('shell/write_image.sh')
     command = ['/bin/bash', script, image, device]
-    log.info('Writing image', command=' '.join(command))
+    LOG.info('Writing image with command: {0}'.format(' '.join(command)))
     exit_code = subprocess.call(command)
     if exit_code != 0:
         raise errors.ImageWriteError(exit_code, device)
     totaltime = time.time() - starttime
-    log.info('Image written', device=device, seconds=totaltime, image=image)
+    LOG.info('Image {0} written to device {1} in {2} seconds'.format(
+             image, device, totaltime))
 
 
 def _copy_configdrive_to_disk(configdrive_dir, device):
     starttime = time.time()
     script = _path_to_script('shell/copy_configdrive_to_disk.sh')
     command = ['/bin/bash', script, configdrive_dir, device]
-    log.info('copying configdrive to disk', command=' '.join(command))
+    LOG.info('copying configdrive to disk with command {0}'.format(
+             ' '.join(command)))
     exit_code = subprocess.call(command)
 
     if exit_code != 0:
         raise errors.ConfigDriveWriteError(exit_code, device)
 
     totaltime = time.time() - starttime
-    log.info('configdrive copied',
-             from_directory=configdrive_dir,
-             device=device,
-             seconds=totaltime)
+    LOG.info('configdrive copied from {0} to {1} in {2} seconds'.format(
+             configdrive_dir,
+             device,
+             totaltime))
 
 
 def _request_url(image_info, url):
@@ -86,11 +88,12 @@ def _download_image(image_info):
     resp = None
     for url in image_info['urls']:
         try:
-            log.info("Attempting to download image", url=url)
+            LOG.info("Attempting to download image from {0}".format(url))
             resp = _request_url(image_info, url)
         except errors.ImageDownloadError:
             failtime = time.time() - starttime
-            log.warning("Image download failed", url=url, seconds=failtime)
+            log_msg = "Image download failed. URL: {0}; time: {1} seconds"
+            LOG.warning(log_msg.format(url, failtime))
             continue
         else:
             break
@@ -106,7 +109,8 @@ def _download_image(image_info):
             raise errors.ImageDownloadError(image_info['id'])
 
     totaltime = time.time() - starttime
-    log.info("Image downloaded", image=image_location, seconds=totaltime)
+    LOG.info("Image downloaded from {0} in {1} seconds".format(image_location,
+                                                               totaltime))
 
     if not _verify_image(image_info, image_location):
         raise errors.ImageChecksumError(image_info['id'])
@@ -118,19 +122,16 @@ def _verify_image(image_info, image_location):
         algo = getattr(hashlib, k, None)
         if algo is None:
             continue
-        log.debug('Verifying image',
-                  image=image_location,
-                  algo=k,
-                  passed_hash=v)
+        log_msg = 'Verifying image at {0} with algorithm {1} against hash {2}'
+        LOG.debug(log_msg.format(image_location, k, v))
         hash_ = algo(open(image_location).read()).hexdigest()
         if hash_ == v:
             return True
         else:
-            log.warning('Image verification failed',
-                        image=image_location,
-                        algo=k,
-                        imagehash=hash_,
-                        passed_hash=v)
+            log_msg = ('Image verification failed. Location: {0};'
+                       'algorithm: {1}; image hash: {2};'
+                       'verification hash: {3}')
+            LOG.warning(log_msg.format(image_location, k, hash_, v))
     return False
 
 
@@ -185,14 +186,14 @@ class StandbyMode(base.BaseAgentMode):
             _write_image(image_info, device)
             self.cached_image_id = image_info['id']
 
-        log.debug('Writing configdrive', location=location)
+        LOG.debug('Writing configdrive to {0}'.format(location))
         configdrive.write_configdrive(location, metadata, files)
         _copy_configdrive_to_disk(location, device)
 
     @decorators.async_command()
     def run_image(self, command_name):
         script = _path_to_script('shell/reboot.sh')
-        log.info('Rebooting system')
+        LOG.info('Rebooting system')
         command = ['/bin/bash', script]
         # this should never return if successful
         exit_code = subprocess.call(command)
diff --git a/teeth_agent/tests/agent.py b/teeth_agent/tests/agent.py
index 83313606f..61a48c686 100644
--- a/teeth_agent/tests/agent.py
+++ b/teeth_agent/tests/agent.py
@@ -53,7 +53,7 @@ class TestHeartbeater(unittest.TestCase):
             hardware.HardwareManager)
         self.heartbeater.stop_event = mock.Mock()
 
-    @mock.patch('time.time')
+    @mock.patch('teeth_agent.agent._time')
     @mock.patch('random.uniform')
     def test_heartbeat(self, mocked_uniform, mocked_time):
         time_responses = []
diff --git a/teeth_agent/tests/logging.py b/teeth_agent/tests/logging.py
deleted file mode 100644
index fc8fde34e..000000000
--- a/teeth_agent/tests/logging.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-Copyright 2013 Rackspace, Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-"""
-
-import structlog
-import unittest
-
-import teeth_agent.logging
-
-
-def _return_event_processor(logger, method, event):
-    return event['event']
-
-
-class EventLogger(unittest.TestCase):
-    def test_format_event_basic(self):
-        processors = [teeth_agent.logging._format_event,
-                      _return_event_processor]
-        structlog.configure(processors=processors)
-        log = structlog.wrap_logger(structlog.ReturnLogger())
-        logged_msg = log.msg("hello {word}", word='world')
-        self.assertEqual(logged_msg, "hello world")
-
-    def test_no_format_keys(self):
-        """Check that we get an exception if you don't provide enough keys to
-        format a log message requiring format
-        """
-        processors = [teeth_agent.logging._format_event,
-                      _return_event_processor]
-        structlog.configure(processors=processors)
-        log = structlog.wrap_logger(structlog.ReturnLogger())
-        self.assertRaises(KeyError, log.msg, "hello {word}")
diff --git a/tox.ini b/tox.ini
index 3e8f68a98..87ef3fb57 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,3 +16,6 @@ commands =
 [testenv:devenv]
 envdir = devenv
 usedevelop = True
+
+[flake8]
+exclude=*openstack/common*