xpra icon
Bug tracker and wiki

Ticket #752: log-tool.patch

File log-tool.patch, 13.9 KB (added by Antoine Martin, 5 years ago)

basic implementation of a logging debug tool, integrated / launched from the bug report tool

  • xpra/client/gtk_base/bug_report.py

     
    3737    def init(self, show_about=True, get_server_info=None, opengl_info=None, includes={}):
    3838        self.get_server_info = get_server_info
    3939        self.includes = includes
     40        self.log_tool = None
    4041        self.window = gtk.Window()
    4142        self.window.connect("destroy", self.close)
    4243        self.window.set_default_size(400, 300)
     
    4445        self.window.set_title("Xpra Bug Report")
    4546        self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535))
    4647
     48        settings = self.window.get_settings()
     49        settings.set_property('gtk-button-images', True)
     50
    4751        icon_pixbuf = self.get_icon("bugs.png")
    4852        if icon_pixbuf:
    4953            self.window.set_icon(icon_pixbuf)
     
    5761        icon_pixbuf = self.get_icon("xpra.png")
    5862        if icon_pixbuf and show_about:
    5963            logo_button = gtk.Button("")
    60             settings = logo_button.get_settings()
    61             settings.set_property('gtk-button-images', True)
    6264            logo_button.connect("clicked", about)
    6365            logo_button.set_tooltip_text("About")
    6466            image = gtk.Image()
     
    156158            def get_screenshot():
    157159                #take_screenshot() returns: w, h, "png", rowstride, data
    158160                return take_screenshot_fn()[4]
     161        def log_tool(*args):
     162            if self.log_tool is None:
     163                from xpra.client.gtk_base.log_tool import get_log_tool
     164                self.log_tool = get_log_tool()
     165            self.log_tool.show()
     166        def get_log_output():
     167            if self.log_tool is None:
     168                return "Log Tool not configured"
     169            return self.log_tool.get_text_data()
    159170        self.toggles = (
    160                    ("system",       "txt",  "System",           get_sys_info,   "Xpra version, platform and host information"),
    161                    ("network",      "txt",  "Network",          get_net_info,   "Compression, packet encoding and encryption"),
    162                    ("encoding",     "txt",  "Encodings",        codec_versions, "Picture encodings supported"),
    163                    ("opengl",       "txt",  "OpenGL",           get_gl_info,    "OpenGL driver and features"),
    164                    ("sound",        "txt",  "Sound",            get_sound_info, "Sound codecs and GStreamer version information"),
    165                    ("keyboard",     "txt",  "Keyboard Mapping", get_gtk_keymap, "Keyboard layout and key mapping"),
    166                    ("xpra-info",    "txt",  "Server Info",      self.get_server_info, "Full server information from 'xpra info'"),
    167                    ("screenshot",   "png",  "Screenshot",       get_screenshot, ""),
     171                   ("system",       "txt",  "System",           get_sys_info,
     172                            None,   None,   "Xpra version, platform and host information"),
     173                   ("network",      "txt",  "Network",          get_net_info,
     174                            None,   None,   "Compression, packet encoding and encryption"),
     175                   ("encoding",     "txt",  "Encodings",        codec_versions,
     176                            None,   None,   "Picture encodings supported"),
     177                   ("opengl",       "txt",  "OpenGL",           get_gl_info,
     178                            None,   None,   "OpenGL driver and features"),
     179                   ("sound",        "txt",  "Sound",            get_sound_info,
     180                            None,   None,   "Sound codecs and GStreamer version information"),
     181                   ("keyboard",     "txt",  "Keyboard Mapping", get_gtk_keymap,
     182                            None,   None,   "Keyboard layout and key mapping"),
     183                   ("xpra-info",    "txt",  "Server Info",      self.get_server_info,
     184                            None,   None, "Full server information from 'xpra info'"),
     185                   ("screenshot",   "png",  "Screenshot",       get_screenshot,
     186                            None,   None, ""),
     187                   ("log-output",   "txt",  "Log Output",       get_log_output,
     188                            "Log Tool", log_tool,   "Collect debug logging")
    168189                   )
    169         for name, _, title, value_cb, tooltip in self.toggles:
     190        for name, _, title, value_cb, tool_label, tool_cb, tooltip in self.toggles:
    170191            cb = gtk.CheckButton(title+[" (not available)", ""][bool(value_cb)])
    171192            cb.set_active(self.includes.get(name, True))
    172193            cb.set_sensitive(bool(value_cb))
    173194            cb.set_tooltip_text(tooltip)
    174             ibox.pack_start(cb)
     195            h = gtk.HBox(False, 20)
     196            h.add(cb)
     197            if tool_cb:
     198                btn = gtk.Button(tool_label)
     199                btn.connect("clicked", tool_cb)
     200                h.add(btn)
     201            ibox.pack_start(h)
    175202            setattr(self, name, cb)
    176203
    177204        # Buttons:
  • xpra/client/gtk_base/log_tool.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (C) 2014 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7
     8import os.path
     9import sys
     10import signal
     11
     12from xpra.platform.gui import init as gui_init
     13gui_init()
     14
     15from xpra.gtk_common.gobject_compat import import_gtk, import_gdk, import_gobject, import_pango, is_gtk3
     16
     17gtk = import_gtk()
     18gdk = import_gdk()
     19gobject = import_gobject()
     20gobject.threads_init()
     21pango = import_pango()
     22
     23
     24from xpra.log import get_all_loggers
     25from xpra.gtk_common.gtk_util import gtk_main, add_close_accel, scaled_image, pixbuf_new_from_file, \
     26                                    JUSTIFY_LEFT, WIN_POS_CENTER, STATE_NORMAL, FILE_CHOOSER_ACTION_SAVE, choose_file
     27from xpra.platform.paths import get_icon_dir
     28from xpra.log import Logger, enable_debug_for
     29import logging
     30log = Logger("util")
     31
     32
     33singleton = None
     34def get_log_tool():
     35    global singleton
     36    if not singleton:
     37        singleton = LogTool()
     38    return singleton
     39
     40
     41class LogTool(logging.Handler):
     42
     43    #handler functions:
     44    def __init__(self):
     45        logging.Handler.__init__(self, logging.DEBUG)
     46
     47    def handle(self, record):
     48        self.new_log_message(record.getMessage())
     49
     50    def emit(self, record):
     51        self.new_log_message(record.getMessage())
     52
     53    def createLock(self):
     54        self.lock = None
     55
     56
     57    def init(self, cancel_label="Cancel"):
     58        self.window = gtk.Window()
     59        self.window.connect("destroy", self.close)
     60        self.window.set_default_size(600, 400)
     61        self.window.set_border_width(20)
     62        self.window.set_title("Xpra Logging Tool")
     63        self.window.modify_bg(STATE_NORMAL, gdk.Color(red=65535, green=65535, blue=65535))
     64
     65        settings = self.window.get_settings()
     66        settings.set_property('gtk-button-images', True)
     67
     68        icon_pixbuf = self.get_icon("bugs.png")
     69        if icon_pixbuf:
     70            self.window.set_icon(icon_pixbuf)
     71        self.window.set_position(WIN_POS_CENTER)
     72
     73        vbox = gtk.VBox(False, 0)
     74        vbox.set_spacing(15)
     75
     76        # Title
     77        label = gtk.Label("Xpra Log Debugging Tool")
     78        label.modify_font(pango.FontDescription("sans 14"))
     79        vbox.pack_start(label)
     80
     81        hbox = gtk.HBox(False, 0)
     82        vbox.pack_start(hbox)
     83        def btn(label, tooltip, callback, icon_name=None):
     84            btn = gtk.Button(label)
     85            btn.set_tooltip_text(tooltip)
     86            btn.connect("clicked", callback)
     87            if icon_name:
     88                icon = self.get_icon(icon_name)
     89                #log.info("get_icon(%s)=%s", icon_name, icon)
     90                if icon:
     91                    btn.set_image(scaled_image(icon, 24))
     92            hbox.pack_start(btn)
     93            return btn
     94
     95        self.log_records = []
     96        self.update_pending = False
     97        self.capturing = False
     98        self.start_stop_btn = btn("Start", "", self.toggle_log_capture, "browse.png")
     99        btn("Clear", "Clear the log buffer", self.clear, "remove.png")
     100        if not is_gtk3():
     101            #clipboard does not work in gtk3..
     102            btn("Copy to clipboard", "Copy all data to clipboard", self.copy_clicked, "clipboard.png")
     103        btn("Save", "Save Log Output", self.save_clicked, "download.png")
     104        btn(cancel_label, "", self.close, "quit.png")
     105
     106        #the box containing all the log output:
     107        ibox = gtk.VBox(False, 0)
     108        ibox.set_spacing(3)
     109        vbox.pack_start(ibox)
     110        # log_data
     111        al = gtk.Alignment(xalign=0, yalign=0.5, xscale=0.0, yscale=0)
     112        al.add(gtk.Label("Log Output:"))
     113        ibox.pack_start(al)
     114        self.log_view = gtk.TextView()
     115        self.log_view.set_editable(False)
     116        self.log_view.set_accepts_tab(True)
     117        self.log_view.set_justification(JUSTIFY_LEFT)
     118        self.log_view.set_border_width(2)
     119        self.log_view.set_size_request(500, 300)
     120        ibox.pack_start(self.log_view, expand=False, fill=False)
     121
     122        def accel_close(*args):
     123            self.close()
     124        add_close_accel(self.window, accel_close)
     125        vbox.show_all()
     126        self.window.vbox = vbox
     127        self.window.add(vbox)
     128
     129
     130    def show(self):
     131        log("show()")
     132        self.window.show()
     133        self.window.present()
     134
     135    def hide(self):
     136        log("hide()")
     137        self.window.hide()
     138
     139    def close(self, *args):
     140        log("close%s", args)
     141        self.hide()
     142
     143    def destroy(self, *args):
     144        log("destroy%s", args)
     145        if self.window:
     146            self.window.destroy()
     147            self.window = None
     148
     149
     150    def run(self):
     151        log("run()")
     152        gtk_main()
     153        log("run() gtk_main done")
     154
     155    def quit(self, *args):
     156        log("quit%s", args)
     157        if gtk.main_level()>0:
     158            self.destroy()
     159            gtk.main_quit()
     160
     161
     162    def get_icon(self, icon_name):
     163        icon_filename = os.path.join(get_icon_dir(), icon_name)
     164        if os.path.exists(icon_filename):
     165            return pixbuf_new_from_file(icon_filename)
     166        return None
     167
     168
     169    def toggle_log_capture(self, *args):
     170        log.info("toggle_log_capture%s", args)
     171        if self.capturing:
     172            self.stop_capture()
     173            self.capturing = False
     174            icon = self.get_icon("close.png")
     175            self.start_stop_btn.set_label("Start")
     176        else:
     177            self.start_capture()
     178            self.capturing = True
     179            icon = self.get_icon("browse.png")
     180            self.start_stop_btn.set_label("Stop")
     181        if icon:
     182            self.start_stop_btn.set_image(scaled_image(icon, 24))
     183
     184    def start_capture(self):
     185        for xl in get_all_loggers():
     186            l = xl.logger       #xpra logger has a logging.logger
     187            l.saved_handlers = l.handlers
     188            l.saved_propagate = l.propagate
     189            l.handlers = [self]
     190            l.propagate = 0
     191
     192    def stop_capture(self):
     193        for xl in get_all_loggers():
     194            l = xl.logger       #xpra logger has a logging.logger
     195            l.handlers = l.saved_handlers
     196            l.propagate = l.saved_propagate
     197
     198
     199    def clear(self, *args):
     200        self.log_records = []
     201        self.update_text_buffer()
     202
     203    def new_log_message(self, message):
     204        #warning: this can run from any thread which uses logging!
     205        print("new_log_message(%s)", message)
     206        self.log_records.append(message)
     207        if not self.update_pending:
     208            #maybe slow down if the buffers get big?
     209            self.update_pending = True
     210            gobject.timeout_add(100, self.update_text_buffer)
     211
     212
     213    def update_text_buffer(self):
     214        self.update_pending = False
     215        buf = gtk.TextBuffer()
     216        buf.set_text(self.get_text_data())
     217        self.log_view.set_buffer(buf)
     218
     219
     220    def get_text_data(self):
     221        return "\n".join(self.log_records)
     222
     223    def copy_clicked(self, *args):
     224        data = self.get_text_data()
     225        clipboard = gtk.clipboard_get(gdk.SELECTION_CLIPBOARD)
     226        clipboard.set_text(data)
     227        log.info("%s characters copied to clipboard", len(data))
     228
     229    def save_clicked(self, *args):
     230        file_filter = gtk.FileFilter()
     231        file_filter.set_name("txt")
     232        file_filter.add_pattern("*.txt")
     233        choose_file(self.window, "Save Log Output Data", FILE_CHOOSER_ACTION_SAVE, gtk.STOCK_SAVE, self.do_save)
     234
     235    def do_save(self, filename):
     236        log("do_save(%s)", filename)
     237        with open(filename, 'a') as f:
     238            f.write(self.get_text_data())
     239
     240
     241def main():
     242    from xpra.platform import init as platform_init
     243    from xpra.platform.gui import ready as gui_ready
     244    platform_init("Xpra-Log-Debugging-Tool", "Xpra Log Debugging Tool")
     245
     246    #logging init:
     247    if "-v" in sys.argv:
     248        enable_debug_for("util")
     249
     250    from xpra.os_util import SIGNAMES
     251    from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable
     252    gtk_main_quit_on_fatal_exceptions_enable()
     253
     254    app = LogTool()
     255    app.close = app.quit
     256    app.init("Quit")
     257    def app_signal(signum, frame):
     258        print("")
     259        log.info("got signal %s", SIGNAMES.get(signum, signum))
     260        app.quit()
     261    signal.signal(signal.SIGINT, app_signal)
     262    signal.signal(signal.SIGTERM, app_signal)
     263    try:
     264        gui_ready()
     265        app.show()
     266        app.run()
     267    except KeyboardInterrupt:
     268        pass
     269    return 0
     270
     271
     272if __name__ == "__main__":
     273    v = main()
     274    sys.exit(v)