diff --git a/bob/devtools/changelog.py b/bob/devtools/changelog.py
index 5dba4fa0894f2bfc05b9f1bb393e6b215366d44d..aec6760bde66b60e731ae8ba07a875a3e0f7515b 100644
--- a/bob/devtools/changelog.py
+++ b/bob/devtools/changelog.py
@@ -5,12 +5,13 @@
 
 import io
 import datetime
-import logging
-logger = logging.getLogger(__name__)
 
 import pytz
 import dateutil.parser
 
+from .log import get_logger
+logger = get_logger(__name__)
+
 
 def parse_date(d):
     '''Parses any date supported by :py:func:`dateutil.parser.parse`'''
diff --git a/bob/devtools/ci.py b/bob/devtools/ci.py
index e30441316bd6a532573a722e6a26889ecf44a1b3..91ae88cc923087c1ecf200fa6f9a2181fedc8da7 100644
--- a/bob/devtools/ci.py
+++ b/bob/devtools/ci.py
@@ -4,12 +4,12 @@
 '''Tools to help CI-based builds and artifact deployment'''
 
 
-import logging
-logger = logging.getLogger(__name__)
-
 import git
 import distutils.version
 
+from .log import get_logger
+logger = get_logger(__name__)
+
 
 def is_master(refname, tag, repodir):
   '''Tells if we're on the master branch via ref_name or tag
diff --git a/bob/devtools/constants.py b/bob/devtools/constants.py
index facc0b7e43a81d6f761495660ebda60def72b272..847a0b65fb6cdd7a374343aee639eaf8e716509e 100644
--- a/bob/devtools/constants.py
+++ b/bob/devtools/constants.py
@@ -6,11 +6,11 @@
 import os
 import pkg_resources
 
-import logging
-logger = logging.getLogger(__name__)
-
 from . import bootstrap
 
+from .log import get_logger
+logger = get_logger(__name__)
+
 
 BASE_CONDARC = bootstrap._BASE_CONDARC
 '''Default setup for conda builds'''
diff --git a/bob/devtools/log.py b/bob/devtools/log.py
index 3cc7c7838299e2c4b9d8ecdde85cb2888ca73aed..50842dc7cf282d672df9e4cc06e146f336af2f8e 100644
--- a/bob/devtools/log.py
+++ b/bob/devtools/log.py
@@ -7,6 +7,10 @@
 import sys
 import logging
 
+import click
+import termcolor
+
+
 # get the default root logger of Bob
 _logger = logging.getLogger('bob')
 
@@ -28,6 +32,87 @@ _debug_info.addFilter(_InfoFilter())
 _logger.addHandler(_debug_info)
 
 
+COLORMAP = dict(
+    debug=dict(),
+    info=dict(attrs=['bold']),
+    warn=dict(color='yellow', attrs=['bold']),
+    warning=dict(color='yellow', attrs=['bold']),
+    error=dict(color='red'),
+    exception=dict(color='red', attrs=['bold']),
+    critical=dict(color='red', attrs=['bold']),
+    )
+'''Default color map for homogenized color display'''
+
+
+def _supports_color():
+  """
+  Returns True if the running system's terminal supports color, and False
+  otherwise.
+  """
+  plat = sys.platform
+  supported_platform = plat != 'Pocket PC' and (plat != 'win32' or
+                                                'ANSICON' in os.environ)
+  # isatty is not always implemented, #6223.
+  is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
+  if not supported_platform or not is_a_tty:
+    return False
+  return True
+
+
+class ColorLog(object):
+  '''Colorizes logging colors'''
+
+  def __init__(self, logger):
+    self._log = logger
+
+  def __getattr__(self, name):
+    if name in ['debug', 'info', 'warn', 'warning', 'error', 'exception',
+        'critical']:
+      if _supports_color():
+        return lambda s, *args: getattr(self._log, name)(
+            termcolor.colored(s, **COLORMAP[name]), *args)
+      else:
+        return lambda s, *args: getattr(self._log, name)(s, *args)
+
+    return getattr(self._log, name)
+
+
+def get_logger(name):
+  """Returns the default logger as setup by this module"""
+
+  return ColorLog(logging.getLogger(name))
+
+
+def _echo(text, *args, **kwargs):
+  """Provides a colorized version of :py:func:`click.echo` (for terminals)
+
+  The color is stripped off if outputting to a file or piping the results of
+  a command using this function.
+
+  Parameters:
+
+    text (str): The text to be printed
+    args (tuple): Tuple of attributes directly passed to
+      :py:func:`termcolor.colored`
+    kwargs (dict): Dictionary of attributes directly passed to
+      :py:func:`termcolor.colored`
+  """
+
+  click.echo(termcolor.colored(text, *args, **kwargs))
+
+
+def echo_normal(text):
+  """Color preset for normal text output for :py:func:`click.echo`"""
+
+  _echo(text, 'green')
+
+
+def echo_warning(text):
+  """Color preset for normal warning output for :py:func:`click.echo`"""
+
+  _echo(text, **COLORMAP['warn'])
+
+
 # helper functions to instantiate and set-up logging
 def setup(logger_name,
     format="%(levelname)s:%(name)s@%(asctime)s: %(message)s"):
@@ -68,7 +153,7 @@ def setup(logger_name,
   for handler in _logger.handlers:
     handler.setFormatter(formatter)
 
-  return logger
+  return ColorLog(logger)
 
 
 def set_verbosity_level(logger, level):
diff --git a/bob/devtools/release.py b/bob/devtools/release.py
index ba7e0e38c460acecfd949f084a9eb768328cadac..d760e2c2837155afe3d006a4b2060ddbbebdb3ed 100644
--- a/bob/devtools/release.py
+++ b/bob/devtools/release.py
@@ -9,8 +9,8 @@ import time
 import shutil
 import gitlab
 
-import logging
-logger = logging.getLogger(__name__)
+from .log import get_logger
+logger = get_logger(__name__)
 
 from distutils.version import StrictVersion
 
diff --git a/bob/devtools/scripts/build.py b/bob/devtools/scripts/build.py
index c539d7780efee10b4470dd9f29b3dbade59f7a54..52d4841169271d6d647e398dbef2c04e5e29d138 100644
--- a/bob/devtools/scripts/build.py
+++ b/bob/devtools/scripts/build.py
@@ -3,8 +3,6 @@
 
 import os
 import sys
-import logging
-logger = logging.getLogger(__name__)
 
 import yaml
 import click
@@ -12,7 +10,6 @@ import pkg_resources
 import conda_build.api
 
 from . import bdt
-from ..log import verbosity_option
 from ..build import next_build_number, conda_arch, should_skip_build, \
     get_rendered_metadata, get_parsed_recipe, make_conda_config, \
     get_docserver_setup, get_env_directory
@@ -20,6 +17,9 @@ from ..constants import CONDA_BUILD_CONFIG, CONDA_RECIPE_APPEND, \
     SERVER, MATPLOTLIB_RCDIR, BASE_CONDARC
 from ..bootstrap import set_environment, get_channels
 
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
diff --git a/bob/devtools/scripts/caupdate.py b/bob/devtools/scripts/caupdate.py
index 575c43adf839c904f7a249f44b580e44b8871c37..b6629a5ae09a56854a3f0135c965a9a90a97958b 100644
--- a/bob/devtools/scripts/caupdate.py
+++ b/bob/devtools/scripts/caupdate.py
@@ -1,13 +1,13 @@
 #!/usr/bin/env python
 
 import os
-import logging
-logger = logging.getLogger(__name__)
 
 import click
 
 from . import bdt
-from ..log import verbosity_option
+
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
 
 
 @click.command(epilog='''
diff --git a/bob/devtools/scripts/changelog.py b/bob/devtools/scripts/changelog.py
index 1ad42af895aa1d64cf635fed2f7e5f0ebeff636a..0f3c1c2ae88e4b7f3266195d88993384a0881e00 100644
--- a/bob/devtools/scripts/changelog.py
+++ b/bob/devtools/scripts/changelog.py
@@ -4,17 +4,16 @@ import os
 import sys
 import datetime
 
-import logging
-logger = logging.getLogger(__name__)
-
 import click
 
 from . import bdt
-from ..log import verbosity_option
 from ..changelog import get_last_tag_date, write_tags_with_commits
 from ..changelog import parse_date
 from ..release import get_gitlab_instance
 
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
diff --git a/bob/devtools/scripts/ci.py b/bob/devtools/scripts/ci.py
index 763cec4072280eb16656abb60a8225c320b9ca34..3ad13b14ce5cfcb22e5735cb7b8364ba43b29ab2 100644
--- a/bob/devtools/scripts/ci.py
+++ b/bob/devtools/scripts/ci.py
@@ -3,8 +3,6 @@
 import os
 import re
 import glob
-import logging
-logger = logging.getLogger(__name__)
 
 import yaml
 import click
@@ -13,9 +11,11 @@ import conda_build.api
 from click_plugins import with_plugins
 
 from . import bdt
-from ..log import verbosity_option
 from ..constants import SERVER
 
+from ..log import verbosity_option, get_logger, echo_normal
+logger = get_logger(__name__)
+
 
 @with_plugins(pkg_resources.iter_entry_points('bdt.ci.cli'))
 @click.group(cls=bdt.AliasedGroup)
@@ -363,11 +363,11 @@ def base_build(order, python, dry_run):
     recipes = list(itertools.product([None], recipes))
 
   for order, (pyver, recipe) in enumerate(recipes):
-    click.echo('\n' + (80*'='))
+    echo_normal('\n' + (80*'='))
     pytext = 'for python-%s ' % pyver if pyver is not None else ''
-    click.echo('Building "%s" %s(%d/%d)' % \
+    echo_normal('Building "%s" %s(%d/%d)' % \
         (recipe, pytext, order+1, len(recipes)))
-    click.echo((80*'=') + '\n')
+    echo_normal((80*'=') + '\n')
     if not os.path.exists(os.path.join(recipe, 'meta.yaml')):
       logger.info('Ignoring directory "%s" - no meta.yaml found' % recipe)
       continue
diff --git a/bob/devtools/scripts/commitfile.py b/bob/devtools/scripts/commitfile.py
index 66303197e33647e5c542fdbec644c8fbfa59e625..b02182475af85db6cf67fc9d8f607d81da9bafe7 100644
--- a/bob/devtools/scripts/commitfile.py
+++ b/bob/devtools/scripts/commitfile.py
@@ -1,15 +1,15 @@
 #!/usr/bin/env python
 
 import os
-import logging
-logger = logging.getLogger(__name__)
 
 import click
 
 from . import bdt
-from ..log import verbosity_option
 from ..release import get_gitlab_instance, update_files_with_mr
 
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
diff --git a/bob/devtools/scripts/create.py b/bob/devtools/scripts/create.py
index 122e7ea67eb8e087bd0f2217b21659604d16feac..0d0b6b5efe1eb83b434106246ad65dcc875eec75 100644
--- a/bob/devtools/scripts/create.py
+++ b/bob/devtools/scripts/create.py
@@ -3,20 +3,20 @@
 
 import os
 import sys
-import logging
-logger = logging.getLogger(__name__)
 
 import pkg_resources
 import click
 import yaml
 
 from . import bdt
-from ..log import verbosity_option
 from ..build import parse_dependencies, conda_create, make_conda_config
 from ..constants import BASE_CONDARC, CONDA_BUILD_CONFIG, \
     CONDA_RECIPE_APPEND, SERVER
 from ..bootstrap import set_environment, get_channels
 
+from ..log import verbosity_option, get_logger, echo_normal
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
@@ -138,4 +138,4 @@ def create(name, recipe_dir, python, overwrite, condarc, use_local, config,
   deps = parse_dependencies(recipe_dir, conda_config)
   status = conda_create(conda, name, overwrite, condarc_options, deps,
       dry_run, use_local)
-  click.echo('Execute on your shell: "conda activate %s"' % name)
+  echo_normal('Execute on your shell: "conda activate %s"' % name)
diff --git a/bob/devtools/scripts/dumpsphinx.py b/bob/devtools/scripts/dumpsphinx.py
index b022e8a38b02911abac1a468032b8ce0d1341a42..51b399a864b31f2202b61b932e3d2a509e978a32 100644
--- a/bob/devtools/scripts/dumpsphinx.py
+++ b/bob/devtools/scripts/dumpsphinx.py
@@ -2,15 +2,14 @@
 # -*- coding: utf-8 -*-
 
 
-import logging
-logger = logging.getLogger(__name__)
-
 from sphinx.ext import intersphinx
 
 import click
 
 from . import bdt
-from ..log import verbosity_option
+
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
 
 
 @click.command(epilog='''
diff --git a/bob/devtools/scripts/getpath.py b/bob/devtools/scripts/getpath.py
index d70be09a9a45664c8d509fb53546ece1bcea769b..b8105a8e88ca742c3cbccbd321e49949714f9b34 100644
--- a/bob/devtools/scripts/getpath.py
+++ b/bob/devtools/scripts/getpath.py
@@ -1,15 +1,15 @@
 #!/usr/bin/env python
 
 import os
-import logging
-logger = logging.getLogger(__name__)
 
 import click
 
 from . import bdt
-from ..log import verbosity_option
 from ..release import get_gitlab_instance, download_path
 
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
diff --git a/bob/devtools/scripts/lasttag.py b/bob/devtools/scripts/lasttag.py
index 63a4d224c1a6f9709aa790db0317e0a1c2e749b2..a6fdc114e1a8dd5af7f7c93e4e4649f6875f4d06 100644
--- a/bob/devtools/scripts/lasttag.py
+++ b/bob/devtools/scripts/lasttag.py
@@ -1,16 +1,17 @@
 #!/usr/bin/env python
 
 import os
-import logging
-logger = logging.getLogger(__name__)
 
 import click
+import gitlab
 
 from . import bdt
-from ..log import verbosity_option
 from ..changelog import get_last_tag, parse_date
 from ..release import get_gitlab_instance
 
+from ..log import verbosity_option, get_logger, echo_normal, echo_warning
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
@@ -38,11 +39,16 @@ def lasttag(package):
     gl = get_gitlab_instance()
 
     # we lookup the gitlab package once
-    use_package = gl.projects.get(package)
-    logger.info('Found gitlab project %s (id=%d)',
-        use_package.attributes['path_with_namespace'], use_package.id)
-
-    tag = get_last_tag(use_package)
-    date = parse_date(tag.commit['committed_date'])
-    click.echo('Lastest tag for %s is %s (%s)' % \
-        (package, tag.name, date.strftime('%Y-%m-%d %H:%M:%S')))
+    try:
+      use_package = gl.projects.get(package)
+      logger.info('Found gitlab project %s (id=%d)',
+          use_package.attributes['path_with_namespace'], use_package.id)
+
+      tag = get_last_tag(use_package)
+      date = parse_date(tag.commit['committed_date'])
+      echo_normal('%s: %s (%s)' % \
+          (package, tag.name, date.strftime('%Y-%m-%d %H:%M:%S')))
+    except gitlab.GitlabGetError as e:
+      logger.warn('Gitlab access error - package %s does not exist?',
+          package)
+      echo_warning('%s: unknown' % (package,))
diff --git a/bob/devtools/scripts/new.py b/bob/devtools/scripts/new.py
index c6f7b8e2097c4d78823b5fd39248ea8b294e6cc5..2c8ce0b6fc9e7f9398ce4ed39f363e6324c5b711 100644
--- a/bob/devtools/scripts/new.py
+++ b/bob/devtools/scripts/new.py
@@ -2,16 +2,16 @@
 
 import os
 import shutil
-import logging
 import datetime
-logger = logging.getLogger(__name__)
 
 import click
 import jinja2
 import pkg_resources
 
 from . import bdt
-from ..log import verbosity_option
+
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
 
 
 def copy_file(template, output_dir):
diff --git a/bob/devtools/scripts/release.py b/bob/devtools/scripts/release.py
index 87e1ca97711be9402ee21598ec154200de49a996..0a350f65b0ad61373c10db0296607d3bf19fddb3 100644
--- a/bob/devtools/scripts/release.py
+++ b/bob/devtools/scripts/release.py
@@ -4,17 +4,17 @@
 
 import os
 
-import logging
-logger = logging.getLogger(__name__)
-
 import click
 
 from . import bdt
-from ..log import verbosity_option
 from ..release import release_bob, parse_and_process_package_changelog
 from ..release import release_package, wait_for_pipeline_to_finish
 from ..release import get_gitlab_instance
 
+from ..log import verbosity_option, get_logger
+logger = get_logger(__name__)
+
+
 @click.command(epilog='''
 Examples:
 
diff --git a/bob/devtools/scripts/visibility.py b/bob/devtools/scripts/visibility.py
index ed682f01009981290e803008fd5c73b007e47045..263fc144efe1fd4984296891a5197c48eaad633c 100644
--- a/bob/devtools/scripts/visibility.py
+++ b/bob/devtools/scripts/visibility.py
@@ -6,13 +6,12 @@ import sys
 import click
 import gitlab
 
-import logging
-logger = logging.getLogger(__name__)
-
 from . import bdt
-from ..log import verbosity_option
 from ..release import get_gitlab_instance
 
+from ..log import verbosity_option, get_logger, echo_normal, echo_warning
+logger = get_logger(__name__)
+
 
 @click.command(epilog='''
 Examples:
@@ -48,12 +47,12 @@ def visibility(target, group):
 
     # reads package list or considers name to be a package name
     if os.path.exists(target) and os.path.isfile(target):
-        logger.info('Reading package names from file %s...', target)
+        logger.debug('Reading package names from file %s...', target)
         with open(target, 'rt') as f:
             packages = [k.strip() for k in f.readlines() if k.strip() and not \
                 k.strip().startswith('#')]
     else:
-        logger.info('Assuming %s is a package name (file does not ' \
+        logger.debug('Assuming %s is a package name (file does not ' \
             'exist)...', target)
         packages = [target]
 
@@ -66,11 +65,11 @@ def visibility(target, group):
         # retrieves the gitlab package object
         try:
           use_package = gl.projects.get(package)
-          logger.info('Found gitlab project %s (id=%d)',
+          logger.debug('Found gitlab project %s (id=%d)',
               use_package.attributes['path_with_namespace'], use_package.id)
-          click.echo('%s: %s' % (package,
+          echo_normal('%s: %s' % (package,
             use_package.attributes['visibility'].lower()))
         except gitlab.GitlabGetError as e:
           logger.warn('Gitlab access error - package %s does not exist?',
               package)
-          click.echo('%s: unknown' % (package,))
+          echo_warning('%s: unknown' % (package,))
diff --git a/bob/devtools/webdav3/client.py b/bob/devtools/webdav3/client.py
index b1850d705ac91c92c4ecffb01a0ed4685231bd3c..2fe7a588eb932a67987a9ac110068bb0dfc5588f 100644
--- a/bob/devtools/webdav3/client.py
+++ b/bob/devtools/webdav3/client.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8
 
 import functools
-import logging
 import os
 import shutil
 import threading
@@ -15,6 +14,10 @@ from .connection import *
 from .exceptions import *
 from .urn import Urn
 
+from ..log import get_logger
+logger = get_logger(__name__)
+
+
 try:
     from urllib.parse import unquote, urlsplit
 except ImportError:
@@ -22,7 +25,6 @@ except ImportError:
     from urlparse import urlsplit
 
 __version__ = "0.2"
-log = logging.getLogger(__name__)
 
 
 def listdir(directory):
diff --git a/conda/meta.yaml b/conda/meta.yaml
index 09cd327e9f57649e6bb1aac48c8571f9b6202b02..79ea741739dbc75fcdf601896bc692ce35778892 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -47,6 +47,7 @@ requirements:
     - twine
     - lxml
     - jinja2
+    - termcolor
 
 test:
   requires:
diff --git a/setup.py b/setup.py
index d1e0f1abbe471654a8b0f7d2f6111d7fed83764e..b37d09221538026d9bbe7e06e68b6741d84730c8 100644
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,7 @@ requires = [
     'twine',
     'lxml',
     'jinja2',
+    'termcolor',
     ]
 
 setup(