diff --git a/force_bdss/bdss_application.py b/force_bdss/bdss_application.py
index 2cc751620af346bcad03a6db223f75ec4faaa29c..4f498eb497a6206b8a3cebf37658083336381ba8 100644
--- a/force_bdss/bdss_application.py
+++ b/force_bdss/bdss_application.py
@@ -12,6 +12,8 @@ from .factory_registry_plugin import FactoryRegistryPlugin
 from .core_evaluation_driver import CoreEvaluationDriver
 from .core_mco_driver import CoreMCODriver
 
+log = logging.getLogger(__name__)
+
 
 class BDSSApplication(Application):
     """Main application for the BDSS.
@@ -47,7 +49,7 @@ class BDSSApplication(Application):
         try:
             mgr.map(functools.partial(_import_extensions, plugins))
         except NoMatches:
-            logging.info("No extensions found")
+            log.info("No extensions found")
 
         super(BDSSApplication, self).__init__(plugins=plugins)
 
@@ -56,7 +58,7 @@ def _import_extensions(plugins, ext):
     """Service routine extracted for testing.
     Imports the extension in the plugins argument.
     """
-    logging.info("Found extension {}".format(ext.obj))
+    log.info("Found extension {}".format(ext.obj))
     plugins.append(ext.obj)
 
 
@@ -65,7 +67,8 @@ def _load_failure_callback(plugins, manager, entry_point, exception):
     Reports failure to load a module through stevedore, using the
     on_load_failure_callback option.
     """
-    logging.error(
+    log.error(
         "Unable to load plugin {}. Exception: {}. Message: {}".format(
             entry_point, exception.__class__.__name__, exception)
     )
+    log.exception(exception)
diff --git a/force_bdss/cli/force_bdss.py b/force_bdss/cli/force_bdss.py
index 48ce95353a49a5531c68bd09d4e441c7151179d7..40503ce5116ce15db3d00e9f85d726b10d028c3b 100644
--- a/force_bdss/cli/force_bdss.py
+++ b/force_bdss/cli/force_bdss.py
@@ -1,3 +1,4 @@
+import logging
 import click
 
 from ..bdss_application import BDSSApplication
@@ -10,12 +11,26 @@ push_exception_handler(reraise_exceptions=True)
 
 @click.command()
 @click.option("--evaluate", is_flag=True)
+@click.option("--logfile",
+              type=click.Path(exists=False),
+              help="If specified, the log filename. "
+                   " If unspecified, the log will be written to stdout.")
 @click.argument('workflow_filepath', type=click.Path(exists=True))
-def run(evaluate, workflow_filepath):
+def run(evaluate, logfile, workflow_filepath):
 
-    application = BDSSApplication(
-        evaluate=evaluate,
-        workflow_filepath=workflow_filepath
-    )
+    logging_config = {}
+    if logfile is not None:
+        logging_config["filename"] = logfile
 
-    application.run()
+    logging.basicConfig(**logging_config)
+    log = logging.getLogger(__name__)
+
+    try:
+        application = BDSSApplication(
+            evaluate=evaluate,
+            workflow_filepath=workflow_filepath
+        )
+
+        application.run()
+    except Exception as e:
+        log.exception(e)
diff --git a/force_bdss/core_evaluation_driver.py b/force_bdss/core_evaluation_driver.py
index 33fc4755a456ad4c0609b0365c643e8f844641a5..1f4b9dc42b10b5d10d9e3719246628cd5ed1f88f 100644
--- a/force_bdss/core_evaluation_driver.py
+++ b/force_bdss/core_evaluation_driver.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
 import sys
 import logging
 
@@ -14,6 +12,8 @@ from .io.workflow_reader import (
 
 CORE_EVALUATION_DRIVER_ID = plugin_id("core", "CoreEvaluationDriver")
 
+log = logging.getLogger(__name__)
+
 
 class CoreEvaluationDriver(BaseCoreDriver):
     """Main plugin that handles the execution of the MCO
@@ -26,12 +26,12 @@ class CoreEvaluationDriver(BaseCoreDriver):
         try:
             workflow = self.workflow
         except (InvalidVersionException, InvalidFileException) as e:
-            print(str(e), file=sys.stderr)
+            log.exception(e)
             sys.exit(1)
 
         mco_model = workflow.mco
         if mco_model is None:
-            print("No MCO defined. Nothing to do. Exiting.")
+            log.info("No MCO defined. Nothing to do. Exiting.")
             sys.exit(0)
 
         mco_factory = mco_model.factory
@@ -105,7 +105,7 @@ def _compute_layer_results(environment_data_values,
             in_slots)
 
         # execute data source, passing only relevant data values.
-        logging.info("Evaluating for Data Source {}".format(
+        log.info("Evaluating for Data Source {}".format(
             factory.name))
         res = evaluator.run(model, passed_data_values)
 
@@ -118,7 +118,7 @@ def _compute_layer_results(environment_data_values,
                 len(res), factory.name, len(out_slots)
             )
 
-            logging.error(error_txt)
+            log.error(error_txt)
             raise RuntimeError(error_txt)
 
         if len(res) != len(model.output_slot_names):
@@ -133,7 +133,7 @@ def _compute_layer_results(environment_data_values,
                 len(model.output_slot_names)
             )
 
-            logging.error(error_txt)
+            log.error(error_txt)
             raise RuntimeError(error_txt)
 
         # At this point, the returned data values are unnamed.
@@ -173,7 +173,7 @@ def _get_data_values_from_mco(model, communicator):
                      " file is corrupted.").format(
             len(mco_data_values), len(model.parameters)
         )
-        logging.error(error_txt)
+        log.error(error_txt)
         raise RuntimeError(error_txt)
 
     # The data values obtained by the communicator are unnamed.
diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py
index 478499af138b50ee6ecdd1abe0ca95e583d37e85..ea2cbfafbec5683480450ec2854d402137d2b717 100644
--- a/force_bdss/core_mco_driver.py
+++ b/force_bdss/core_mco_driver.py
@@ -1,5 +1,3 @@
-from __future__ import print_function
-
 import sys
 import logging
 
@@ -44,12 +42,12 @@ class CoreMCODriver(BaseCoreDriver):
         try:
             workflow = self.workflow
         except (InvalidVersionException, InvalidFileException) as e:
-            print(str(e), file=sys.stderr)
+            log.exception(e)
             sys.exit(1)
 
         mco_model = workflow.mco
         if mco_model is None:
-            print("No MCO defined. Nothing to do. Exiting.")
+            log.info("No MCO defined. Nothing to do. Exiting.")
             sys.exit(0)
 
         mco_factory = mco_model.factory
diff --git a/force_bdss/io/tests/test_workflow_writer.py b/force_bdss/io/tests/test_workflow_writer.py
index 7d649c067abf006eb9e71dd36feea441610fe1dd..74a8dbf90ff1b776f1bccbc9f40a73462a1d16cf 100644
--- a/force_bdss/io/tests/test_workflow_writer.py
+++ b/force_bdss/io/tests/test_workflow_writer.py
@@ -53,7 +53,6 @@ class TestWorkflowWriter(unittest.TestCase):
         fp = StringIO()
         wf = self._create_mock_workflow()
         wfwriter.write(wf, fp)
-        print(fp.getvalue())
         fp.seek(0)
         wfreader = WorkflowReader(self.mock_registry)
         wf_result = wfreader.read(fp)
diff --git a/force_bdss/io/workflow_reader.py b/force_bdss/io/workflow_reader.py
index 19d4ecbb9b06309cc1169d7cd465da32558543f2..748479e2ecc1ef31d48c5036fbd705a1021c04f3 100644
--- a/force_bdss/io/workflow_reader.py
+++ b/force_bdss/io/workflow_reader.py
@@ -7,6 +7,8 @@ from force_bdss.core.input_slot_map import InputSlotMap
 from force_bdss.core.workflow import Workflow
 from ..factory_registry_plugin import IFactoryRegistryPlugin
 
+log = logging.getLogger(__name__)
+
 SUPPORTED_FILE_VERSIONS = ["1"]
 
 
@@ -69,12 +71,12 @@ class WorkflowReader(HasStrictTraits):
         try:
             version = json_data["version"]
         except KeyError:
-            logging.error("File missing version information")
+            log.error("File missing version information")
             raise InvalidFileException("Corrupted input file, no version"
                                        " specified")
 
         if version not in SUPPORTED_FILE_VERSIONS:
-            logging.error(
+            log.error(
                 "File contains version {} that is not in the "
                 "list of supported versions {}".format(
                     version, SUPPORTED_FILE_VERSIONS)
@@ -92,7 +94,7 @@ class WorkflowReader(HasStrictTraits):
             wf.notification_listeners[:] = \
                 self._extract_notification_listeners(wf_data)
         except KeyError as e:
-            logging.exception("Could not read file {}".format(file))
+            log.exception("Could not read file {}".format(file))
             raise InvalidFileException("Could not read file {}. "
                                        "Unable to find key {}".format(file, e))
         return wf