Repository: DataDog/integrations-core

Issue:
[postgres] Improve config reading errors
I had this `postgres.yaml`:

```
init_config:

instances:
  - host: pepepe
    ...
    custom_metrics:
    - query: SELECT %s FROM pg_locks WHERE granted = false;
      metrics:
        count(distinct pid): [postgresql.connections_locked]
      descriptors: []
      relation: false
```

with a few other hosts and custom metrics. When deploying this I got the following error:

```
2017-02-13 15:33:14 UTC | ERROR | dd.collector | checks.postgres(__init__.py:762) | Check 'postgres' instance #0 failed
Traceback (most recent call last):
  File "/opt/datadog-agent/agent/checks/__init__.py", line 745, in run
    self.check(copy.deepcopy(instance))
  File "/opt/datadog-agent/agent/checks.d/postgres.py", line 606, in check
    custom_metrics = self._get_custom_metrics(instance.get('custom_metrics', []), key)
  File "/opt/datadog-agent/agent/checks.d/postgres.py", line 576, in _get_custom_metrics
    for ref, (_, mtype) in m['metrics'].iteritems():
ValueError: need more than 1 value to unpack
```

This was caused by a missing metric type in the yaml above i.e. it should have been `[postgresql.connections_locked, GAUGE]`.
Because the error message is unclear and also doesn't point to the offending metric (remember I have other hosts and custom metrics), it took me a couple of hours to figure out the cause of this error.
Please consider improving the error messages around config reading.



Discussion:
Thanks a lot for this report @mausch!

We can't validate the config in a consistent manner, which makes something like this tricky to make the error better. We will work on making this a lot better in the future.

However, what we can do in the very near future is make the documentation both online and in the config yaml itself a lot better. The documentation for the postgres check does not make it clear how to use the custom metrics very well, so better documentation will definitely help to assuage this issue!

Thanks again for your report, we really appreciate this and I will add this to our issue board.
> We can't validate the config in a consistent manner

Not sure what this means exactly, but generally speaking a good error message should give the user enough context so that they can readily fix it.

Better docs are great, but ultimately people will always make mistakes when defining complex config so you need good error messages.

In this particular case, it could be as easy as wrapping the iteration in `_get_custom_metrics` with a `try..except` and in the exception handler wrap the exception with another one that displays the metric being processed (e.g. `raise CheckException("Error processing custom metric: " + str(m)) from e`)

More generally, avoiding partial functions (like tuple unpacking in Python) makes it much easier to validate input and report errors correctly.
Adding to our queue, this would make the life of support engineers much easier, thanks for reporting and for the suggestions.

Resolving patch:
diff --git a/postgres/check.py b/postgres/check.py
--- a/postgres/check.py
+++ b/postgres/check.py
@@ -651,14 +651,17 @@ def _get_custom_metrics(self, custom_metrics, key):
 
             self.log.debug("Metric: {0}".format(m))
 
-            for ref, (_, mtype) in m['metrics'].iteritems():
-                cap_mtype = mtype.upper()
-                if cap_mtype not in ('RATE', 'GAUGE', 'MONOTONIC'):
-                    raise CheckException("Collector method {0} is not known."
-                        " Known methods are RATE, GAUGE, MONOTONIC".format(cap_mtype))
-
-                m['metrics'][ref][1] = getattr(PostgreSql, cap_mtype)
-                self.log.debug("Method: %s" % (str(mtype)))
+            try:
+                for ref, (_, mtype) in m['metrics'].iteritems():
+                    cap_mtype = mtype.upper()
+                    if cap_mtype not in ('RATE', 'GAUGE', 'MONOTONIC'):
+                        raise CheckException("Collector method {0} is not known."
+                            " Known methods are RATE, GAUGE, MONOTONIC".format(cap_mtype))
+
+                    m['metrics'][ref][1] = getattr(PostgreSql, cap_mtype)
+                    self.log.debug("Method: %s" % (str(mtype)))
+            except Exception as e:
+                raise CheckException("Error processing custom metric '{}': {}".format(m, e))
 
         self.custom_metrics[key] = custom_metrics
         return custom_metrics


Test patch: