Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/buildbot/buildbot/status/web/logs.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildbot/buildbot/status/web/logs.py')
-rw-r--r--buildbot/buildbot/status/web/logs.py171
1 files changed, 171 insertions, 0 deletions
diff --git a/buildbot/buildbot/status/web/logs.py b/buildbot/buildbot/status/web/logs.py
new file mode 100644
index 0000000..dfcf7f0
--- /dev/null
+++ b/buildbot/buildbot/status/web/logs.py
@@ -0,0 +1,171 @@
+
+from zope.interface import implements
+from twisted.python import components
+from twisted.spread import pb
+from twisted.web import html, server
+from twisted.web.resource import Resource
+from twisted.web.error import NoResource
+
+from buildbot import interfaces
+from buildbot.status import builder
+from buildbot.status.web.base import IHTMLLog, HtmlResource
+
+
+textlog_stylesheet = """
+<style type="text/css">
+ div.data {
+ font-family: "Courier New", courier, monotype;
+ }
+ span.stdout {
+ font-family: "Courier New", courier, monotype;
+ }
+ span.stderr {
+ font-family: "Courier New", courier, monotype;
+ color: red;
+ }
+ span.header {
+ font-family: "Courier New", courier, monotype;
+ color: blue;
+ }
+</style>
+"""
+
+class ChunkConsumer:
+ implements(interfaces.IStatusLogConsumer)
+
+ def __init__(self, original, textlog):
+ self.original = original
+ self.textlog = textlog
+ def registerProducer(self, producer, streaming):
+ self.producer = producer
+ self.original.registerProducer(producer, streaming)
+ def unregisterProducer(self):
+ self.original.unregisterProducer()
+ def writeChunk(self, chunk):
+ formatted = self.textlog.content([chunk])
+ try:
+ self.original.write(formatted)
+ except pb.DeadReferenceError:
+ self.producing.stopProducing()
+ def finish(self):
+ self.textlog.finished()
+
+
+# /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname
+class TextLog(Resource):
+ # a new instance of this Resource is created for each client who views
+ # it, so we can afford to track the request in the Resource.
+ implements(IHTMLLog)
+
+ asText = False
+ subscribed = False
+
+ def __init__(self, original):
+ Resource.__init__(self)
+ self.original = original
+
+ def getChild(self, path, req):
+ if path == "text":
+ self.asText = True
+ return self
+ return HtmlResource.getChild(self, path, req)
+
+ def htmlHeader(self, request):
+ title = "Log File contents"
+ data = "<html>\n<head><title>" + title + "</title>\n"
+ data += textlog_stylesheet
+ data += "</head>\n"
+ data += "<body vlink=\"#800080\">\n"
+ texturl = request.childLink("text")
+ data += '<a href="%s">(view as text)</a><br />\n' % texturl
+ data += "<pre>\n"
+ return data
+
+ def content(self, entries):
+ spanfmt = '<span class="%s">%s</span>'
+ data = ""
+ for type, entry in entries:
+ if type >= len(builder.ChunkTypes) or type < 0:
+ # non-std channel, don't display
+ continue
+ if self.asText:
+ if type != builder.HEADER:
+ data += entry
+ else:
+ data += spanfmt % (builder.ChunkTypes[type],
+ html.escape(entry))
+ return data
+
+ def htmlFooter(self):
+ data = "</pre>\n"
+ data += "</body></html>\n"
+ return data
+
+ def render_HEAD(self, request):
+ if self.asText:
+ request.setHeader("content-type", "text/plain")
+ else:
+ request.setHeader("content-type", "text/html")
+
+ # vague approximation, ignores markup
+ request.setHeader("content-length", self.original.length)
+ return ''
+
+ def render_GET(self, req):
+ self.req = req
+
+ if self.asText:
+ req.setHeader("content-type", "text/plain")
+ else:
+ req.setHeader("content-type", "text/html")
+
+ if not self.asText:
+ req.write(self.htmlHeader(req))
+
+ self.original.subscribeConsumer(ChunkConsumer(req, self))
+ return server.NOT_DONE_YET
+
+ def finished(self):
+ if not self.req:
+ return
+ try:
+ if not self.asText:
+ self.req.write(self.htmlFooter())
+ self.req.finish()
+ except pb.DeadReferenceError:
+ pass
+ # break the cycle, the Request's .notifications list includes the
+ # Deferred (from req.notifyFinish) that's pointing at us.
+ self.req = None
+
+components.registerAdapter(TextLog, interfaces.IStatusLog, IHTMLLog)
+
+
+class HTMLLog(Resource):
+ implements(IHTMLLog)
+
+ def __init__(self, original):
+ Resource.__init__(self)
+ self.original = original
+
+ def render(self, request):
+ request.setHeader("content-type", "text/html")
+ return self.original.html
+
+components.registerAdapter(HTMLLog, builder.HTMLLogFile, IHTMLLog)
+
+
+class LogsResource(HtmlResource):
+ addSlash = True
+
+ def __init__(self, step_status):
+ HtmlResource.__init__(self)
+ self.step_status = step_status
+
+ def getChild(self, path, req):
+ for log in self.step_status.getLogs():
+ if path == log.getName():
+ if log.hasContents():
+ return IHTMLLog(interfaces.IStatusLog(log))
+ return NoResource("Empty Log '%s'" % path)
+ return HtmlResource.getChild(self, path, req)