diff --git a/README.rst b/README.rst
index e6aceda..67b6459 100644
--- a/README.rst
+++ b/README.rst
@@ -1,15 +1,13 @@
 phial
 =====
 
-|Documentation Status| |CircleCI| |Coverage Status|
+|Documentation Status| |CircleCI| |Coverage Status| |PyPi|
 
     A simple framework for building slack bots
 
 Phial is a slack bot framework, modelled loosely on
 `flask <https://github.com/pallets/flask/>`__.
 
-NOTE: This package is still in early development, things are likely to
-change
 
 Table of Contents
 -----------------
@@ -90,3 +88,5 @@ Erlenmeyer Flask by Iconic from the Noun Project - `CC BY
    :target: https://circleci.com/gh/sedders123/phial
 .. |Coverage Status| image:: https://coveralls.io/repos/github/sedders123/phial/badge.svg?branch=develop
    :target: https://coveralls.io/github/sedders123/phial?branch=develop
+.. |PyPi| image:: https://badge.fury.io/py/phial-slack.svg
+    :target: https://badge.fury.io/py/phial-slack
diff --git a/phial/bot.py b/phial/bot.py
index 9f25b14..9d7cc3c 100644
--- a/phial/bot.py
+++ b/phial/bot.py
@@ -1,6 +1,7 @@
 from slackclient import SlackClient  # type: ignore
 import re
 from typing import Dict, List, Pattern, Callable, Union, Tuple, Any  # noqa
+import logging
 from .globals import _command_ctx_stack, command, _global_ctx_stack
 from .wrappers import Command, Response, Message, Attachment
 
@@ -17,12 +18,17 @@ class Phial():
         'prefix': '!'
     }
 
-    def __init__(self, token: str, config: dict = default_config) -> None:
+    def __init__(self,
+                 token: str,
+                 config: dict = default_config,
+                 logger: logging.Logger = logging.getLogger(__name__)) -> None:
         self.slack_client = SlackClient(token)
         self.commands = {}  # type: Dict
         self.middleware_functions = []  # type: List
         self.config = config
         self.running = False
+        self.logger = logger
+
         _global_ctx_stack.push({})
 
     @staticmethod
@@ -69,6 +75,8 @@ class Phial():
                                                       case_sensitive)
         if command_pattern not in self.commands:
             self.commands[command_pattern] = command_func
+            self.logger.debug("Command {0} added"
+                              .format(command_pattern_template))
         else:
             raise ValueError('Command {0} already exists'
                              .format(command_pattern.split("<")[0]))
@@ -158,7 +166,7 @@ class Phial():
 
         '''
         def decorator(f: Callable) -> Callable:
-            self.middleware_functions.append(f)
+            self.add_middleware(f)
             return f
         return decorator
 
@@ -183,6 +191,10 @@ class Phial():
             middleware_func(func): The function to be added to the middleware
                                    pipeline
         '''
+        self.logger.debug("Middleware {0} added"
+                          .format(getattr(middleware_func,
+                                          '__name__',
+                                          repr(middleware_func))))
         self.middleware_functions.append(middleware_func)
 
     def alias(self,
@@ -247,6 +259,8 @@ class Phial():
         if output_list and len(output_list) > 0:
             for output in output_list:
                 if(output and 'text' in output):
+                    self.logger.debug("Message recieved from Slack: {0}"
+                                      .format(output))
                     bot_id = None
                     if 'bot_id' in output:
                         bot_id = output['bot_id']
@@ -359,7 +373,7 @@ class Phial():
             response = self._handle_command(command)
             self._execute_response(response)
         except ValueError as err:
-            print('ValueError: {}'.format(err))
+            self.logger.exception('ValueError: {}'.format(err))
         finally:
             _command_ctx_stack.pop()
 
@@ -370,7 +384,7 @@ class Phial():
         if not slack_client.rtm_connect():
             raise ValueError("Connection failed. Invalid Token or bot ID")
 
-        print("Phial connected and running!")
+        self.logger.info("Phial connected and running!")
         while self._is_running():
             try:
                 message = self._parse_slack_output(slack_client
@@ -378,4 +392,4 @@ class Phial():
                 if message:
                     self._handle_message(message)
             except Exception as e:
-                print("Error: {0}".format(e))
+                self.logger.exception("Error: {0}".format(e))

diff --git a/tests/test_bot.py b/tests/test_bot.py
index 93faed7..19d905c 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -4,7 +4,7 @@ from phial import Phial, command, Response, Attachment, g
 import phial.wrappers
 import phial.globals
 import re
-from .helpers import captured_output, MockTrueFunc
+from .helpers import MockTrueFunc
 
 
 class TestPhialBotIsRunning(unittest.TestCase):
@@ -580,12 +580,13 @@ class TestRun(TestPhialBot):
                                               'user_id',
                                               'timestamp')
         self.bot._parse_slack_output = MagicMock(return_value=test_command)
-        with captured_output() as (out, err):
-            self.bot.run()
 
-        output = out.getvalue().strip()
         expected_msg = 'ValueError: Command "test" has not been registered'
-        self.assertTrue(expected_msg in output)
+        with self.assertLogs(logger='phial.bot', level='ERROR') as cm:
+            self.bot.run()
+
+            error = cm.output[0]
+            self.assertIn(expected_msg, error)
 
 
 class TestGlobalContext(unittest.TestCase):

Add support for logger
Allow a user to pass in a `logger` and log when events of interest happen.
tests/test_bot.py::TestRun::test_errors_with_invalid_command
tests/test_bot.py::TestPhialBotIsRunning::test_returns_expected_value tests/test_bot.py::TestCommandDecarator::test_command_decorator_calls_add_command tests/test_bot.py::TestCommandDecarator::test_command_decorator_functionality tests/test_bot.py::TestAliasDecarator::test_command_decorator_calls_add_command_case_insensitive tests/test_bot.py::TestAliasDecarator::test_command_decorator_calls_add_command_case_sensitive tests/test_bot.py::TestAliasDecarator::test_command_decorator_functionality tests/test_bot.py::TestAddCommand::test_add_command_errors_on_duplicate_name tests/test_bot.py::TestAddCommand::test_add_command_functionality tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_multiple_substition_case_sensitive tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_multiple_substition_ignore_case tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_no_substition_case_sensitive tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_no_substition_ignore_case tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_single_substition_case_sensitive tests/test_bot.py::TestBuildCommandPattern::test_build_command_pattern_single_substition_ignore_case tests/test_bot.py::TestGetCommandMatch::test_basic_functionality tests/test_bot.py::TestGetCommandMatch::test_multi_substition_matching tests/test_bot.py::TestGetCommandMatch::test_returns_none_correctly tests/test_bot.py::TestGetCommandMatch::test_single_substition_matching tests/test_bot.py::TestCreateCommand::test_basic_functionality tests/test_bot.py::TestCreateCommand::test_basic_functionality_with_args tests/test_bot.py::TestCreateCommand::test_errors_when_no_command_match tests/test_bot.py::TestHandleCommand::test_handle_command_basic_functionality tests/test_bot.py::TestCommandContextWorksCorrectly::test_command_context_pops_correctly tests/test_bot.py::TestCommandContextWorksCorrectly::test_command_context_works_correctly tests/test_bot.py::TestParseSlackOutput::test_basic_functionality tests/test_bot.py::TestParseSlackOutput::test_returns_message_correctly_for_normal_message tests/test_bot.py::TestParseSlackOutput::test_returns_message_with_bot_id_correctly tests/test_bot.py::TestParseSlackOutput::test_returns_none_correctly_if_no_messages tests/test_bot.py::TestSendMessage::test_send_message tests/test_bot.py::TestSendMessage::test_send_reply tests/test_bot.py::TestSendReaction::test_basic_functionality tests/test_bot.py::TestUploadAttachment::test_basic_functionality tests/test_bot.py::TestExecuteResponse::test_errors_on_invalid_response tests/test_bot.py::TestExecuteResponse::test_errors_with_invalid_attachment tests/test_bot.py::TestExecuteResponse::test_errors_with_reaction_and_reply tests/test_bot.py::TestExecuteResponse::test_send_message tests/test_bot.py::TestExecuteResponse::test_send_reaction tests/test_bot.py::TestExecuteResponse::test_send_reply tests/test_bot.py::TestExecuteResponse::test_send_string tests/test_bot.py::TestExecuteResponse::test_upload_attachment tests/test_bot.py::TestMiddleware::test_add_function tests/test_bot.py::TestMiddleware::test_decarator tests/test_bot.py::TestMiddleware::test_halts_message_when_none_returned tests/test_bot.py::TestMiddleware::test_passes_on_message_correctly tests/test_bot.py::TestHandleMessage::test_bot_message_does_not_trigger_command tests/test_bot.py::TestRun::test_basic_functionality tests/test_bot.py::TestRun::test_errors_with_invalid_token tests/test_bot.py::TestGlobalContext::test_global_context_in_command