diff --git a/lib/in-process-server-test-helpers.js b/lib/in-process-server-test-helpers.js
index 4679a8c9c821a1ef9ea8784d45c21e0796105882..2187427362885bda191c6c19d35b1b749bb22493 100644
--- a/lib/in-process-server-test-helpers.js
+++ b/lib/in-process-server-test-helpers.js
@@ -13,6 +13,7 @@
 'use strict'
 
 const config = require('./test-config')
+const serverConfig = require('./server-config')
 
 let startCalled = false
 
@@ -31,13 +32,13 @@ function start() {
   }
   startCalled = true
 
-  const originalArgv = process.argv
-  // Modifying argv during import is a bit dirty, but it works, and avoids
-  // making bigger changes to server.js.
-  process.argv = ['', '', config.port, 'localhost']
+  // Modifying config is a bit dirty, but it works, and avoids making bigger
+  // changes to server.js.
+  serverConfig.bind = {
+    host: 'localhost',
+    port: config.port,
+  }
   const server = require('../server')
-
-  process.argv = originalArgv
   return server
 }
 
diff --git a/lib/server-config.js b/lib/server-config.js
index e3634df31952f328abbf1f04b83ae5c37af80f4a..838b6004d046055652a9bcdd966e9ed4648b9968 100644
--- a/lib/server-config.js
+++ b/lib/server-config.js
@@ -60,6 +60,7 @@ const config = {
         intervalSeconds: process.env.GITHUB_DEBUG_INTERVAL_SECONDS || 300,
       },
     },
+    trace: envFlag(process.env.TRACE_SERVICES),
   },
   font: {
     path: process.env.FONT_PATH || defaults.font.path,
diff --git a/package-lock.json b/package-lock.json
index 4d022c25ec025a193c31a85b828200697db4b4bd..faf5cf46608560a97fc2d6a6e020c55d7bcd20c8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -378,8 +378,7 @@
     "acorn": {
       "version": "2.7.0",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz",
-      "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=",
-      "optional": true
+      "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc="
     },
     "acorn-dynamic-import": {
       "version": "2.0.2",
@@ -756,6 +755,27 @@
         "chalk": "^1.1.3",
         "esutils": "^2.0.2",
         "js-tokens": "^3.0.2"
+      },
+      "dependencies": {
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
       }
     },
     "babel-core": {
@@ -2237,6 +2257,15 @@
         }
       }
     },
+    "camelo": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/camelo/-/camelo-1.1.11.tgz",
+      "integrity": "sha512-kYyJyTKwCcKXtWMNqHogVBgQMRArxK0sLcPaMgDteo4vHr5vRytzBZt6n2T81RrQIU9FPi4MW8X+snzMDQqH0w==",
+      "requires": {
+        "regex-escape": "^3.3.0",
+        "uc-first-array": "^1.0.0"
+      }
+    },
     "camp": {
       "version": "17.2.1",
       "resolved": "https://registry.npmjs.org/camp/-/camp-17.2.1.tgz",
@@ -2334,16 +2363,23 @@
       }
     },
     "chalk": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-      "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
-      "dev": true,
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+      "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
       "requires": {
-        "ansi-styles": "^2.2.1",
-        "escape-string-regexp": "^1.0.2",
-        "has-ansi": "^2.0.0",
-        "strip-ansi": "^3.0.0",
-        "supports-color": "^2.0.0"
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        }
       }
     },
     "chardet": {
@@ -2491,8 +2527,7 @@
             "ansi-regex": {
               "version": "2.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "aproba": {
               "version": "1.2.0",
@@ -2513,14 +2548,12 @@
             "balanced-match": {
               "version": "1.0.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "brace-expansion": {
               "version": "1.1.11",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "balanced-match": "^1.0.0",
                 "concat-map": "0.0.1"
@@ -2535,20 +2568,17 @@
             "code-point-at": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "concat-map": {
               "version": "0.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "console-control-strings": {
               "version": "1.1.0",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "core-util-is": {
               "version": "1.0.2",
@@ -2665,8 +2695,7 @@
             "inherits": {
               "version": "2.0.3",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "ini": {
               "version": "1.3.5",
@@ -2678,7 +2707,6 @@
               "version": "1.0.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "number-is-nan": "^1.0.0"
               }
@@ -2693,7 +2721,6 @@
               "version": "3.0.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "brace-expansion": "^1.1.7"
               }
@@ -2701,14 +2728,12 @@
             "minimist": {
               "version": "0.0.8",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "minipass": {
               "version": "2.2.4",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "safe-buffer": "^5.1.1",
                 "yallist": "^3.0.0"
@@ -2727,7 +2752,6 @@
               "version": "0.5.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "minimist": "0.0.8"
               }
@@ -2808,8 +2832,7 @@
             "number-is-nan": {
               "version": "1.0.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "object-assign": {
               "version": "4.1.1",
@@ -2821,7 +2844,6 @@
               "version": "1.4.0",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "wrappy": "1"
               }
@@ -2907,8 +2929,7 @@
             "safe-buffer": {
               "version": "5.1.1",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "safer-buffer": {
               "version": "2.1.2",
@@ -2944,7 +2965,6 @@
               "version": "1.0.2",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "code-point-at": "^1.0.0",
                 "is-fullwidth-code-point": "^1.0.0",
@@ -2964,7 +2984,6 @@
               "version": "3.0.1",
               "bundled": true,
               "dev": true,
-              "optional": true,
               "requires": {
                 "ansi-regex": "^2.0.0"
               }
@@ -3008,14 +3027,12 @@
             "wrappy": {
               "version": "1.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             },
             "yallist": {
               "version": "3.0.2",
               "bundled": true,
-              "dev": true,
-              "optional": true
+              "dev": true
             }
           }
         },
@@ -3239,7 +3256,6 @@
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
       "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
-      "dev": true,
       "requires": {
         "color-name": "^1.1.1"
       }
@@ -3247,8 +3263,7 @@
     "color-name": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-      "dev": true
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "colors": {
       "version": "1.1.2",
@@ -3725,8 +3740,7 @@
     "cssom": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
-      "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=",
-      "optional": true
+      "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs="
     },
     "cssstyle": {
       "version": "0.2.37",
@@ -4300,6 +4314,22 @@
       "integrity": "sha1-WUjLKG8uSO3DslGnz8H3iDOW1lw=",
       "dev": true
     },
+    "emojic": {
+      "version": "1.1.14",
+      "resolved": "https://registry.npmjs.org/emojic/-/emojic-1.1.14.tgz",
+      "integrity": "sha512-n6yEaHU9GB6AfHTDAvV/263Ds+nIvboyiYYrAOuZox64RP6AlXPW1jbLsT3cBX+8hRVc7IV3P59ODPr2bC0OJA==",
+      "requires": {
+        "camelo": "^1.0.0",
+        "emojilib": "^2.0.2",
+        "iterate-object": "^1.2.0",
+        "r-json": "^1.1.0"
+      }
+    },
+    "emojilib": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.3.0.tgz",
+      "integrity": "sha512-gDrDiITC7W5lvXTtUmk0k9soizsjfY9Wqr2GWsEMX7+cKChoxLt3RnT8zW32BvN1SFDQbi7+D5/+HNu/R+qvKA=="
+    },
     "emojis-list": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
@@ -5655,6 +5685,27 @@
         "chalk": "^1.1.3",
         "error-stack-parser": "^2.0.0",
         "string-length": "^1.0.1"
+      },
+      "dependencies": {
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
       }
     },
     "from2": {
@@ -6853,6 +6904,11 @@
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
+    "iterate-object": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/iterate-object/-/iterate-object-1.3.2.tgz",
+      "integrity": "sha1-JOwVr/pdADnog5aVohwsrh9Ftms="
+    },
     "jison": {
       "version": "0.4.13",
       "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.13.tgz",
@@ -8778,7 +8834,6 @@
           "version": "0.1.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "kind-of": "^3.0.2",
             "longest": "^1.0.1",
@@ -9809,8 +9864,7 @@
         "longest": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "loose-envify": {
           "version": "1.3.1",
@@ -11765,6 +11819,11 @@
         }
       }
     },
+    "r-json": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/r-json/-/r-json-1.2.8.tgz",
+      "integrity": "sha1-dEBWDMHt8AudjZT6MLytfd6U6uI="
+    },
     "ramda": {
       "version": "0.25.0",
       "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
@@ -12153,6 +12212,11 @@
         "private": "^0.1.6"
       }
     },
+    "regex-escape": {
+      "version": "3.4.8",
+      "resolved": "https://registry.npmjs.org/regex-escape/-/regex-escape-3.4.8.tgz",
+      "integrity": "sha512-DG0VFPTDwfl+XsuVaM/0RmGJvCpZNB9UG/limzbev50XQ44G4mbOG+0Eh5M7Al9JB68NbP7YeY1KhDzpnX7qSw=="
+    },
     "regex-not": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@@ -13618,10 +13682,19 @@
       "dev": true
     },
     "supports-color": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
-      "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
-      "dev": true
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+      "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+      "requires": {
+        "has-flag": "^3.0.0"
+      },
+      "dependencies": {
+        "has-flag": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+          "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+        }
+      }
     },
     "supports-hyperlinks": {
       "version": "1.0.1",
@@ -14121,6 +14194,19 @@
       "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==",
       "dev": true
     },
+    "uc-first-array": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/uc-first-array/-/uc-first-array-1.1.8.tgz",
+      "integrity": "sha512-LTAnb8G/BxmXyBqiUxRhlbYt+IPCCpAOJJsC3xp5cwtXZKdtBfBxAMWRJlNzknM+Gpw3DRD2K3sQ64srroRf3w==",
+      "requires": {
+        "ucfirst": "^1.0.0"
+      }
+    },
+    "ucfirst": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ucfirst/-/ucfirst-1.0.0.tgz",
+      "integrity": "sha1-ThBbZEjQXiZOzsQ14LkZNjxfLy8="
+    },
     "uglify-es": {
       "version": "3.3.9",
       "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
@@ -14496,6 +14582,27 @@
       "requires": {
         "chalk": "^1.1.1",
         "object-assign": "^4.0.1"
+      },
+      "dependencies": {
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
       }
     },
     "verror": {
@@ -14892,6 +14999,19 @@
         "moment": "^2.11.2"
       },
       "dependencies": {
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^2.2.1",
+            "escape-string-regexp": "^1.0.2",
+            "has-ansi": "^2.0.0",
+            "strip-ansi": "^3.0.0",
+            "supports-color": "^2.0.0"
+          }
+        },
         "debug": {
           "version": "2.6.9",
           "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -14906,6 +15026,12 @@
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
           "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
           "dev": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
         }
       }
     },
diff --git a/package.json b/package.json
index 50d0186f82a1ea0cb2f43c862564f4c89fe6159a..8c5281ccddc1b5806e4c1c79fa50e193fe01361e 100644
--- a/package.json
+++ b/package.json
@@ -23,8 +23,10 @@
   },
   "dependencies": {
     "camp": "~17.2.1",
+    "chalk": "^2.4.1",
     "chrome-web-store-item-property": "~1.1.2",
     "dot": "~1.1.2",
+    "emojic": "^1.1.14",
     "escape-string-regexp": "^1.0.5",
     "glob": "^7.1.1",
     "gm": "^1.23.0",
@@ -69,6 +71,7 @@
     "test:js:server": "HANDLE_INTERNAL_ERRORS=false mocha \"*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
     "test:integration": "mocha \"services/**/*.integration.js\"",
     "test:services": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js",
+    "test:services:trace": "TRACE_SERVICES=true npm run test:services -- $*",
     "test:services:pr:prepare": "node lib/service-test-runner/pull-request-services-cli.js > pull-request-services.log",
     "test:services:pr:run": "HANDLE_INTERNAL_ERRORS=false mocha --delay lib/service-test-runner/cli.js --stdin < pull-request-services.log",
     "test:services:pr": "npm run test:services:pr:prepare && npm run test:services:pr:run",
diff --git a/services/base.js b/services/base.js
index a5450c6ea6a0da58efbd6592bd1ca8f209c69a88..3c1e5a51f7945a4cbe9680d740c4fdafd68b4d24 100644
--- a/services/base.js
+++ b/services/base.js
@@ -1,6 +1,9 @@
 'use strict'
 
 const Joi = require('joi')
+// See available emoji at http://emoji.muan.co/
+const emojic = require('emojic')
+const chalk = require('chalk')
 const { NotFound, InvalidResponse, Inaccessible } = require('./errors')
 const queryString = require('query-string')
 const {
@@ -10,6 +13,14 @@ const {
   setBadgeColor,
 } = require('../lib/badge-data')
 const { checkErrorResponse, asJson } = require('../lib/error-helper')
+// Config is loaded globally but it would be better to inject it. To do that,
+// there needs to be one instance of the service created at registration time,
+// which gets the config injected into it, instead of one instance per request.
+// That way most of the current static methods could become instance methods,
+// thereby gaining access to the injected config.
+const {
+  services: { trace: enableTraceLogging },
+} = require('../lib/server-config')
 
 class BaseService {
   constructor({ sendAndCacheRequest }, { handleInternalErrors }) {
@@ -141,10 +152,20 @@ class BaseService {
   }
 
   async invokeHandler(namedParams, queryParams) {
+    const logTrace = (...args) => this.constructor.logTrace(...args)
+    logTrace(
+      'inbound',
+      emojic.womanCook,
+      'Service class',
+      this.constructor.name
+    )
+    logTrace('inbound', emojic.ticket, 'Named params', namedParams)
+    logTrace('inbound', emojic.crayon, 'Query params', queryParams)
     try {
       return await this.handle(namedParams, queryParams)
     } catch (error) {
       if (error instanceof NotFound) {
+        logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
         return {
           message: error.prettyMessage,
           color: 'red',
@@ -153,18 +174,36 @@ class BaseService {
         error instanceof InvalidResponse ||
         error instanceof Inaccessible
       ) {
+        logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
         return {
           message: error.prettyMessage,
           color: 'lightgray',
         }
       } else if (this._handleInternalErrors) {
-        console.log(error)
+        if (
+          !logTrace(
+            'unhandledError',
+            emojic.boom,
+            'Unhandled internal error',
+            error
+          )
+        ) {
+          // This is where we end up if an unhandled exception is thrown in
+          // production. Send the error to the logs.
+          console.log(error)
+        }
         return {
           label: 'shields',
           message: 'internal error',
           color: 'lightgray',
         }
       } else {
+        logTrace(
+          'unhandledError',
+          emojic.boom,
+          'Unhandled internal error',
+          error
+        )
         throw error
       }
     }
@@ -232,6 +271,7 @@ class BaseService {
             namedParams,
             queryParams
           )
+          this.logTrace('outbound', emojic.shield, 'Service data', serviceData)
           const badgeData = this._makeBadgeData(queryParams, serviceData)
 
           // Assumes the final capture group is the extension
@@ -241,6 +281,31 @@ class BaseService {
       })
     )
   }
+
+  static _formatLabelForStage(stage, label) {
+    const colorFn = {
+      inbound: chalk.black.bgBlue,
+      fetch: chalk.black.bgYellow,
+      validate: chalk.black.bgGreen,
+      unhandledError: chalk.white.bgRed,
+      outbound: chalk.black.bgBlue,
+    }[stage]
+    return colorFn(` ${label} `)
+  }
+
+  static logTrace(stage, symbol, label, ...content) {
+    if (enableTraceLogging) {
+      console.log(
+        this._formatLabelForStage(stage, label),
+        symbol,
+        '\n',
+        ...content
+      )
+      return true
+    } else {
+      return false
+    }
+  }
 }
 
 class BaseJsonService extends BaseService {
@@ -250,29 +315,47 @@ class BaseJsonService extends BaseService {
       stripUnknown: true,
     })
     if (error) {
+      this.logTrace(
+        'error',
+        emojic.womanShrugging,
+        'Response did not match schema',
+        error.message
+      )
       throw new InvalidResponse({
         prettyMessage: 'invalid json response',
         underlyingError: error,
       })
     } else {
+      this.logTrace('validate', emojic.bathtub, 'JSON after validation', value)
       return value
     }
   }
 
   async _requestJson({ schema, url, options = {}, notFoundMessage }) {
+    const logTrace = (...args) => this.constructor.logTrace('fetch', ...args)
     if (!schema || !schema.isJoi) {
       throw Error('A Joi schema is required')
     }
-    return this._sendAndCacheRequest(url, {
+    const mergedOptions = {
       ...{ headers: { Accept: 'application/json' } },
       ...options,
-    })
+    }
+    logTrace(emojic.bowAndArrow, 'Request', url, '\n', mergedOptions)
+    return this._sendAndCacheRequest(url, mergedOptions)
+      .then(({ res, buffer }) => {
+        logTrace(emojic.dart, 'Response status code', res.statusCode)
+        return { res, buffer }
+      })
       .then(
         checkErrorResponse.asPromise(
           notFoundMessage ? { notFoundMessage: notFoundMessage } : undefined
         )
       )
       .then(asJson)
+      .then(json => {
+        logTrace(emojic.dart, 'Response JSON (before validation)', json)
+        return json
+      })
       .then(json => this.constructor._validate(json, schema))
   }
 }
diff --git a/services/base.spec.js b/services/base.spec.js
index b553f43f6af6a8a548fc21d8d25af7092415058e..5faf615d7eec25e2f163d1e52a3da89ee6b29f64 100644
--- a/services/base.spec.js
+++ b/services/base.spec.js
@@ -83,6 +83,48 @@ describe('BaseService', () => {
     expect(serviceData).to.deep.equal({ message: 'Hello bar.bar.bar!' })
   })
 
+  describe('Logging', function() {
+    let sandbox
+    beforeEach(function() {
+      sandbox = sinon.createSandbox()
+    })
+    afterEach(function() {
+      sandbox.restore()
+    })
+    beforeEach(function() {
+      sinon.stub(DummyService, 'logTrace')
+    })
+    it('Invokes the logger as expected', async function() {
+      const serviceInstance = new DummyService({}, defaultConfig)
+      await serviceInstance.invokeHandler(
+        {
+          namedParamA: 'bar.bar.bar',
+        },
+        { queryParamA: '!' }
+      )
+      expect(DummyService.logTrace).to.be.calledWithMatch(
+        'inbound',
+        sinon.match.string,
+        'Service class',
+        'DummyService'
+      )
+      expect(DummyService.logTrace).to.be.calledWith(
+        'inbound',
+        sinon.match.string,
+        'Named params',
+        {
+          namedParamA: 'bar.bar.bar',
+        }
+      )
+      expect(DummyService.logTrace).to.be.calledWith(
+        'inbound',
+        sinon.match.string,
+        'Query params',
+        { queryParamA: '!' }
+      )
+    })
+  })
+
   describe('Error handling', function() {
     it('Handles internal errors', async function() {
       const serviceInstance = new DummyService(