Repository: lanl/BEE (language: python)

Issue:
pypa: Python packaging authority compliance
Based in part on: https://github.com/pypa/sampleproject

Some additional info available here: https://docs.google.com/presentation/d/1EuiC4PyIwj73mBYhrxqJpPjtSA-7PhfIgdMdrXoHmDo/edit#slide=id.p (Observe the presenter notes))
General logging implementation: restructure
This PR implements logging for BEE, and converts all bee component logging mechanisms to the new logging style based on the #274. This work was only possible after the PyPA code restructure, which is why I created a new PR.

I will demonstrate how the system works on Wednesday, but feel free to test at your convenience. Expected behavior is this:

1. All log calls in cli.py itself stream to STDIO (this includes gdb and restd, as described in #276.
2. All logs associated with cli.py are stored in `{bee_workdir}/logs/beeflow.log`.
3. All logs associated with specific BEE components and sub-components (flask, werkzeug) stream to `{bee_workdir}/logs/{component_name}.log` until the process is killed or crashes. 

Notes:

- Werkzeug appears to have an error associated with accepting external logging handlers. See this issue I filed: https://github.com/pallets/werkzeug/issues/2002. I pestered them in their community chat and got no responses, so assuming it's a bug for now.
- Because we are not using dedicated launch scripts for gdb and restd, the logging is a little clunky. If we make gdb and restd launch in a subprocess script like the other stuff, then we only have to use one logger for all bee components. As it stands, we have to use 3 [1]. See issue #276.
- I changed all components logs in one way or another. You shouldn't need to change anything, but it might be good to verify that your scripts are reporting in a way that makes sense to you. Let me know if I can help.
- I tried to turn all of our "print" statements into "log" calls. If I missed some of yours, please change them or these messages will be lost to oblivion.

# How to test
1. Check out this branch on Fog
2. Clean up any previous logs from `{bee_workdir}/logs`
2. `poetry install` from project root
3. Be sure poetry environment is activated
4. `cd src/` (this necessity will go away with #275)
5. `beeflow --sleep-time 45`
6. Connect client
7. Load workflow in it's new path relative to src: `beeflow/data/cwl/cf.cwl`
8. Run workflow
9. Observe CLAMR movie in `$HOME`. Observe logs from all components relative to `{bee_workdir}/logs`.

### References
[1]Logging will be less clunky and more efficient if GDB and restd get their own launch scripts.
https://github.com/lanl/BEE_Private/blob/41f2ccc60388da483a49f52d18b1677ba9a98f3f/src/beeflow/cli.py#L26-L28



Discussion / hints:

Fix patch:
diff --git a/beeflow/wfm/README.md b/beeflow/wfm/README.md
deleted file mode 100644
index 4724b7ba7..000000000
--- a/beeflow/wfm/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# BEE WorkflowManager (wfm)
-The wfm directory contains all of the wfm-only software necessary
-to execute BEE workflows.
diff --git a/doc/ECPMilestones/ECPFY20STNS01-4-Completion.pdf b/docs/ECPMilestones/ECPFY20STNS01-4-Completion.pdf
similarity index 100%
rename from doc/ECPMilestones/ECPFY20STNS01-4-Completion.pdf
rename to docs/ECPMilestones/ECPFY20STNS01-4-Completion.pdf
diff --git a/doc/ECPMilestones/STNS01-6-2020-03-31-BEE-P6-3.pdf b/docs/ECPMilestones/STNS01-6-2020-03-31-BEE-P6-3.pdf
similarity index 100%
rename from doc/ECPMilestones/STNS01-6-2020-03-31-BEE-P6-3.pdf
rename to docs/ECPMilestones/STNS01-6-2020-03-31-BEE-P6-3.pdf
diff --git a/doc/README.md b/docs/README.md
similarity index 100%
rename from doc/README.md
rename to docs/README.md
diff --git a/doc/configuration.md b/docs/configuration.md
similarity index 100%
rename from doc/configuration.md
rename to docs/configuration.md
diff --git a/doc/html/.buildinfo b/docs/html/.buildinfo
similarity index 100%
rename from doc/html/.buildinfo
rename to docs/html/.buildinfo
diff --git a/doc/html/_sources/api/wf_api.rst.txt b/docs/html/_sources/api/wf_api.rst.txt
similarity index 100%
rename from doc/html/_sources/api/wf_api.rst.txt
rename to docs/html/_sources/api/wf_api.rst.txt
diff --git a/doc/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt
similarity index 100%
rename from doc/html/_sources/index.rst.txt
rename to docs/html/_sources/index.rst.txt
diff --git a/doc/html/_static/basic.css b/docs/html/_static/basic.css
similarity index 100%
rename from doc/html/_static/basic.css
rename to docs/html/_static/basic.css
diff --git a/doc/html/_static/css/badge_only.css b/docs/html/_static/css/badge_only.css
similarity index 100%
rename from doc/html/_static/css/badge_only.css
rename to docs/html/_static/css/badge_only.css
diff --git a/doc/html/_static/css/theme.css b/docs/html/_static/css/theme.css
similarity index 100%
rename from doc/html/_static/css/theme.css
rename to docs/html/_static/css/theme.css
diff --git a/doc/html/_static/doctools.js b/docs/html/_static/doctools.js
similarity index 100%
rename from doc/html/_static/doctools.js
rename to docs/html/_static/doctools.js
diff --git a/doc/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js
similarity index 100%
rename from doc/html/_static/documentation_options.js
rename to docs/html/_static/documentation_options.js
diff --git a/doc/html/_static/file.png b/docs/html/_static/file.png
similarity index 100%
rename from doc/html/_static/file.png
rename to docs/html/_static/file.png
diff --git a/doc/html/_static/fonts/Inconsolata-Bold.ttf b/docs/html/_static/fonts/Inconsolata-Bold.ttf
similarity index 100%
rename from doc/html/_static/fonts/Inconsolata-Bold.ttf
rename to docs/html/_static/fonts/Inconsolata-Bold.ttf
diff --git a/doc/html/_static/fonts/Inconsolata-Regular.ttf b/docs/html/_static/fonts/Inconsolata-Regular.ttf
similarity index 100%
rename from doc/html/_static/fonts/Inconsolata-Regular.ttf
rename to docs/html/_static/fonts/Inconsolata-Regular.ttf
diff --git a/doc/html/_static/fonts/Inconsolata.ttf b/docs/html/_static/fonts/Inconsolata.ttf
similarity index 100%
rename from doc/html/_static/fonts/Inconsolata.ttf
rename to docs/html/_static/fonts/Inconsolata.ttf
diff --git a/doc/html/_static/fonts/Lato-Bold.ttf b/docs/html/_static/fonts/Lato-Bold.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato-Bold.ttf
rename to docs/html/_static/fonts/Lato-Bold.ttf
diff --git a/doc/html/_static/fonts/Lato-Regular.ttf b/docs/html/_static/fonts/Lato-Regular.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato-Regular.ttf
rename to docs/html/_static/fonts/Lato-Regular.ttf
diff --git a/doc/html/_static/fonts/Lato/lato-bold.eot b/docs/html/_static/fonts/Lato/lato-bold.eot
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bold.eot
rename to docs/html/_static/fonts/Lato/lato-bold.eot
diff --git a/doc/html/_static/fonts/Lato/lato-bold.ttf b/docs/html/_static/fonts/Lato/lato-bold.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bold.ttf
rename to docs/html/_static/fonts/Lato/lato-bold.ttf
diff --git a/doc/html/_static/fonts/Lato/lato-bold.woff b/docs/html/_static/fonts/Lato/lato-bold.woff
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bold.woff
rename to docs/html/_static/fonts/Lato/lato-bold.woff
diff --git a/doc/html/_static/fonts/Lato/lato-bold.woff2 b/docs/html/_static/fonts/Lato/lato-bold.woff2
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bold.woff2
rename to docs/html/_static/fonts/Lato/lato-bold.woff2
diff --git a/doc/html/_static/fonts/Lato/lato-bolditalic.eot b/docs/html/_static/fonts/Lato/lato-bolditalic.eot
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bolditalic.eot
rename to docs/html/_static/fonts/Lato/lato-bolditalic.eot
diff --git a/doc/html/_static/fonts/Lato/lato-bolditalic.ttf b/docs/html/_static/fonts/Lato/lato-bolditalic.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bolditalic.ttf
rename to docs/html/_static/fonts/Lato/lato-bolditalic.ttf
diff --git a/doc/html/_static/fonts/Lato/lato-bolditalic.woff b/docs/html/_static/fonts/Lato/lato-bolditalic.woff
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bolditalic.woff
rename to docs/html/_static/fonts/Lato/lato-bolditalic.woff
diff --git a/doc/html/_static/fonts/Lato/lato-bolditalic.woff2 b/docs/html/_static/fonts/Lato/lato-bolditalic.woff2
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-bolditalic.woff2
rename to docs/html/_static/fonts/Lato/lato-bolditalic.woff2
diff --git a/doc/html/_static/fonts/Lato/lato-italic.eot b/docs/html/_static/fonts/Lato/lato-italic.eot
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-italic.eot
rename to docs/html/_static/fonts/Lato/lato-italic.eot
diff --git a/doc/html/_static/fonts/Lato/lato-italic.ttf b/docs/html/_static/fonts/Lato/lato-italic.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-italic.ttf
rename to docs/html/_static/fonts/Lato/lato-italic.ttf
diff --git a/doc/html/_static/fonts/Lato/lato-italic.woff b/docs/html/_static/fonts/Lato/lato-italic.woff
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-italic.woff
rename to docs/html/_static/fonts/Lato/lato-italic.woff
diff --git a/doc/html/_static/fonts/Lato/lato-italic.woff2 b/docs/html/_static/fonts/Lato/lato-italic.woff2
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-italic.woff2
rename to docs/html/_static/fonts/Lato/lato-italic.woff2
diff --git a/doc/html/_static/fonts/Lato/lato-regular.eot b/docs/html/_static/fonts/Lato/lato-regular.eot
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-regular.eot
rename to docs/html/_static/fonts/Lato/lato-regular.eot
diff --git a/doc/html/_static/fonts/Lato/lato-regular.ttf b/docs/html/_static/fonts/Lato/lato-regular.ttf
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-regular.ttf
rename to docs/html/_static/fonts/Lato/lato-regular.ttf
diff --git a/doc/html/_static/fonts/Lato/lato-regular.woff b/docs/html/_static/fonts/Lato/lato-regular.woff
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-regular.woff
rename to docs/html/_static/fonts/Lato/lato-regular.woff
diff --git a/doc/html/_static/fonts/Lato/lato-regular.woff2 b/docs/html/_static/fonts/Lato/lato-regular.woff2
similarity index 100%
rename from doc/html/_static/fonts/Lato/lato-regular.woff2
rename to docs/html/_static/fonts/Lato/lato-regular.woff2
diff --git a/doc/html/_static/fonts/RobotoSlab-Bold.ttf b/docs/html/_static/fonts/RobotoSlab-Bold.ttf
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab-Bold.ttf
rename to docs/html/_static/fonts/RobotoSlab-Bold.ttf
diff --git a/doc/html/_static/fonts/RobotoSlab-Regular.ttf b/docs/html/_static/fonts/RobotoSlab-Regular.ttf
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab-Regular.ttf
rename to docs/html/_static/fonts/RobotoSlab-Regular.ttf
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff
diff --git a/doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
similarity index 100%
rename from doc/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
rename to docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2
diff --git a/doc/html/_static/fonts/fontawesome-webfont.eot b/docs/html/_static/fonts/fontawesome-webfont.eot
similarity index 100%
rename from doc/html/_static/fonts/fontawesome-webfont.eot
rename to docs/html/_static/fonts/fontawesome-webfont.eot
diff --git a/doc/html/_static/fonts/fontawesome-webfont.svg b/docs/html/_static/fonts/fontawesome-webfont.svg
similarity index 100%
rename from doc/html/_static/fonts/fontawesome-webfont.svg
rename to docs/html/_static/fonts/fontawesome-webfont.svg
diff --git a/doc/html/_static/fonts/fontawesome-webfont.ttf b/docs/html/_static/fonts/fontawesome-webfont.ttf
similarity index 100%
rename from doc/html/_static/fonts/fontawesome-webfont.ttf
rename to docs/html/_static/fonts/fontawesome-webfont.ttf
diff --git a/doc/html/_static/fonts/fontawesome-webfont.woff b/docs/html/_static/fonts/fontawesome-webfont.woff
similarity index 100%
rename from doc/html/_static/fonts/fontawesome-webfont.woff
rename to docs/html/_static/fonts/fontawesome-webfont.woff
diff --git a/doc/html/_static/fonts/fontawesome-webfont.woff2 b/docs/html/_static/fonts/fontawesome-webfont.woff2
similarity index 100%
rename from doc/html/_static/fonts/fontawesome-webfont.woff2
rename to docs/html/_static/fonts/fontawesome-webfont.woff2
diff --git a/doc/html/_static/jquery-3.2.1.js b/docs/html/_static/jquery-3.2.1.js
similarity index 100%
rename from doc/html/_static/jquery-3.2.1.js
rename to docs/html/_static/jquery-3.2.1.js
diff --git a/doc/html/_static/jquery.js b/docs/html/_static/jquery.js
similarity index 100%
rename from doc/html/_static/jquery.js
rename to docs/html/_static/jquery.js
diff --git a/doc/html/_static/js/modernizr.min.js b/docs/html/_static/js/modernizr.min.js
similarity index 100%
rename from doc/html/_static/js/modernizr.min.js
rename to docs/html/_static/js/modernizr.min.js
diff --git a/doc/html/_static/js/theme.js b/docs/html/_static/js/theme.js
similarity index 100%
rename from doc/html/_static/js/theme.js
rename to docs/html/_static/js/theme.js
diff --git a/doc/html/_static/language_data.js b/docs/html/_static/language_data.js
similarity index 100%
rename from doc/html/_static/language_data.js
rename to docs/html/_static/language_data.js
diff --git a/doc/html/_static/minus.png b/docs/html/_static/minus.png
similarity index 100%
rename from doc/html/_static/minus.png
rename to docs/html/_static/minus.png
diff --git a/doc/html/_static/plus.png b/docs/html/_static/plus.png
similarity index 100%
rename from doc/html/_static/plus.png
rename to docs/html/_static/plus.png
diff --git a/doc/html/_static/pygments.css b/docs/html/_static/pygments.css
similarity index 100%
rename from doc/html/_static/pygments.css
rename to docs/html/_static/pygments.css
diff --git a/doc/html/_static/searchtools.js b/docs/html/_static/searchtools.js
similarity index 100%
rename from doc/html/_static/searchtools.js
rename to docs/html/_static/searchtools.js
diff --git a/doc/html/_static/underscore-1.3.1.js b/docs/html/_static/underscore-1.3.1.js
similarity index 100%
rename from doc/html/_static/underscore-1.3.1.js
rename to docs/html/_static/underscore-1.3.1.js
diff --git a/doc/html/_static/underscore.js b/docs/html/_static/underscore.js
similarity index 100%
rename from doc/html/_static/underscore.js
rename to docs/html/_static/underscore.js
diff --git a/doc/html/api/wf_api.html b/docs/html/api/wf_api.html
similarity index 100%
rename from doc/html/api/wf_api.html
rename to docs/html/api/wf_api.html
diff --git a/doc/html/genindex.html b/docs/html/genindex.html
similarity index 100%
rename from doc/html/genindex.html
rename to docs/html/genindex.html
diff --git a/doc/html/index.html b/docs/html/index.html
similarity index 100%
rename from doc/html/index.html
rename to docs/html/index.html
diff --git a/doc/html/objects.inv b/docs/html/objects.inv
similarity index 100%
rename from doc/html/objects.inv
rename to docs/html/objects.inv
diff --git a/doc/html/py-modindex.html b/docs/html/py-modindex.html
similarity index 100%
rename from doc/html/py-modindex.html
rename to docs/html/py-modindex.html
diff --git a/doc/html/search.html b/docs/html/search.html
similarity index 100%
rename from doc/html/search.html
rename to docs/html/search.html
diff --git a/doc/html/searchindex.js b/docs/html/searchindex.js
similarity index 100%
rename from doc/html/searchindex.js
rename to docs/html/searchindex.js
diff --git a/doc/neo4j/.gitignore b/docs/neo4j/.gitignore
similarity index 100%
rename from doc/neo4j/.gitignore
rename to docs/neo4j/.gitignore
diff --git a/doc/neo4j/README.md b/docs/neo4j/README.md
similarity index 100%
rename from doc/neo4j/README.md
rename to docs/neo4j/README.md
diff --git a/doc/neo4j/ch-grow-files/Dockerfile b/docs/neo4j/ch-grow-files/Dockerfile
similarity index 100%
rename from doc/neo4j/ch-grow-files/Dockerfile
rename to docs/neo4j/ch-grow-files/Dockerfile
diff --git a/doc/neo4j/ch-grow-files/ch-run-neo4j.sh b/docs/neo4j/ch-grow-files/ch-run-neo4j.sh
similarity index 100%
rename from doc/neo4j/ch-grow-files/ch-run-neo4j.sh
rename to docs/neo4j/ch-grow-files/ch-run-neo4j.sh
diff --git a/doc/neo4j/ch-grow-files/environment b/docs/neo4j/ch-grow-files/environment
similarity index 100%
rename from doc/neo4j/ch-grow-files/environment
rename to docs/neo4j/ch-grow-files/environment
diff --git a/doc/neo4j/img/5node.png b/docs/neo4j/img/5node.png
similarity index 100%
rename from doc/neo4j/img/5node.png
rename to docs/neo4j/img/5node.png
diff --git a/doc/neo4j/img/cypher_pane.png b/docs/neo4j/img/cypher_pane.png
similarity index 100%
rename from doc/neo4j/img/cypher_pane.png
rename to docs/neo4j/img/cypher_pane.png
diff --git a/doc/neo4j/img/graph_pane.png b/docs/neo4j/img/graph_pane.png
similarity index 100%
rename from doc/neo4j/img/graph_pane.png
rename to docs/neo4j/img/graph_pane.png
diff --git a/doc/neo4j/img/neo4j_browser.png b/docs/neo4j/img/neo4j_browser.png
similarity index 100%
rename from doc/neo4j/img/neo4j_browser.png
rename to docs/neo4j/img/neo4j_browser.png
diff --git a/doc/poetry_old.md b/docs/poetry_old.md
similarity index 100%
rename from doc/poetry_old.md
rename to docs/poetry_old.md
diff --git a/doc/poetry_tutorial/README.md b/docs/poetry_tutorial/README.md
similarity index 100%
rename from doc/poetry_tutorial/README.md
rename to docs/poetry_tutorial/README.md
diff --git a/doc-src/Makefile b/docs/sphinx/Makefile
similarity index 100%
rename from doc-src/Makefile
rename to docs/sphinx/Makefile
diff --git a/doc-src/README.md b/docs/sphinx/README.md
similarity index 100%
rename from doc-src/README.md
rename to docs/sphinx/README.md
diff --git a/doc-src/api/wf_api.rst b/docs/sphinx/api/wf_api.rst
similarity index 100%
rename from doc-src/api/wf_api.rst
rename to docs/sphinx/api/wf_api.rst
diff --git a/doc-src/conf.py b/docs/sphinx/conf.py
similarity index 100%
rename from doc-src/conf.py
rename to docs/sphinx/conf.py
diff --git a/doc-src/index.rst b/docs/sphinx/index.rst
similarity index 100%
rename from doc-src/index.rst
rename to docs/sphinx/index.rst
diff --git a/doc-src/make.bat b/docs/sphinx/make.bat
similarity index 100%
rename from doc-src/make.bat
rename to docs/sphinx/make.bat
diff --git a/doc/summit.md b/docs/summit.md
similarity index 100%
rename from doc/summit.md
rename to docs/summit.md
diff --git a/pyproject.toml b/pyproject.toml
index 73b530a71..7a04afc87 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,6 +39,9 @@ classifiers = [
     "Topic :: System :: Monitoring"
 ]
 
+[tool.poetry.scripts]
+beeflow = 'beeflow.cli:main'
+
 [tool.poetry.dependencies]
 # Python version (>=3.6.8, <4.0.0)
 python = "^3.6.8"
diff --git a/bin/BEEStart b/src/beeflow/cli.py
similarity index 79%
rename from bin/BEEStart
rename to src/beeflow/cli.py
index 34c326d47..208933b1a 100755
--- a/bin/BEEStart
+++ b/src/beeflow/cli.py
@@ -11,7 +11,6 @@
 
 import argparse
 import getpass
-import logging
 import os
 import shutil
 import subprocess
@@ -21,28 +20,26 @@
 import platform
 from subprocess import PIPE
 from configparser import NoOptionError
-from beeflow.common.config.config_driver import BeeConfig
+import beeflow.common.log as bee_logging
+from beeflow.common.config_driver import BeeConfig
 
-BEESTART = os.path.splitext(__file__)[0]
+log = bee_logging.setup_logging(level='DEBUG')
+restd_log = bee_logging.setup_logging(level='DEBUG') 
+gdb_log = bee_logging.setup_logging(level='DEBUG')
 
 def StartGDB(bc, args):
     """Start the graph database. Returns a Popen process object."""
-    log = logging.getLogger(BEESTART)
+    gdb_handler = bee_logging.save_log(bc, gdb_log, logfile='gdb_launch.log')
     # Load gdb config from config file if exists
     try:
         bc.userconfig['graphdb']
     except KeyError:
-        if platform.system() == 'Windows':
-            # Would prefer something like a uid for windows.
-            offset = os.getppid()%100
-        else:
-            offset = os.getuid()%100
         graphdb_dict = {
             'hostname': 'localhost',
             'dbpass': 'password',
-            'bolt_port': 7687+offset,
-            'http_port': 7474+offset,
-            'https_port': 7473+offset,
+            'bolt_port': bc.default_bolt_port,
+            'http_port': bc.default_http_port,
+            'https_port': bc.default_https_port ,
             'gdb_image': '/usr/projects/beedev/neo4j-3-5-17-ch.tar.gz',
             'gdb_image_mntdir': '/tmp',
         }
@@ -51,7 +48,7 @@ def StartGDB(bc, args):
     if args.config_only:
        return None
     if shutil.which("ch-tar2dir") == None or shutil.which("ch-run") == None:
-        log.error("ch-tar2dir or ch-run not found. Charliecloud required for neo4j container.")
+        gdb_log.error("ch-tar2dir or ch-run not found. Charliecloud required for neo4j container.")
         return None
 
     # Setup subprocess output
@@ -70,16 +67,15 @@ def StartGDB(bc, args):
 
     container_dir = tempfile.mkdtemp(suffix="_" + getpass.getuser(), prefix="gdb_", dir=str(gdb_img_mntdir))
     if args.debug:
-        log.info("GraphDB container mount directory " + container_dir + " created")
+        gdb_log.info("GraphDB container mount directory " + container_dir + " created")
 
     try:
         cp = subprocess.run(["ch-tar2dir",str(gdb_img),str(container_dir)], stdout=stdout, stderr=stderr, check=True)
     except subprocess.CalledProcessError as cp:
-        log.error("ch-tar2dir failed")
-        print("ch-tar2dir failed", file=sys.stderr)
+        gdb_log.error("ch-tar2dir failed")
         shutil.rmtree(container_dir)
         if args.debug:
-            log.error("GraphDB container mount directory " + container_dir + " removed")
+            gdb_log.error("GraphDB container mount directory " + container_dir + " removed")
         return None
 
     newdir = os.path.split(container_dir)[1]
@@ -89,7 +85,7 @@ def StartGDB(bc, args):
     container_certs_path = os.path.join(container_path, 'var/lib/neo4j/certificates')
     os.makedirs(container_certs_path, exist_ok=True)
     if args.debug:
-        log.info('Created certificates directory %s', container_certs_path)
+        gdb_log.info('Created certificates directory %s', container_certs_path)
     # Setup working path data
     gdb_workdir = os.path.join(bc.userconfig.get('DEFAULT','bee_workdir'),
                                newdir)
@@ -97,7 +93,7 @@ def StartGDB(bc, args):
     os.makedirs(gdb_config_path, exist_ok=True)
     gdb_configfile = shutil.copyfile(container_path + "/var/lib/neo4j/conf/neo4j.conf", gdb_config_path + "/neo4j.conf")
     if args.debug:
-        log.info(gdb_configfile)
+        gdb_log.info(gdb_configfile)
 
     cfile = open(gdb_configfile, "rt")
     data = cfile.read()
@@ -137,7 +133,7 @@ def StartGDB(bc, args):
             str(db_password)
         ], stdout=stdout, stderr=stderr, check=True)
     except subprocess.CalledProcessError as cp:
-        log.error("neo4j-admin set-initial-password failed")
+        gdb_log.error("neo4j-admin set-initial-password failed")
         print("neo4j-admin set-initial-password failed", file=sys.stderr)
         return None
 
@@ -161,8 +157,7 @@ def StartGDB(bc, args):
             "start",
         ], stdout=stdout, stderr=stderr)
     except FileNotFoundError as e:
-        log.error("neo4j failed to start.")
-        print("neo4j failed to start.", file=sys.stderr)
+        gdb_log.error("neo4j failed to start.")
         return None
 
     return proc
@@ -171,33 +166,19 @@ def StartGDB(bc, args):
 def StartSlurmRestD(bc, args):
     """Start BEESlurmRestD. Returns a Popen process object."""
 
-    log = logging.getLogger(BEESTART)
+    restd_handler = bee_logging.save_log(bc, restd_log, logfile='restd.log')
+    slurmrestd_log = '/'.join([bc.userconfig.get('DEFAULT','bee_workdir'),'logs','restd.log']) 
     # Load gdb config from config file if exists
     try:
         bc.userconfig['slurmrestd']
     except KeyError:
-        if platform.system() == 'Windows':
-            # Would prefer something like a uid for windows.
-            offset = os.getppid()%100
-        else:
-            offset = os.getuid()%100
         restd_dict = {
-            'slurm_socket': '/tmp/slurm_{}_{}.sock'.format(os.getlogin(), 100 + offset),
+            'slurm_socket': '/tmp/slurm_{}_{}.sock'.format(os.getlogin(), 100 + bc.offset),
         }
         # Add section (writes to config file)
         bc.modify_section('user','slurmrestd',restd_dict)
     if args.config_only:
         return None
-    # Try accessing the log path from config file, create if not there
-    # Perhaps better to do this when adding default slurm socket path?
-    try:
-        bc.userconfig.get('slurmrestd','log')
-    except NoOptionError:
-        bc.modify_section('user','slurmrestd',
-                          {'log':'/'.join([bc.userconfig['DEFAULT'].get('bee_workdir'),
-                                           'logs', 'slurmrestd.log'])})
-    finally:
-        slurmrestd_log = bc.userconfig.get('slurmrestd','log')
     slurm_socket = bc.userconfig.get('slurmrestd','slurm_socket')
     subprocess.Popen(['rm','-f',slurm_socket])
     log.info("Attempting to open socket: {}".format(slurm_socket))
@@ -211,13 +192,8 @@ def StartWorkflowManager(bc, args):
     try:
         bc.userconfig['workflow_manager']
     except KeyError:
-        if platform.system() == 'Windows':
-            # Would prefer something like a uid for windows.
-            offset = os.getppid()%100
-        else:
-            offset = os.getuid()%100
         wfm_dict = {
-            'listen_port': 5000 + offset,
+            'listen_port': bc.default_wfm_port,
         }
         # Add section (writes to config file)
         bc.modify_section('user','workflow_manager',wfm_dict)
@@ -230,7 +206,7 @@ def StartWorkflowManager(bc, args):
         userconfig_file = args.userconfig_file
     else:
         userconfig_file = os.path.expanduser('~/.config/beeflow/bee.conf')
-    return subprocess.Popen(["python", 'beeflow/wfm/wfm.py',
+    return subprocess.Popen(["python", 'beeflow/wf_manager.py',
                             userconfig_file],
                             stdout=PIPE, stderr=PIPE)
 
@@ -240,24 +216,20 @@ def StartTaskManager(bc, args):
     try:
         bc.userconfig['task_manager']
     except KeyError:
-        if platform.system() == 'Windows':
-            # Would prefer something like a uid for windows.
-            offset = os.getppid()%100
-        else:
-            offset = os.getuid()%100
         if args.job_template:
             job_template = args.job_template
         tm_dict = {
-            'listen_port': 5050 + offset,
+            'listen_port': bc.default_tm_port,
             'container_runtime': 'Charliecloud'
         }
         # Add section (writes to config file)
         bc.modify_section('user','task_manager',tm_dict)
-        return None
     finally:
         if args.job_template:
             tm_dict= {'job_template': args.job_template}
             bc.modify_section('user','task_manager',tm_dict)
+    if args.config_only:
+        return None
 
     # Either use the userconfig file argument specified to BEEStart,
     # or assume the default path to ~/.config/beeflow/bee.conf.
@@ -265,7 +237,7 @@ def StartTaskManager(bc, args):
         userconfig_file = args.userconfig_file
     else:
         userconfig_file = os.path.expanduser('~/.config/beeflow/bee.conf')
-    return subprocess.Popen(["python", 'beeflow/task_manager/task_manager.py',
+    return subprocess.Popen(["python", 'beeflow/task_manager.py',
                             userconfig_file],
                             stdout=PIPE, stderr=PIPE)
 
@@ -279,16 +251,11 @@ def StartScheduler(bc, args):
     try:
         bc.userconfig['scheduler']
     except KeyError:
-        if platform.system() == 'Windows':
-            # Would prefer something like a uid for windows.
-            offset = os.getppid()%100
-        else:
-            offset = os.getuid()%100
-        wfm_dict = {
-            'listen_port': 5100 + offset,
+        sched_dict = {
+            'listen_port': bc.default_sched_port
         }
         # Add section (writes to config file)
-        bc.modify_section('user','scheduler',wfm_dict)
+        bc.modify_section('user','scheduler',sched_dict)
 
     if args.config_only:
         return None
@@ -308,28 +275,6 @@ def create_pid_file(proc, pid_file, bc):
     with open('{}/{}'.format(str(bc.userconfig.get('DEFAULT','bee_workdir')),pid_file), 'w') as fp:
         fp.write(str(proc.pid))
 
-def setup_logging(bc, debug=False):
-    """
-    Setup logging. Add default values to the config if not found. Return None on
-    error and the log otherwise.
-    """
-    if debug:
-        # Output everything to the console
-        logging.basicConfig(level=logging.DEBUG)
-    else:
-        default = bc.userconfig['DEFAULT']
-        bee_workdir = default.get('bee_workdir', '')
-        logdir = default.get('logdir', os.path.join(bee_workdir, 'logs'))
-        # Make the logdir if it doesn't exist already
-        os.makedirs(logdir, exist_ok=True)
-        logfile = default.get('logfile', 'bee.log')
-        path = os.path.join(bee_workdir, logdir) if logdir else bee_workdir
-        path = os.path.join(path, logfile)
-        # Turn on the default stream handler so we print to a file and stderr
-        logging.getLogger().addHandler(logging.StreamHandler())
-        logging.basicConfig(filename=path)
-    return logging.getLogger(BEESTART)
-
 def parse_args(args=sys.argv[1:]):
     """Parse arguments."""
     parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__,
@@ -374,10 +319,9 @@ def main():
         bc.modify_section('user', 'DEFAULT', {'bee_workdir':bc.resolve_path(args.bee_workdir)} )
     # If workload_scheduler argument exists, over-write
     if args.workload_scheduler:
-        bc.modify_section('user', 'DEFAULT', {'workload_scheduler':args.workload_scheduler} )
-
+        bc.modify_section('user', 'task_manager', {'workload_scheduler':args.workload_scheduler} )
     # Setup logging based on args.debug
-    log = setup_logging(bc, args.debug)
+    handler = bee_logging.save_log(bc, log, logfile='beeflow.log')
     if log is None:
         # Something went wrong
         return 1
diff --git a/beeflow/client/README.md b/src/beeflow/client/README.md
similarity index 100%
rename from beeflow/client/README.md
rename to src/beeflow/client/README.md
diff --git a/beeflow/client/client.py b/src/beeflow/client/client.py
similarity index 98%
rename from beeflow/client/client.py
rename to src/beeflow/client/client.py
index 824197463..bbdbaeba3 100755
--- a/beeflow/client/client.py
+++ b/src/beeflow/client/client.py
@@ -7,7 +7,7 @@
 import platform
 from errors import ApiError
 from pathlib import Path
-from beeflow.common.config.config_driver import BeeConfig
+from beeflow.common.config_driver import BeeConfig
 
 try:
     bc = BeeConfig(userconfig=sys.argv[1])
diff --git a/beeflow/client/client.sh b/src/beeflow/client/client.sh
similarity index 100%
rename from beeflow/client/client.sh
rename to src/beeflow/client/client.sh
diff --git a/beeflow/client/errors.py b/src/beeflow/client/errors.py
similarity index 100%
rename from beeflow/client/errors.py
rename to src/beeflow/client/errors.py
diff --git a/beeflow/common/README.md b/src/beeflow/common/README.md
similarity index 100%
rename from beeflow/common/README.md
rename to src/beeflow/common/README.md
diff --git a/beeflow/common/build/README.md b/src/beeflow/common/build/README.md
similarity index 93%
rename from beeflow/common/build/README.md
rename to src/beeflow/common/build/README.md
index 971a7c938..8a6ef01f0 100644
--- a/beeflow/common/build/README.md
+++ b/src/beeflow/common/build/README.md
@@ -45,8 +45,8 @@ A few examples to use for testing:
 ## CharliecloudBuildDriver Examples
 ### dockerPull
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  requirements={'DockerRequirement':{'dockerPull':'git.lanl.gov:5050/qwofford/containerhub/lstopo'}},
                  subworkflow=None,
@@ -76,8 +76,8 @@ a.dockerPull('git.lanl.gov:5050/qwofford/containerhub/lstopo',force=True)
 ```
 ### dockerFile
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  requirements={'DockerRequirement':{'dockerFile':'FROM git.lanl.gov:5050/trandles/baseimages/centos:7\nCMD cat /etc/centos-release',
                                                     'dockerImageId':'my_fun_container:sillytag'}},
@@ -89,8 +89,8 @@ b.dockerFile()
 ```
 ### dockerImport
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  requirements={},
                  subworkflow=None,
@@ -125,8 +125,8 @@ a.dockerImport()
 ```
 ### dockerOutputDirectory
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  hints={'DockerRequirement':{'dockerImport':'/usr/projects/beedev/neo4j-3-5-17-ch.tar.gz'}},
                  subworkflow=None,
@@ -146,8 +146,8 @@ a.dockerOutputDirectory()
 ```
 ### dockerLoad
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  hints={'DockerRequirement':{'dockerLoad':'bogus path'}},
                  subworkflow=None,
@@ -172,8 +172,8 @@ a.dockerLoad()
 ```
 ### dockerImageId
 ```
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
-from beeflow.common.data.wf_data import BuildTask
+from common.build.container_drivers import CharliecloudBuildDriver
+from common.data.wf_data import BuildTask
 task = BuildTask(name='hi',command=['hi','hello'],
                  hints={'DockerRequirement':{'dockerImageId':'my_imageid'}},
                  subworkflow=None,
diff --git a/beeflow/common/build/build_driver.py b/src/beeflow/common/build/build_driver.py
similarity index 100%
rename from beeflow/common/build/build_driver.py
rename to src/beeflow/common/build/build_driver.py
diff --git a/beeflow/common/build/container_drivers.py b/src/beeflow/common/build/container_drivers.py
similarity index 99%
rename from beeflow/common/build/container_drivers.py
rename to src/beeflow/common/build/container_drivers.py
index cbf637460..7e741a814 100644
--- a/beeflow/common/build/container_drivers.py
+++ b/src/beeflow/common/build/container_drivers.py
@@ -8,8 +8,8 @@
 import tempfile
 import shutil
 import subprocess
-from beeflow.common.config.config_driver import BeeConfig
-# from beeflow.common.crt.crt_drivers import CharliecloudDriver, SingularityDriver
+from common.config_driver import BeeConfig
+# from common.crt.crt_drivers import CharliecloudDriver, SingularityDriver
 
 
 class ContainerBuildDriver(ABC):
diff --git a/beeflow/common/build/build_interfaces.py b/src/beeflow/common/build_interfaces.py
similarity index 80%
rename from beeflow/common/build/build_interfaces.py
rename to src/beeflow/common/build_interfaces.py
index 51cdf488a..442047f36 100644
--- a/beeflow/common/build/build_interfaces.py
+++ b/src/beeflow/common/build_interfaces.py
@@ -6,10 +6,10 @@
 components of the gdb_interface as required.
 """
 
-# from beeflow.common.gdb.gdb_interface import GraphDatabaseInterface
-# from beeflow.common.build.container_drivers import CharliecloudBuildDriver,
+# from common.gdb.gdb_interface import GraphDatabaseInterface
+# from common.build.container_drivers import CharliecloudBuildDriver,
 #                                                    SingularityBuildDriver
-from beeflow.common.build.container_drivers import CharliecloudBuildDriver
+from common.build.container_drivers import CharliecloudBuildDriver
 
 
 class BuildInterfaceTM:
diff --git a/beeflow/common/config/config_driver.py b/src/beeflow/common/config_driver.py
similarity index 89%
rename from beeflow/common/config/config_driver.py
rename to src/beeflow/common/config_driver.py
index fceba3149..0ab7d67a9 100644
--- a/beeflow/common/config/config_driver.py
+++ b/src/beeflow/common/config_driver.py
@@ -68,10 +68,9 @@ def __init__(self, **kwargs):
                 # Make sure conf_file path exists
                 os.makedirs(os.path.dirname(self.userconfig_file),
                             exist_ok=True)
-            except PermissionError as e:
-                raise PermissionError('Do you have write access to {}?'.\
-                                       format(conf_file)) from e
-            # Set default bee_workdir relative to HOME unless otherwise specified.
+            except PermissionError as error:
+                file = self.userconfig_file
+                raise PermissionError(f'Do you have write access to {file}?') from error
             try:
                 self.bee_workdir = kwargs['bee_workdir']
             except KeyError:
@@ -93,7 +92,19 @@ def __init__(self, **kwargs):
             self.modify_section('user',
                                 'DEFAULT',
                                 {'workload_scheduler': str(self.workload_scheduler)})
-
+        # Set up default ports, get parent's pid to offset ports.
+        # Windows note: uid method better but not available in Windows
+        if platform.system() == 'Windows':
+            # Would prefer something like a uid for windows.
+            self.offset = os.getppid() % 100
+        else:
+            self.offset = os.getuid() % 100
+        self.default_bolt_port = 7687 + self.offset
+        self.default_http_port = 7474 + self.offset
+        self.default_https_port = 7473 + self.offset
+        self.default_wfm_port = 5000 + self.offset
+        self.default_tm_port = 5050 + self.offset
+        self.default_sched_port = 5100 + self.offset
 
     def modify_section(self, conf, section, keyvalue, replace=False):
         """Add a new section to the system or user config file.
diff --git a/beeflow/common/crt/crt_drivers.py b/src/beeflow/common/crt/crt_drivers.py
similarity index 98%
rename from beeflow/common/crt/crt_drivers.py
rename to src/beeflow/common/crt/crt_drivers.py
index 20b022d10..43159ff6a 100644
--- a/beeflow/common/crt/crt_drivers.py
+++ b/src/beeflow/common/crt/crt_drivers.py
@@ -5,7 +5,7 @@
 from abc import ABC, abstractmethod
 import os
 from configparser import NoOptionError
-from beeflow.common.config.config_driver import BeeConfig
+from beeflow.common.config_driver import BeeConfig
 bc = BeeConfig()
 
 
diff --git a/beeflow/common/crt/crt_interface.py b/src/beeflow/common/crt_interface.py
similarity index 100%
rename from beeflow/common/crt/crt_interface.py
rename to src/beeflow/common/crt_interface.py
diff --git a/beeflow/common/gdb/gdb_driver.py b/src/beeflow/common/gdb/gdb_driver.py
similarity index 100%
rename from beeflow/common/gdb/gdb_driver.py
rename to src/beeflow/common/gdb/gdb_driver.py
diff --git a/beeflow/common/gdb/neo4j_cypher.py b/src/beeflow/common/gdb/neo4j_cypher.py
similarity index 100%
rename from beeflow/common/gdb/neo4j_cypher.py
rename to src/beeflow/common/gdb/neo4j_cypher.py
diff --git a/beeflow/common/gdb/neo4j_driver.py b/src/beeflow/common/gdb/neo4j_driver.py
similarity index 99%
rename from beeflow/common/gdb/neo4j_driver.py
rename to src/beeflow/common/gdb/neo4j_driver.py
index c84512001..ef8cbbd6a 100644
--- a/beeflow/common/gdb/neo4j_driver.py
+++ b/src/beeflow/common/gdb/neo4j_driver.py
@@ -10,7 +10,7 @@
 
 from beeflow.common.gdb.gdb_driver import GraphDatabaseDriver
 from beeflow.common.gdb import neo4j_cypher as tx
-from beeflow.common.data.wf_data import Task, Requirement
+from beeflow.common.wf_data import Task, Requirement
 
 # Default Neo4j authentication
 # We may want to instead get these from a config at some point
diff --git a/beeflow/common/gdb/gdb_interface.py b/src/beeflow/common/gdb_interface.py
similarity index 100%
rename from beeflow/common/gdb/gdb_interface.py
rename to src/beeflow/common/gdb_interface.py
diff --git a/src/beeflow/common/log.py b/src/beeflow/common/log.py
new file mode 100644
index 000000000..f1ad92499
--- /dev/null
+++ b/src/beeflow/common/log.py
@@ -0,0 +1,153 @@
+import logging
+import sys
+import os
+
+STEP_INFO = 15
+logging.addLevelName(STEP_INFO, "STEP_INFO")
+
+
+class LogFormatter(logging.Formatter):
+    """Format a string for the log file.
+    
+    This is a place to apply rich text formatting.
+    
+    Level Values are:
+    DEBUG: 10
+    STEP_INFO: 15
+    INFO: 20
+    WARNING: 30
+    ERROR: 40
+    CRITICAL:50
+    """
+
+    # Log Colors
+    BOLD_CYAN = "[01;36m"
+    RESET = "[0m"
+    BOLD_YELLOW = "[01;33m"
+    BOLD_RED = "[01;31m"
+
+    log_format = {
+        "DEBUG": f"{BOLD_CYAN}%(levelname)s: %(msg)s {RESET}",
+        "STEP_INFO": "%(levelname)s: %(msg)s",
+        "INFO": "%(levelname)s: %(msg)s",
+        "WARNING": f"{BOLD_YELLOW}%(levelname)s: %(msg)s{RESET}",
+        "ERROR": f"{BOLD_RED}%(levelname)s: %(msg)s{RESET}",
+        "CRITICAL": f"{BOLD_RED}%(levelname)s: %(msg)s{RESET}",
+    }
+
+    log_format_no_colors = {
+        "DEBUG": "%(levelname)s: %(msg)s ",
+        "STEP_INFO": "%(levelname)s: %(msg)s",
+        "INFO": "%(levelname)s: %(msg)s",
+        "WARNING": "%(levelname)s: %(msg)s",
+        "ERROR": "%(levelname)s: %(msg)s",
+        "CRITICAL": "%(levelname)s: %(msg)s",
+    }
+
+
+    def __init__(self, colors):
+        """Initialize formatted loggers.
+
+        :param colors: Format with or without ANSI Escape Code color formatting
+        :type colors: Bool
+        """
+        self.log_fmt = self.log_format if colors else self.log_format_no_colors
+
+    def format(self, record):
+        """Format record for logging.
+
+        :param record: The part of the log record to extract info from.
+        :type record: logging.LogRecord
+        """
+        fmt = self.log_fmt.get(logging.getLevelName(record.levelno))
+        return logging.Formatter(fmt).format(record)
+
+class BeeLogger(logging.Logger):
+    """ Extend Python logger to handle custom log category."""
+
+    def step_info(self, msg="", *args, **kwargs):
+        """ Log a messagewith severity 'STEP_INFO'.
+
+        :param msg: Message to be logged
+        :type msg: String
+        :param *args: List of non-key worded, variable length args.
+        :type *args: list
+        :param **kwargs: Key-worded, variable length args.
+        :type **kwargs: dict
+        """
+        if self.isEnabledFor(STEP_INFO):
+            self._log(STEP_INFO, msg, args, **kwargs)
+
+class LevelFilter(logging.Filter):
+    """Filters the level that are to be accepted and rejected."""
+
+    def __init__(self, passlevels, reject):
+        self.passlevels = passlevels
+        self.reject = reject
+
+    def filter(self, record):
+        """Returns True and False according to the pass levels and reject value.
+
+        :param record: Record from logs
+        :type record: logging.LogRecord
+        """
+        if self.reject:
+            return record.levelno not in self.passlevels
+        else:
+            return record.levelno in self.passlevels
+
+
+def setup_logging(level="STEP_INFO", colors=True):
+    """Setup logger.
+
+    :param level: Level to be logged in logger.
+    :type level: String
+    """
+    logging.setLoggerClass(BeeLogger)
+    log = logging.getLogger("bee")
+    if log.hasHandlers():
+        log.setLevel(level)
+        return log
+    else:
+        formatter = LogFormatter(colors=colors)
+    
+        # Intermediate step info goes to stdout
+        h1 = logging.StreamHandler(sys.stdout)
+        h1.addFilter(LevelFilter([logging.INFO, STEP_INFO], False))
+        h1.setFormatter(formatter)
+    
+        # All stepinfo goes to stdout
+        h2 = logging.StreamHandler(sys.stdout)
+        h2.addFilter(LevelFilter([logging.INFO, STEP_INFO], True))
+        h2.setFormatter(formatter)
+    
+        log.addHandler(h1)
+        log.addHandler(h2)
+        log.setLevel(level)
+        return log
+
+def save_log(bc, log, logfile):
+    """Set log formatter for handle and add handler to logger.
+
+    :param bc: The BeeConfig object
+    :type bc: beeflow.common.config_driver
+    :param log: The logger object
+    :type log: Logging.logger
+    :param logfile: Path for the logfile
+    :type logfile: String
+    """
+    default = bc.userconfig['DEFAULT']
+    bee_workdir = default.get('bee_workdir', '')
+    logdir = os.path.join(bee_workdir, 'logs')
+    # Make the logdir if it doesn't exist already
+    os.makedirs(logdir, exist_ok=True)
+    path = os.path.join(bee_workdir, logdir)
+    path = os.path.join(path, logfile)
+
+    handler = logging.FileHandler(path)
+    formatter = LogFormatter(colors=False)
+
+    # Set
+    handler.setFormatter(formatter)
+    log.addHandler(handler)
+    return handler
diff --git a/beeflow/common/parser/README.md b/src/beeflow/common/parser/README.md
similarity index 100%
rename from beeflow/common/parser/README.md
rename to src/beeflow/common/parser/README.md
diff --git a/beeflow/common/parser/dump_cwl.py b/src/beeflow/common/parser/dump_cwl.py
similarity index 100%
rename from beeflow/common/parser/dump_cwl.py
rename to src/beeflow/common/parser/dump_cwl.py
diff --git a/beeflow/common/parser/grepcount/gc.cwl b/src/beeflow/common/parser/grepcount/gc.cwl
similarity index 100%
rename from beeflow/common/parser/grepcount/gc.cwl
rename to src/beeflow/common/parser/grepcount/gc.cwl
diff --git a/beeflow/common/parser/lorem.txt b/src/beeflow/common/parser/lorem.txt
similarity index 100%
rename from beeflow/common/parser/lorem.txt
rename to src/beeflow/common/parser/lorem.txt
diff --git a/beeflow/common/parser/parse_clamr.py b/src/beeflow/common/parser/parse_clamr.py
similarity index 100%
rename from beeflow/common/parser/parse_clamr.py
rename to src/beeflow/common/parser/parse_clamr.py
diff --git a/beeflow/common/parser/parse_cwl.py b/src/beeflow/common/parser/parse_cwl.py
similarity index 99%
rename from beeflow/common/parser/parse_cwl.py
rename to src/beeflow/common/parser/parse_cwl.py
index c3781e106..81a1719cf 100755
--- a/beeflow/common/parser/parse_cwl.py
+++ b/src/beeflow/common/parser/parse_cwl.py
@@ -22,7 +22,7 @@
 import argparse
 import uuid
 import cwl_utils.parser_v1_0 as cwl
-from beeflow.common.wf_interface import WorkflowInterface
+from common.wf_interface import WorkflowInterface
 
 
 
diff --git a/beeflow/common/data/wf_data.py b/src/beeflow/common/wf_data.py
similarity index 100%
rename from beeflow/common/data/wf_data.py
rename to src/beeflow/common/wf_data.py
diff --git a/beeflow/common/wf_interface.py b/src/beeflow/common/wf_interface.py
similarity index 98%
rename from beeflow/common/wf_interface.py
rename to src/beeflow/common/wf_interface.py
index 631e27cf4..8b1041470 100644
--- a/beeflow/common/wf_interface.py
+++ b/src/beeflow/common/wf_interface.py
@@ -3,8 +3,8 @@
 Delegates its work to a GraphDatabaseInterface instance.
 """
 
-from beeflow.common.gdb.gdb_interface import GraphDatabaseInterface
-from beeflow.common.data.wf_data import Task, Requirement
+from beeflow.common.gdb_interface import GraphDatabaseInterface
+from beeflow.common.wf_data import Task, Requirement
 
 
 class WorkflowInterface:
diff --git a/beeflow/common/worker/README.md b/src/beeflow/common/worker/README.md
similarity index 100%
rename from beeflow/common/worker/README.md
rename to src/beeflow/common/worker/README.md
diff --git a/beeflow/common/worker/ToDo.md b/src/beeflow/common/worker/ToDo.md
similarity index 100%
rename from beeflow/common/worker/ToDo.md
rename to src/beeflow/common/worker/ToDo.md
diff --git a/beeflow/common/worker/lsf_worker.py b/src/beeflow/common/worker/lsf_worker.py
similarity index 95%
rename from beeflow/common/worker/lsf_worker.py
rename to src/beeflow/common/worker/lsf_worker.py
index 0d8c287c2..258d52368 100644
--- a/beeflow/common/worker/lsf_worker.py
+++ b/src/beeflow/common/worker/lsf_worker.py
@@ -8,16 +8,16 @@
 import subprocess
 
 from beeflow.common.worker.worker import Worker
-from beeflow.common.crt.crt_interface import ContainerRuntimeInterface
+from beeflow.common.crt_interface import ContainerRuntimeInterface
 
 # Import all implemented container runtime drivers now
 # No error if they don't exist
 try:
-    from beeflow.common.crt.crt_drivers import CharliecloudDriver
+    from common.crt.crt_drivers import CharliecloudDriver
 except ModuleNotFoundError:
     pass
 try:
-    from beeflow.common.crt.crt_drivers import SingularityDriver
+    from common.crt.crt_drivers import SingularityDriver
 except ModuleNotFoundError:
     pass
 
diff --git a/beeflow/common/worker/slurm_worker.py b/src/beeflow/common/worker/slurm_worker.py
similarity index 98%
rename from beeflow/common/worker/slurm_worker.py
rename to src/beeflow/common/worker/slurm_worker.py
index c0653e17f..fd28efc00 100644
--- a/beeflow/common/worker/slurm_worker.py
+++ b/src/beeflow/common/worker/slurm_worker.py
@@ -12,7 +12,7 @@
 import requests
 
 from beeflow.common.worker.worker import Worker
-from beeflow.common.crt.crt_interface import ContainerRuntimeInterface
+from beeflow.common.crt_interface import ContainerRuntimeInterface
 
 # Import all implemented container runtime drivers now
 # No error if they don't exist
diff --git a/beeflow/common/worker/worker.py b/src/beeflow/common/worker/worker.py
similarity index 100%
rename from beeflow/common/worker/worker.py
rename to src/beeflow/common/worker/worker.py
diff --git a/beeflow/common/worker/worker_interface.py b/src/beeflow/common/worker_interface.py
similarity index 100%
rename from beeflow/common/worker/worker_interface.py
rename to src/beeflow/common/worker_interface.py
diff --git a/beeflow/client/cancel.cwl b/src/beeflow/data/cwl/cancel.cwl
similarity index 100%
rename from beeflow/client/cancel.cwl
rename to src/beeflow/data/cwl/cancel.cwl
diff --git a/beeflow/client/cf-singularity.cwl b/src/beeflow/data/cwl/cf-singularity.cwl
similarity index 100%
rename from beeflow/client/cf-singularity.cwl
rename to src/beeflow/data/cwl/cf-singularity.cwl
diff --git a/beeflow/client/cf-summit.cwl b/src/beeflow/data/cwl/cf-summit.cwl
similarity index 100%
rename from beeflow/client/cf-summit.cwl
rename to src/beeflow/data/cwl/cf-summit.cwl
diff --git a/beeflow/client/cf.cwl b/src/beeflow/data/cwl/cf.cwl
similarity index 96%
rename from beeflow/client/cf.cwl
rename to src/beeflow/data/cwl/cf.cwl
index 5d6af448b..bf4bb680e 100644
--- a/beeflow/client/cf.cwl
+++ b/src/beeflow/data/cwl/cf.cwl
@@ -21,7 +21,7 @@ steps:
       inputs:
         infile:
           type: File
-          default: lorem.txt
+          default: src/beeflow/data/lorem.txt
           inputBinding: {position: 1}
       outputs:
         outfile: stdout
diff --git a/beeflow/client/echo.cwl b/src/beeflow/data/cwl/echo.cwl
similarity index 100%
rename from beeflow/client/echo.cwl
rename to src/beeflow/data/cwl/echo.cwl
diff --git a/beeflow/client/gc-nc.cwl b/src/beeflow/data/cwl/gc-nc.cwl
similarity index 100%
rename from beeflow/client/gc-nc.cwl
rename to src/beeflow/data/cwl/gc-nc.cwl
diff --git a/beeflow/client/gc.cwl b/src/beeflow/data/cwl/gc.cwl
similarity index 100%
rename from beeflow/client/gc.cwl
rename to src/beeflow/data/cwl/gc.cwl
diff --git a/examples/Dockerfile.clamr-lanl-x86_64 b/src/beeflow/data/examples/Dockerfile.clamr-lanl-x86_64
similarity index 100%
rename from examples/Dockerfile.clamr-lanl-x86_64
rename to src/beeflow/data/examples/Dockerfile.clamr-lanl-x86_64
diff --git a/examples/Dockerfile.clamr-ppc64le b/src/beeflow/data/examples/Dockerfile.clamr-ppc64le
similarity index 100%
rename from examples/Dockerfile.clamr-ppc64le
rename to src/beeflow/data/examples/Dockerfile.clamr-ppc64le
diff --git a/examples/Dockerfile.neo4j-ppc64le b/src/beeflow/data/examples/Dockerfile.neo4j-ppc64le
similarity index 100%
rename from examples/Dockerfile.neo4j-ppc64le
rename to src/beeflow/data/examples/Dockerfile.neo4j-ppc64le
diff --git a/examples/README.md b/src/beeflow/data/examples/README.md
similarity index 100%
rename from examples/README.md
rename to src/beeflow/data/examples/README.md
diff --git a/examples/clamr-ffmpeg/README.md b/src/beeflow/data/examples/clamr-ffmpeg/README.md
similarity index 100%
rename from examples/clamr-ffmpeg/README.md
rename to src/beeflow/data/examples/clamr-ffmpeg/README.md
diff --git a/examples/clamr-ffmpeg/clamr.cwl b/src/beeflow/data/examples/clamr-ffmpeg/clamr.cwl
similarity index 100%
rename from examples/clamr-ffmpeg/clamr.cwl
rename to src/beeflow/data/examples/clamr-ffmpeg/clamr.cwl
diff --git a/examples/clamr-ffmpeg/clamr.yml b/src/beeflow/data/examples/clamr-ffmpeg/clamr.yml
similarity index 100%
rename from examples/clamr-ffmpeg/clamr.yml
rename to src/beeflow/data/examples/clamr-ffmpeg/clamr.yml
diff --git a/examples/clamr-ffmpeg/ffmpeg.cwl b/src/beeflow/data/examples/clamr-ffmpeg/ffmpeg.cwl
similarity index 100%
rename from examples/clamr-ffmpeg/ffmpeg.cwl
rename to src/beeflow/data/examples/clamr-ffmpeg/ffmpeg.cwl
diff --git a/examples/clamr-ffmpeg/ffmpeg.yml b/src/beeflow/data/examples/clamr-ffmpeg/ffmpeg.yml
similarity index 100%
rename from examples/clamr-ffmpeg/ffmpeg.yml
rename to src/beeflow/data/examples/clamr-ffmpeg/ffmpeg.yml
diff --git a/examples/configuration/bee.conf b/src/beeflow/data/examples/configuration/bee.conf
similarity index 100%
rename from examples/configuration/bee.conf
rename to src/beeflow/data/examples/configuration/bee.conf
diff --git a/examples/job_templates/lsf-job.template b/src/beeflow/data/examples/job_templates/lsf-job.template
similarity index 100%
rename from examples/job_templates/lsf-job.template
rename to src/beeflow/data/examples/job_templates/lsf-job.template
diff --git a/lorem.txt b/src/beeflow/data/lorem.txt
similarity index 100%
rename from lorem.txt
rename to src/beeflow/data/lorem.txt
diff --git a/beeflow/scheduler/README.md b/src/beeflow/scheduler/README.md
similarity index 100%
rename from beeflow/scheduler/README.md
rename to src/beeflow/scheduler/README.md
diff --git a/beeflow/scheduler/algorithms.py b/src/beeflow/scheduler/algorithms.py
similarity index 99%
rename from beeflow/scheduler/algorithms.py
rename to src/beeflow/scheduler/algorithms.py
index 6084b1459..11104023f 100644
--- a/beeflow/scheduler/algorithms.py
+++ b/src/beeflow/scheduler/algorithms.py
@@ -186,7 +186,7 @@ def load(mars_model='model', **kwargs):
         :type mars_model: str
         """
         # Only import the mars module if necessary
-        import beeflow.scheduler.mars as mars
+        import scheduler.mars as mars
         MARS.mod = mars
         MARS.actor, MARS.critic = mars.load_models(mars_model)
 
diff --git a/beeflow/scheduler/evaluate.py b/src/beeflow/scheduler/evaluate.py
similarity index 100%
rename from beeflow/scheduler/evaluate.py
rename to src/beeflow/scheduler/evaluate.py
diff --git a/beeflow/scheduler/mars.py b/src/beeflow/scheduler/mars.py
similarity index 99%
rename from beeflow/scheduler/mars.py
rename to src/beeflow/scheduler/mars.py
index 16daccf9f..fa21275ba 100644
--- a/beeflow/scheduler/mars.py
+++ b/src/beeflow/scheduler/mars.py
@@ -3,7 +3,7 @@
 import json
 import os
 import tensorflow as tf
-import beeflow.scheduler.mars_util as mars_util
+import scheduler.mars_util as mars_util
 
 
 def _get_action(x, total_avail):
diff --git a/beeflow/scheduler/mars_util.py b/src/beeflow/scheduler/mars_util.py
similarity index 100%
rename from beeflow/scheduler/mars_util.py
rename to src/beeflow/scheduler/mars_util.py
diff --git a/beeflow/scheduler/resource_allocation.py b/src/beeflow/scheduler/resource_allocation.py
similarity index 100%
rename from beeflow/scheduler/resource_allocation.py
rename to src/beeflow/scheduler/resource_allocation.py
diff --git a/beeflow/scheduler/scheduler.py b/src/beeflow/scheduler/scheduler.py
similarity index 88%
rename from beeflow/scheduler/scheduler.py
rename to src/beeflow/scheduler/scheduler.py
index ec5ad44b2..05f9d801c 100644
--- a/beeflow/scheduler/scheduler.py
+++ b/src/beeflow/scheduler/scheduler.py
@@ -12,8 +12,9 @@
 import beeflow.scheduler.algorithms as algorithms
 import beeflow.scheduler.task as task
 import beeflow.scheduler.resource_allocation as resource_allocation
-from beeflow.common.config.config_driver import BeeConfig
-
+from beeflow.common.config_driver import BeeConfig
+from beeflow.cli import log
+import beeflow.common.log as bee_logging
 
 flask_app = Flask(__name__)
 api = Api(flask_app)
@@ -136,8 +137,8 @@ def load_config_values():
             for key in bc.userconfig['scheduler']:
                 conf[key] = bc.userconfig['scheduler'].get(key)
         else:
-            print('[scheduler] section not found in configuration file, '
-                  'default values will be added')
+            log.info('[scheduler] section not found in configuration file, '
+                    'default values will be added')
             bc.modify_section('user', 'scheduler', conf)
             sys.exit(f'Please check {bc.userconfig_file} and restart '
                      'Scheduler')
@@ -160,22 +161,22 @@ def load_config_values():
             conf['alloc_logfile'] = ALLOC_LOGFILE
 
     conf = argparse.Namespace(**conf)
-    print('Config = [')
-    print(f'\tlisten_port = {conf.listen_port}')
-    print(f'\tuse_mars = {conf.use_mars}')
-    print(f'\tmars_model = {conf.mars_model}')
-    print(f'\tmars_task_cnt = {conf.mars_task_cnt}')
-    print(f'\talloc_logfile = {conf.alloc_logfile}')
-    print(f'\talgorithm = {conf.algorithm}')
-    print(f'\tdefault_algorithm = {conf.default_algorithm}')
-    print(f'\tworkdir = {conf.workdir}')
-    print(']')
-    return conf
+    log.info('Config = [')
+    log.info(f'\tlisten_port = {conf.listen_port}')
+    log.info(f'\tuse_mars = {conf.use_mars}')
+    log.info(f'\tmars_model = {conf.mars_model}')
+    log.info(f'\tmars_task_cnt = {conf.mars_task_cnt}')
+    log.info(f'\talloc_logfile = {conf.alloc_logfile}')
+    log.info(f'\talgorithm = {conf.algorithm}')
+    log.info(f'\tdefault_algorithm = {conf.default_algorithm}')
+    log.info(f'\tworkdir = {conf.workdir}')
+    log.info(']')
+    return conf, bc
 
 
 if __name__ == '__main__':
-    print('Scheduler first line')
-    CONF = load_config_values()
+    CONF, bc = load_config_values()
+    handler = bee_logging.save_log(bc, log, logfile='sched.log')
     flask_app.sched_conf = CONF
     # Load algorithm data
     algorithms.load(**vars(CONF))
@@ -183,9 +184,6 @@ def load_config_values():
     # Create the scheduler workdir, if necessary
     os.makedirs(CONF.workdir, exist_ok=True)
 
-    handler = logging.FileHandler(CONF.log)
-    handler.setLevel(logging.DEBUG)
-
     # Werkzeug logging
     werk_log = logging.getLogger('werkzeug')
     werk_log.setLevel(logging.INFO)
@@ -194,7 +192,6 @@ def load_config_values():
     # Flask logging
     flask_app.logger.addHandler(handler) # noqa
     flask_app.run(debug=True, port=CONF.listen_port)
-    print('Scheduler last line')
 
 # Ignore todo's or pylama fails
 # pylama:ignore=W0511
diff --git a/beeflow/scheduler/scheduler.yaml b/src/beeflow/scheduler/scheduler.yaml
similarity index 100%
rename from beeflow/scheduler/scheduler.yaml
rename to src/beeflow/scheduler/scheduler.yaml
diff --git a/beeflow/scheduler/serializable.py b/src/beeflow/scheduler/serializable.py
similarity index 100%
rename from beeflow/scheduler/serializable.py
rename to src/beeflow/scheduler/serializable.py
diff --git a/beeflow/scheduler/task.py b/src/beeflow/scheduler/task.py
similarity index 100%
rename from beeflow/scheduler/task.py
rename to src/beeflow/scheduler/task.py
diff --git a/beeflow/scheduler/train.py b/src/beeflow/scheduler/train.py
similarity index 96%
rename from beeflow/scheduler/train.py
rename to src/beeflow/scheduler/train.py
index 4c4899906..8f206dc96 100644
--- a/beeflow/scheduler/train.py
+++ b/src/beeflow/scheduler/train.py
@@ -7,10 +7,10 @@
 import tensorflow as tf
 import json
 
-import beeflow.scheduler.mars as mars
-import beeflow.scheduler.mars_util as mars_util
-import beeflow.scheduler.evaluate as evaluate
-import beeflow.scheduler.task as task
+import scheduler.mars as mars
+import scheduler.mars_util as mars_util
+import scheduler.evaluate as evaluate
+import scheduler.task as task
 
 
 if __name__ == '__main__':
diff --git a/beeflow/task_manager/task_manager.py b/src/beeflow/task_manager.py
similarity index 83%
rename from beeflow/task_manager/task_manager.py
rename to src/beeflow/task_manager.py
index 0f6e9eafc..859d3d8fd 100644
--- a/beeflow/task_manager/task_manager.py
+++ b/src/beeflow/task_manager.py
@@ -6,24 +6,24 @@
 import configparser
 import atexit
 import sys
-import os
-import platform
-import logging
 import jsonpickle
 import requests
-import types
 
 from flask import Flask, jsonify, make_response
 from flask_restful import Resource, Api, reqparse
 
 from apscheduler.schedulers.background import BackgroundScheduler
-from beeflow.common.config.config_driver import BeeConfig
+from beeflow.common.config_driver import BeeConfig
+from beeflow.cli import log
+import beeflow.common.log as bee_logging
+import logging
 
-if (len(sys.argv) > 2):
+if len(sys.argv) > 2:
     bc = BeeConfig(userconfig=sys.argv[1])
 else:
     bc = BeeConfig()
 
+
 def check_crt_config(container_runtime):
     """Check container runtime configurations."""
     supported_runtimes = ['Charliecloud', 'Singularity']
@@ -36,7 +36,6 @@ def check_crt_config(container_runtime):
             cc_opts = {'setup': 'module load charliecloud',
                        'image_mntdir': '/tmp',
                        'chrun_opts': '--cd /home/$USER'
-
                        }
             bc.modify_section('user', 'charliecloud', cc_opts)
         else:
@@ -46,19 +45,9 @@ def check_crt_config(container_runtime):
                 bc.modify_section('user', 'charliecloud', {'image_mntdir': '/tmp'})
 
 
-# Set Task Manager default port, attempt to prevent collisions
-TM_PORT = 5050
-if platform.system() == 'Windows':
-    # Get parent's pid to offset ports. uid method better but not available in Windows
-    TM_PORT += os.getppid() % 100
-else:
-    TM_PORT += os.getuid() % 100
-
 # Check task_manager and container_runtime sections of user configuration file
 tm_dict = {}
-tm_log = f"{bc.userconfig['DEFAULT'].get('bee_workdir')}/logs/tm.log"
-tm_default = {'listen_port': TM_PORT,
-              'log': tm_log,
+tm_default = {'listen_port': bc.default_tm_port,
               'container_runtime': 'Charliecloud'}
 if bc.userconfig.has_section('task_manager'):
     # Insert defaults for any options not in task_manager section of userconfig file
@@ -73,7 +62,7 @@ def check_crt_config(container_runtime):
     if UPDATE_CONFIG:
         bc.modify_section('user', 'task_manager', tm_dict)
 else:
-    tm_listen_port = TM_PORT
+    tm_listen_port = bc.default_tm_port
     bc.modify_section('user', 'task_manager', tm_default)
     check_crt_config(tm_default['container_runtime'])
     sys.exit(f'[task_manager] section missing in {bc.userconfig_file}\n' +
@@ -83,16 +72,12 @@ def check_crt_config(container_runtime):
 
 tm_listen_port = bc.userconfig.get('task_manager', 'listen_port')
 
-# Check Workflow manager port
+# Check Workflow manager port, use default if none.
 if bc.userconfig.has_section('workflow_manager'):
-    try:
-        bc.userconfig.get('workflow_manager', 'listen_port')
-    except NoOptionError:
-        sys.exit(f'[workflow_manager] missing listen_port in {bc.userconfig_file}')
+    wfm_listen_port = bc.userconfig['workflow_manager'].get('listen_port',
+                                                            bc.default_wfm_port)
 else:
-    sys.exit(f'[workflow_manager] section missing in {bc.userconfig_file}')
-
-wfm_listen_port = bc.userconfig.get('workflow_manager', 'listen_port')
+    wfm_listen_port = bc.default_wfm_port
 
 flask_app = Flask(__name__)
 api = Api(flask_app)
@@ -137,7 +122,7 @@ def submit_jobs():
                 print(f'{task.name} state: {job_state}')
             else:
                 # place job in queue to monitor and send initial state to WFM
-                print(f'Job Submitted {task.name}: job_id: {job_id} job_state: {job_state}')
+                log.step_info(f'Job Submitted {task.name}: job_id: {job_id} job_state: {job_state}')
                 job_queue.append({task_id: {'name': task.name,
                                  'job_id': job_id, 'job_state': job_state}})
         # Send the initial state to WFM
@@ -153,7 +138,7 @@ def update_jobs():
         job_state = worker.query_task(job_id)
 
         if job_state != current_task['job_state']:
-            print(f'{current_task["name"]} {current_task["job_state"]} -> {job_state}')
+            log.step_info(f'{current_task["name"]} {current_task["job_state"]} -> {job_state}')
             current_task['job_state'] = job_state
             update_task_state(task_id, job_state)
         if job_state in ('COMPLETED', 'CANCELLED', 'ZOMBIE'):
@@ -221,7 +206,7 @@ def delete():
 
 
 # WorkerInterface needs to be placed here. Don't Move!
-from beeflow.common.worker.worker_interface import WorkerInterface
+from beeflow.common.worker_interface import WorkerInterface
 from beeflow.common.worker.slurm_worker import SlurmWorker
 from beeflow.common.worker.lsf_worker import LSFWorker
 
@@ -255,20 +240,10 @@ def delete():
 api.add_resource(TaskActions, '/bee_tm/v1/task/')
 
 if __name__ == '__main__':
-    # Get the parameter for logging
-    try:
-        bc.userconfig.get('task_manager', 'log')
-    except NoOptionError:
-        tm_log = {'log': f'/{bc.userconfig["DEFAULT"].get("bee_workdir")}/logs/tm.log'}
-        bc.modify_section('user', 'task_manager', tm_log)
-    finally:
-        tm_log = bc.userconfig.get('task_manager', 'log')
-        tm_log = bc.resolve_path(tm_log)
-    print('tm_listen_port:', tm_listen_port)
-    print('container_runtime', bc.userconfig.get('task_manager', 'container_runtime'))
-
-    handler = logging.FileHandler(tm_log)
-    handler.setLevel(logging.DEBUG)
+    handler = bee_logging.save_log(bc, log, logfile='task_manager.log')
+    log.info(f'tm_listen_port:{tm_listen_port}')
+    container_runtime = bc.userconfig.get('task_manager', 'container_runtime')
+    log.info(f'container_runtime:{container_runtime}')
 
     # Werkzeug logging
     werk_log = logging.getLogger('werkzeug')
diff --git a/beeflow/wfm/wfm.py b/src/beeflow/wf_manager.py
similarity index 82%
rename from beeflow/wfm/wfm.py
rename to src/beeflow/wf_manager.py
index 92cca4ce0..b44e38fa1 100755
--- a/beeflow/wfm/wfm.py
+++ b/src/beeflow/wf_manager.py
@@ -1,11 +1,11 @@
 """Server launch script."""
 import os
 import sys
-import platform
 import logging
 import configparser
 import jsonpickle
 import requests
+import types
 import cwl_utils.parser_v1_0 as cwl
 # Server and REST handlin
 from flask import Flask, jsonify, make_response
@@ -15,64 +15,45 @@
 # Temporary clamr parser
 import beeflow.common.parser.parse_clamr as parser
 from beeflow.common.wf_interface import WorkflowInterface
-from beeflow.common.config.config_driver import BeeConfig
-import types
+from beeflow.common.config_driver import BeeConfig
+from beeflow.cli import log
+import beeflow.common.log as bee_logging
 
-if (len(sys.argv) > 2):
+if len(sys.argv) > 2:
     bc = BeeConfig(userconfig=sys.argv[1])
 else:
     bc = BeeConfig()
 
-# Set Workflow manager ports, attempt to prevent collisions
-WM_PORT = 5000
-
-if platform.system() == 'Windows':
-    # Get parent's pid to offset ports. uid method better but not available in Windows
-    WM_PORT += os.getppid() % 100
-else:
-    WM_PORT += os.getuid() % 100
-
 if bc.userconfig.has_section('workflow_manager'):
     # Try getting listen port from config if exists, use WM_PORT if it doesnt exist
-    wfm_listen_port = bc.userconfig['workflow_manager'].get('listen_port', WM_PORT)
-    print(f"wfm_listen_port {wfm_listen_port}")
+    wfm_listen_port = bc.userconfig['workflow_manager'].get('listen_port', bc.default_wfm_port)
+    log.info(f"wfm_listen_port {wfm_listen_port}")
 else:
-    print("[workflow_manager] section not found in configuration file, default values\
+    log.info("[workflow_manager] section not found in configuration file, default values\
  will be added")
 
     wfm_dict = {
-        'listen_port': WM_PORT,
+        'listen_port': bc.default_wfm_port,
     }
 
     bc.modify_section('user', 'workflow_manager', wfm_dict)
-
     sys.exit("Please check " + str(bc.userconfig_file) + " and restart WorkflowManager")
 
 if bc.userconfig.has_section('task_manager'):
-    # Try getting listen port from config if exists, use 5050 if it doesnt exist
-    TM_LISTEN_PORT = bc.userconfig['task_manager'].get('listen_port', '5050')
+    # Try getting listen port from config if exists, use default if it doesnt exist
+    TM_LISTEN_PORT = bc.userconfig['task_manager'].get('listen_port', bc.default_tm_port)
 else:
-    print("[task_manager] section not found in configuration file, default values will be used")
+    log.info("[task_manager] section not found in configuration file, default values will be used")
     # Set Workflow manager ports, attempt to prevent collisions
-    TM_LISTEN_PORT = 5050
-    if platform.system() == 'Windows':
-        # Get parent's pid to offset ports. uid method better but not available in Windows
-        TM_LISTEN_PORT += os.getppid() % 100
-    else:
-        TM_LISTEN_PORT += os.getuid() % 100
+    TM_LISTEN_PORT = bc.default_tm_port
 
 if bc.userconfig.has_section('scheduler'):
     # Try getting listen port from config if exists, use 5050 if it doesnt exist
-    SCHED_LISTEN_PORT = bc.userconfig['scheduler'].get('listen_port', '5050')
+    SCHED_LISTEN_PORT = bc.userconfig['scheduler'].get('listen_port', bc.default_sched_port)
 else:
-    print("[scheduler] section not found in configuration file, default values will be used")
+    log.info("[scheduler] section not found in configuration file, default values will be used")
     # Set Workflow manager ports, attempt to prevent collisions
-    SCHED_LISTEN_PORT = 5054
-    if platform.system() == 'Windows':
-        # Get parent's pid to offset ports. uid method better but not available in Windows
-        SCHED_LISTEN_PORT += os.getppid() % 100
-    else:
-        SCHED_LISTEN_PORT += os.getuid() % 100
+    SCHED_LISTEN_PORT = bc.default_sched_port
 
 flask_app = Flask(__name__)
 api = Api(flask_app)
@@ -142,7 +123,7 @@ def validate_wf_id(func):
     def wrapper(*args, **kwargs):
         wf_id = int(kwargs['wf_id'])
         if wf_id != 42:
-            print(f'Wrong workflow id. Set to {wf_id}, but should be 42')
+            log.info(f'Wrong workflow id. Set to {wf_id}, but should be 42')
             resp = make_response(jsonify(status='wf_id not found'), 404)
             return resp
         return func(*args, **kwargs)
@@ -221,22 +202,22 @@ def submit_task_tm(task):
     # Serialize task with json
     task_json = jsonpickle.encode(task)
     # Send task_msg to task manager
-    print(f"Submitted {task.name} to Task Manager")
+    log.info(f"Submitted {task.name} to Task Manager")
     resp = requests.post(_resource('tm', "submit/"), json={'task': task_json})
     if resp.status_code != 200:
-        print(f"Submit task to TM returned bad status: {resp.status_code}")
+        log.info(f"Submit task to TM returned bad status: {resp.status_code}")
 
 
 # Submit a list of tasks to the Scheduler
 def submit_tasks_scheduler(sched_tasks):
     """Submit a list of tasks to the scheduler."""
     tasks_json = jsonpickle.encode(sched_tasks)
-    print(f"Submitted {sched_tasks} to Scheduler")
+    log.info(f"Submitted {sched_tasks} to Scheduler")
     # The workflow name will eventually be added to the wfi workflow object
-    print(_resource('sched', "workflows/workflow/jobs"))
+    log.info(_resource('sched', "workflows/workflow/jobs"))
     resp = requests.put(_resource('sched', "workflows/workflow/jobs"), json=sched_tasks)
     if resp.status_code != 200:
-        print(f"Something bad happened {resp.status_code}")
+        log.info(f"Something bad happened {resp.status_code}")
     return resp.json()
 
 
@@ -246,7 +227,7 @@ def setup_scheduler():
     nodes = 32
 
     data = rm.get()
-    print(data)
+    log.info(data)
 
     resources = [
             {
@@ -255,10 +236,10 @@ def setup_scheduler():
             }
     ]
 
-    print(_resource('sched', "resources/"))
+    log.info(_resource('sched', "resources/"))
     #resp = requests.put(_resource('sched', "workflows/workflow/jobs"), json=sched_tasks)
     resp = requests.put(_resource('sched', "resources"), json=resources)
-    print(resp.json())
+    log.info(resp.json())
 
 
 # Used to tell if the workflow is currently paused
@@ -271,7 +252,7 @@ def setup_scheduler():
 def save_task(task):
     """Save a task."""
     global SAVED_TASK
-    print(f"Saving {task.name}")
+    log.info(f"Saving {task.name}")
     SAVED_TASK = task
 
 
@@ -319,7 +300,7 @@ def post(wf_id):
         sched_tasks = tasks_to_sched(tasks)
         # Submit all dependent tasks to the scheduler
         allocation = submit_tasks_scheduler(sched_tasks)
-        print(f"Scheduler says {allocation}")
+        log.info(f"Scheduler says {allocation}")
         # Submit task to TM
         submit_task_tm(tasks[0])
         #resp = make_response(jsonify(msg='Started workflow', status='ok'), 200)
@@ -334,7 +315,7 @@ def get(wf_id):
         for task in tasks:
             if task.name != "bee_init" and task.name != "bee_exit":
                 task_status += f"{task.name}--{wfi.get_task_state(task)}\n"
-        print("Returned query")
+        log.info("Returned query")
         resp = make_response(jsonify(msg=task_status, status='ok'), 200)
         return resp
 
@@ -344,11 +325,11 @@ def delete(wf_id):
         """Send a request to the task manager to cancel any ongoing tasks."""
         resp = requests.delete(_resource('tm'))
         if resp.status_code != 200:
-            print(f"Delete from task manager returned bad status: {resp.status_code}")
+            log.info(f"Delete from task manager returned bad status: {resp.status_code}")
         # Remove all tasks currently in the database
         if wfi.workflow_loaded():
             wfi.finalize_workflow()
-        print("Workflow cancelled")
+        log.info("Workflow cancelled")
         resp = make_response(jsonify(status='cancelled'), 202)
         return resp
 
@@ -361,17 +342,17 @@ def patch(self, wf_id):
         option = data['option']
         if option == 'pause':
             WORKFLOW_PAUSED = True
-            print("Workflow Paused")
+            log.info("Workflow Paused")
             resp = make_response(jsonify(status='Workflow Paused'), 200)
             return resp
         if option == 'resume':
             if WORKFLOW_PAUSED:
                 WORKFLOW_PAUSED = False
                 resume()
-            print("Workflow Resumed")
+            log.info("Workflow Resumed")
             resp = make_response(jsonify(status='Workflow Resumed'), 200)
             return resp
-        print("Invalid option")
+        log.error("Invalid option")
         resp = make_response(jsonify(status='Invalid option for pause/resume'), 400)
         return resp
 
@@ -410,12 +391,12 @@ def put(self):
                 else:
                     sched_tasks = tasks_to_sched(remaining_tasks)
                     if len(remaining_tasks) == 0:
-                        print("Workflow Completed")
+                        log.info("Workflow Completed")
                     else:
                         submit_tasks_scheduler(sched_tasks)
                         submit_task_tm(tasks[0])
             else:
-                print("Workflow Completed!")
+                log.info("Workflow Completed!")
         resp = make_response(jsonify(status=f'Task {task_id} set to {job_state}'), 200)
         return resp
 
@@ -427,24 +408,12 @@ def put(self):
 
 
 if __name__ == '__main__':
-    # Get the paramater for logging
-    try:
-        bc.userconfig.get('workflow_manager', 'log')
-    except configparser.NoOptionError:
-        bc.modify_section('user', 'workflow_manager',
-                          {'log': '/'.join([bc.userconfig['DEFAULT'].get('bee_workdir'),
-                                            'logs', 'wfm.log'])})
-    finally:
-        wfm_log = bc.userconfig.get('workflow_manager', 'log')
-        wfm_log = bc.resolve_path(wfm_log)
-
     # Setup the Scheduler
     setup_scheduler()
 
-    print('wfm_listen_port:', wfm_listen_port)
+    log.info('wfm_listen_port:', wfm_listen_port)
 
-    handler = logging.FileHandler(wfm_log)
-    handler.setLevel(logging.DEBUG)
+    handler = bee_logging.save_log(bc, log, logfile='wf_manager.log')
 
     # Werkzeug logging
     werk_log = logging.getLogger('werkzeug')


Test patch:
diff --git a/doc/unittest_tutorial/__init__.py b/doc/unittest_tutorial/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/doc/unittest_tutorial/README.md b/docs/unittest_tutorial/README.md
similarity index 100%
rename from doc/unittest_tutorial/README.md
rename to docs/unittest_tutorial/README.md
diff --git a/beeflow/__init__.py b/docs/unittest_tutorial/__init__.py
similarity index 100%
rename from beeflow/__init__.py
rename to docs/unittest_tutorial/__init__.py
diff --git a/doc/unittest_tutorial/test_fixtures.py b/docs/unittest_tutorial/test_fixtures.py
similarity index 100%
rename from doc/unittest_tutorial/test_fixtures.py
rename to docs/unittest_tutorial/test_fixtures.py
diff --git a/doc/unittest_tutorial/test_numbers.py b/docs/unittest_tutorial/test_numbers.py
similarity index 100%
rename from doc/unittest_tutorial/test_numbers.py
rename to docs/unittest_tutorial/test_numbers.py
diff --git a/doc/unittest_tutorial/test_skip.py b/docs/unittest_tutorial/test_skip.py
similarity index 100%
rename from doc/unittest_tutorial/test_skip.py
rename to docs/unittest_tutorial/test_skip.py
diff --git a/doc/unittest_tutorial/test_strings.py b/docs/unittest_tutorial/test_strings.py
similarity index 100%
rename from doc/unittest_tutorial/test_strings.py
rename to docs/unittest_tutorial/test_strings.py
diff --git a/test/README.md b/src/beeflow/tests/README.md
similarity index 100%
rename from test/README.md
rename to src/beeflow/tests/README.md
diff --git a/test/cf.cwl b/src/beeflow/tests/cf.cwl
similarity index 100%
rename from test/cf.cwl
rename to src/beeflow/tests/cf.cwl
diff --git a/test/mocks.py b/src/beeflow/tests/mocks.py
similarity index 100%
rename from test/mocks.py
rename to src/beeflow/tests/mocks.py
diff --git a/test/test_scheduler.py b/src/beeflow/tests/test_scheduler.py
similarity index 99%
rename from test/test_scheduler.py
rename to src/beeflow/tests/test_scheduler.py
index 526a243c6..c11afeb44 100644
--- a/test/test_scheduler.py
+++ b/src/beeflow/tests/test_scheduler.py
@@ -5,9 +5,9 @@
 
 import time
 
-import beeflow.scheduler.algorithms as algorithms
-import beeflow.scheduler.task as task
-import beeflow.scheduler.resource_allocation as resource_allocation
+import scheduler.algorithms as algorithms
+import scheduler.task as task
+import scheduler.resource_allocation as resource_allocation
 
 
 class TestFCFS:
diff --git a/test/test_scheduler_mars.py b/src/beeflow/tests/test_scheduler_mars.py
similarity index 95%
rename from test/test_scheduler_mars.py
rename to src/beeflow/tests/test_scheduler_mars.py
index d5fc44545..26f79ed33 100644
--- a/test/test_scheduler_mars.py
+++ b/src/beeflow/tests/test_scheduler_mars.py
@@ -5,9 +5,9 @@
 import os
 import tensorflow as tf
 
-import beeflow.scheduler.mars_util as mars_util
-import beeflow.scheduler.mars as mars
-import beeflow.scheduler.task as task
+import scheduler.mars_util as mars_util
+import scheduler.mars as mars
+import scheduler.task as task
 
 
 def test_workflow2vec_one_task():
diff --git a/test/test_scheduler_resource_allocation.py b/src/beeflow/tests/test_scheduler_resource_allocation.py
similarity index 97%
rename from test/test_scheduler_resource_allocation.py
rename to src/beeflow/tests/test_scheduler_resource_allocation.py
index ead48aca9..d20c17b93 100644
--- a/test/test_scheduler_resource_allocation.py
+++ b/src/beeflow/tests/test_scheduler_resource_allocation.py
@@ -1,7 +1,7 @@
 """Test the resource_allocation submodule of BEE.
 
 """
-import beeflow.scheduler.resource_allocation as resource_allocation
+import scheduler.resource_allocation as resource_allocation
 import json
 
 
diff --git a/test/test_scheduler_rest.py b/src/beeflow/tests/test_scheduler_rest.py
similarity index 100%
rename from test/test_scheduler_rest.py
rename to src/beeflow/tests/test_scheduler_rest.py
diff --git a/test/test_slurm_worker.py b/src/beeflow/tests/test_slurm_worker.py
similarity index 95%
rename from test/test_slurm_worker.py
rename to src/beeflow/tests/test_slurm_worker.py
index 69c49b4dc..7081c27c2 100644
--- a/test/test_slurm_worker.py
+++ b/src/beeflow/tests/test_slurm_worker.py
@@ -5,10 +5,10 @@
 import os
 import string
 
-from beeflow.common.data.wf_data import Task
-from beeflow.common.worker.worker_interface import WorkerInterface
-from beeflow.common.worker.slurm_worker import SlurmWorker
-from beeflow.common.worker.slurm_worker import submit_job
+from common.data.wf_data import Task
+from common.worker.worker_interface import WorkerInterface
+from common.worker.slurm_worker import SlurmWorker
+from common.worker.slurm_worker import submit_job
 
 
 class TestSlurmWorker(unittest.TestCase):
diff --git a/test/test_tm.py b/src/beeflow/tests/test_tm.py
similarity index 81%
rename from test/test_tm.py
rename to src/beeflow/tests/test_tm.py
index 12f1c2916..9b31d130c 100644
--- a/test/test_tm.py
+++ b/src/beeflow/tests/test_tm.py
@@ -1,6 +1,6 @@
 """This is a sample docstring."""
 
-import beeflow.task_manager.task_manager as tm
+import beeflow.task_manager as tm
 import beeflow
 # import flask_restful
 # from flask import Flask
@@ -31,16 +31,16 @@ def test_submit_task(flask_client, mocker):  # noqa
     response = flask_client.post('/bee_tm/v1/task/submit/',
                                  json={'task': task_json})
 
-    mocker.patch('beeflow.task_manager.task_manager.worker',
+    mocker.patch('beeflow.task_manager.worker',
                  new_callable=MockWorkerSubmission)
 
     mocker.patch.object(requests, 'put', side_effect=mock_put)
-    beeflow.task_manager.task_manager.process_queues()
+    beeflow.task_manager.process_queues()
 
     msg = response.get_json()['msg']
     status = response.status_code
 
-    job_queue = beeflow.task_manager.task_manager.job_queue
+    job_queue = beeflow.task_manager.job_queue
     # 42 is the sample task ID
     job = job_queue[0][42]
 
@@ -57,14 +57,14 @@ def test_submit_task(flask_client, mocker):  # noqa
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_completed_task(flask_client, mocker): # noqa
     """Tests how the task manager processes a completed task."""
-    job_queue = beeflow.task_manager.task_manager.job_queue
+    job_queue = beeflow.task_manager.job_queue
     # 42 is the sample task ID
-    mocker.patch('beeflow.task_manager.task_manager.worker',
+    mocker.patch('beeflow.task_manager.worker',
                  new_callable=MockWorkerCompletion)
     mocker.patch.object(requests, 'put', side_effect=mock_put)
 
     # This should notice the job is complete and empty the job_queue
-    beeflow.task_manager.task_manager.process_queues()
+    beeflow.task_manager.process_queues()
     assert len(job_queue) == 0
 
 
@@ -72,25 +72,25 @@ def test_completed_task(flask_client, mocker): # noqa
 def test_remove_task(flask_client, mocker):  # noqa
     """Test cancelling a workflow and removing tasks."""
     # Add a few tasks
-    beeflow.task_manager.task_manager.job_queue.append({2: {'name': 'task1',
+    beeflow.task_manager.job_queue.append({2: {'name': 'task1',
                                                             'job_id': 1,
                                                             'job_state':
                                                             'RUNNING'}
                                                         })
 
-    beeflow.task_manager.task_manager.job_queue.append({4: {'name': 'task2',
+    beeflow.task_manager.job_queue.append({4: {'name': 'task2',
                                                             'job_id': 2,
                                                             'job_state':
                                                             'PENDING'}
                                                         })
 
-    beeflow.task_manager.task_manager.job_queue.append({6: {'name': 'task3',
+    beeflow.task_manager.job_queue.append({6: {'name': 'task3',
                                                             'job_id': 3,
                                                             'job_state':
                                                             'PENDING'}
                                                         })
 
-    mocker.patch('beeflow.task_manager.task_manager.worker',
+    mocker.patch('beeflow.task_manager.worker',
                  new_callable=MockWorkerCompletion)
 
     response = flask_client.delete('/bee_tm/v1/task/')
diff --git a/test/test_wf_interface.py b/src/beeflow/tests/test_wf_interface.py
similarity index 99%
rename from test/test_wf_interface.py
rename to src/beeflow/tests/test_wf_interface.py
index c42951ef2..a69283c85 100644
--- a/test/test_wf_interface.py
+++ b/src/beeflow/tests/test_wf_interface.py
@@ -6,7 +6,7 @@
 
 import unittest
 
-from beeflow.common.wf_interface import WorkflowInterface
+from common.wf_interface import WorkflowInterface
 
 
 class TestWorkflowInterface(unittest.TestCase):
diff --git a/test/test_wfm.py b/src/beeflow/tests/test_wf_manager.py
similarity index 86%
rename from test/test_wfm.py
rename to src/beeflow/tests/test_wf_manager.py
index e99c41087..9ddea6a05 100644
--- a/test/test_wfm.py
+++ b/src/beeflow/tests/test_wf_manager.py
@@ -1,4 +1,4 @@
-import beeflow.wfm.wfm as wfm
+import beeflow.wf_manager as wfm
 import pytest
 import requests
 from mocks import MockWFI, mock_get
@@ -32,11 +32,11 @@ def test_create_workflow(flask_client, mocker): # noqa
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_submit_workflow(flask_client, mocker): # noqa
     """Submit a cwl file to the WFM. parse the cwl file."""
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
 
     wf_id = 42
 
-    files = {'workflow': open('cf.cwl', 'rb')}
+    files = {'workflow': open('beeflow/data/cwl/cf.cwl', 'rb')}
     response = flask_client.put('/bee_wfm/v1/jobs/submit/' + str(wf_id),
                                 data=files)
 
@@ -46,7 +46,7 @@ def test_submit_workflow(flask_client, mocker): # noqa
 
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_start_workflow(flask_client, mocker): # noqa
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
     wf_id = 42
     request = {'wf_id': wf_id}
 
@@ -58,7 +58,7 @@ def test_start_workflow(flask_client, mocker): # noqa
 
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_query_workflow(flask_client, mocker): # noqa
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
     wf_id = 42
     request = {'wf_id': wf_id}
 
@@ -71,7 +71,7 @@ def test_query_workflow(flask_client, mocker): # noqa
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_cancel_workflow(flask_client, mocker): # noqa
     mocker.patch.object(requests, 'delete', side_effect=mock_delete)
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
 
     wf_id = 42
     request = {'wf_id': wf_id}
@@ -83,7 +83,7 @@ def test_cancel_workflow(flask_client, mocker): # noqa
 
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_pause_workflow(flask_client, mocker): # noqa
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
     wf_id = 42
     request = {'wf_id': wf_id, 'option': 'pause'}
 
@@ -94,7 +94,7 @@ def test_pause_workflow(flask_client, mocker): # noqa
 
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_resume_workflow(flask_client, mocker): # noqa
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
     wf_id = 42
     request = {'wf_id': wf_id, 'option': 'resume'}
 
@@ -106,7 +106,7 @@ def test_resume_workflow(flask_client, mocker): # noqa
 @pytest.mark.usefixtures('flask_client', 'mocker')
 def test_update_task(flask_client, mocker): # noqa
     mocker.patch.object(requests, 'post', side_effect=mock_get)
-    mocker.patch('beeflow.wfm.wfm.wfi', new_callable=MockWFI)
+    mocker.patch('beeflow.wf_manager.wfi', new_callable=MockWFI)
 
     task_id = 0
     job_state = 'COMPLETED'
diff --git a/test/workflows/README.md b/src/beeflow/tests/workflows/README.md
similarity index 100%
rename from test/workflows/README.md
rename to src/beeflow/tests/workflows/README.md
diff --git a/test/workflows/cwl/README.md b/src/beeflow/tests/workflows/cwl/README.md
similarity index 100%
rename from test/workflows/cwl/README.md
rename to src/beeflow/tests/workflows/cwl/README.md
diff --git a/test/workflows/neo4j/README.md b/src/beeflow/tests/workflows/neo4j/README.md
similarity index 100%
rename from test/workflows/neo4j/README.md
rename to src/beeflow/tests/workflows/neo4j/README.md
diff --git a/test/workflows/neo4j/blast.cql b/src/beeflow/tests/workflows/neo4j/blast.cql
similarity index 100%
rename from test/workflows/neo4j/blast.cql
rename to src/beeflow/tests/workflows/neo4j/blast.cql
diff --git a/test/workflows/neo4j/blast.neo4j b/src/beeflow/tests/workflows/neo4j/blast.neo4j
similarity index 100%
rename from test/workflows/neo4j/blast.neo4j
rename to src/beeflow/tests/workflows/neo4j/blast.neo4j
diff --git a/test/workflows/neo4j/blast.png b/src/beeflow/tests/workflows/neo4j/blast.png
similarity index 100%
rename from test/workflows/neo4j/blast.png
rename to src/beeflow/tests/workflows/neo4j/blast.png
diff --git a/test/workflows/neo4j/hpc.cql b/src/beeflow/tests/workflows/neo4j/hpc.cql
similarity index 100%
rename from test/workflows/neo4j/hpc.cql
rename to src/beeflow/tests/workflows/neo4j/hpc.cql
diff --git a/test/workflows/neo4j/hpc.neo4j b/src/beeflow/tests/workflows/neo4j/hpc.neo4j
similarity index 100%
rename from test/workflows/neo4j/hpc.neo4j
rename to src/beeflow/tests/workflows/neo4j/hpc.neo4j
diff --git a/test/workflows/neo4j/hpc.png b/src/beeflow/tests/workflows/neo4j/hpc.png
similarity index 100%
rename from test/workflows/neo4j/hpc.png
rename to src/beeflow/tests/workflows/neo4j/hpc.png