Merge "Catch and log url pattern formatting errors" into feature/zuulv3
diff --git a/zuul/model.py b/zuul/model.py
index 2893e4f..4880be1 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -14,7 +14,6 @@
 
 import abc
 import copy
-
 import logging
 import os
 import re
@@ -1251,6 +1250,7 @@
     holds the current `BuildSet` as well as all previous `BuildSets` that were
     produced for this `QueueItem`.
     """
+    log = logging.getLogger("zuul.QueueItem")
 
     def __init__(self, queue, change):
         self.pipeline = queue.pipeline
@@ -1508,6 +1508,35 @@
             fakebuild.result = 'SKIPPED'
             self.addBuild(fakebuild)
 
+    def formatUrlPattern(self, url_pattern, job=None, build=None):
+        url = None
+        # Produce safe versions of objects which may be useful in
+        # result formatting, but don't allow users to crawl through
+        # the entire data structure where they might be able to access
+        # secrets, etc.
+        safe_change = self.change.getSafeAttributes()
+        safe_pipeline = self.pipeline.getSafeAttributes()
+        safe_job = job.getSafeAttributes()
+        safe_build = build.getSafeAttributes()
+        try:
+            url = url_pattern.format(change=safe_change,
+                                     pipeline=safe_pipeline,
+                                     job=safe_job,
+                                     build=safe_build)
+        except KeyError as e:
+            self.log.error("Error while formatting url for job %s: unknown "
+                           "key %s in pattern %s"
+                           % (job, e.message, url_pattern))
+        except AttributeError as e:
+            self.log.error("Error while formatting url for job %s: unknown "
+                           "attribute %s in pattern %s"
+                           % (job, e.message, url_pattern))
+        except Exception:
+            self.log.exception("Error while formatting url for job %s with "
+                               "pattern %s:" % (job, url_pattern))
+
+        return url
+
     def formatJobResult(self, job):
         build = self.current_build_set.getBuild(job.name)
         result = build.result
@@ -1523,22 +1552,8 @@
             if job.failure_url:
                 pattern = job.failure_url
         url = None
-        # Produce safe versions of objects which may be useful in
-        # result formatting, but don't allow users to crawl through
-        # the entire data structure where they might be able to access
-        # secrets, etc.
-        safe_change = self.change.getSafeAttributes()
-        safe_pipeline = self.pipeline.getSafeAttributes()
-        safe_job = job.getSafeAttributes()
-        safe_build = build.getSafeAttributes()
         if pattern:
-            try:
-                url = pattern.format(change=safe_change,
-                                     pipeline=safe_pipeline,
-                                     job=safe_job,
-                                     build=safe_build)
-            except Exception:
-                pass  # FIXME: log this or something?
+            url = self.formatUrlPattern(pattern, job, build)
         if not url:
             url = build.url or job.name
         return (result, url)