Merge branch 'gtk-log-suppress'

Allow suppressing GTK log messages.

See #22424.
This commit is contained in:
Vadim Zeitlin
2022-05-17 16:36:06 +02:00
3 changed files with 232 additions and 25 deletions

View File

@@ -0,0 +1,135 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/gtk/private/log.h
// Purpose: Support for filtering GTK log messages.
// Author: Vadim Zeitlin
// Created: 2022-05-11
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_GTK_PRIVATE_LOG_H_
#define _WX_GTK_PRIVATE_LOG_H_
#include <glib.h>
// Support for custom log writers is only available in glib 2.50 or later.
#if GLIB_CHECK_VERSION(2, 50, 0)
#define wxHAS_GLIB_LOG_WRITER
#endif
namespace wxGTKImpl
{
#ifdef wxHAS_GLIB_LOG_WRITER
// LogFilter is the base class for filtering GTK log messages
//
// Note that all members of this class are defined in src/gtk/app.cpp.
class LogFilter
{
public:
LogFilter()
{
m_next = NULL;
}
// Function to call to install this filter as the active one.
// Does nothing and just returns false if run-time glib version is too old.
bool Install();
protected:
// Function to override in the derived class to actually filter: return
// true if the message should be suppressed or false if it should be passed
// through to the default writer (which may, or not, show it).
virtual bool Filter(GLogLevelFlags log_level,
const GLogField* fields,
gsize n_fields) const = 0;
// Typically called from the derived class dtor to stop using this filter.
void Uninstall();
private:
// The function used as glib log writer.
static GLogWriterOutput
wx_log_writer(GLogLevelFlags log_level,
const GLogField *fields,
gsize n_fields,
gpointer user_data);
// False initially, set to true when we install wx_log_writer() as the log
// writer. Once we do it, we never change it any more.
static bool ms_installed;
// We maintain a simple linked list of log filters and this is the head of
// this list.
static LogFilter* ms_first;
// Next entry in the linked list, may be null.
LogFilter* m_next;
wxDECLARE_NO_COPY_CLASS(LogFilter);
};
// LogFilterByLevel filters out all the messages at the specified level.
class LogFilterByLevel : public LogFilter
{
public:
LogFilterByLevel() { }
void SetLevelToIgnore(int flags)
{
m_logLevelToIgnore = flags;
}
protected:
bool Filter(GLogLevelFlags log_level,
const GLogField* WXUNUSED(fields),
gsize WXUNUSED(n_fields)) const wxOVERRIDE
{
return log_level & m_logLevelToIgnore;
}
private:
int m_logLevelToIgnore;
wxDECLARE_NO_COPY_CLASS(LogFilterByLevel);
};
// LogFilterByMessage filters out all the messages with the specified content.
class LogFilterByMessage : public LogFilter
{
public:
// Objects of this class are supposed to be created with literal strings as
// argument, so don't bother copying the string but just use the pointer.
explicit LogFilterByMessage(const char* message)
: m_message(message)
{
// We shouldn't warn about anything if Install() failed.
m_warnNotFiltered = Install();
}
// Remove this filter when the object goes out of scope.
//
// The dtor also checks if we actually filtered the message and logs a
// trace message with the "gtklog" mask if we didn't: this allows checking
// if the filter is actually being used.
~LogFilterByMessage();
protected:
bool Filter(GLogLevelFlags WXUNUSED(log_level),
const GLogField* fields,
gsize n_fields) const wxOVERRIDE;
private:
const char* const m_message;
mutable bool m_warnNotFiltered;
wxDECLARE_NO_COPY_CLASS(LogFilterByMessage);
};
#endif // wxHAS_GLIB_LOG_WRITER
} // namespace wxGTKImpl
#endif // _WX_GTK_PRIVATE_LOG_H_

View File

@@ -30,6 +30,7 @@
#include "wx/msgout.h"
#include "wx/gtk/private.h"
#include "wx/gtk/private/log.h"
#include "wx/gtk/mimetype.h"
//-----------------------------------------------------------------------------
@@ -178,48 +179,112 @@ bool wxApp::DoIdle()
return keepSource;
}
// Custom Glib log writer: setting it is only possible with glib 2.50 or later.
#if GLIB_CHECK_VERSION(2, 50, 0)
extern "C" {
static GLogWriterOutput
wx_log_writer(GLogLevelFlags log_level,
const GLogField *fields,
gsize n_fields,
gpointer user_data)
{
const wxUIntPtr log_mask = reinterpret_cast<wxUIntPtr>(user_data);
#ifdef wxHAS_GLIB_LOG_WRITER
GLogWriterOutput result;
if (log_level & log_mask)
namespace wxGTKImpl
{
bool LogFilter::ms_installed = false;
LogFilter* LogFilter::ms_first = NULL;
/* static */
GLogWriterOutput
LogFilter::wx_log_writer(GLogLevelFlags log_level,
const GLogField *fields,
gsize n_fields,
gpointer WXUNUSED(user_data))
{
for ( const LogFilter* lf = LogFilter::ms_first; lf; lf = lf->m_next )
{
result = G_LOG_WRITER_HANDLED;
if ( lf->Filter(log_level, fields, n_fields) )
return G_LOG_WRITER_HANDLED;
}
else
return g_log_writer_default(log_level, fields, n_fields, NULL);
}
bool LogFilter::Install()
{
if ( !ms_installed )
{
result = g_log_writer_default(log_level, fields, n_fields, NULL);
if ( glib_check_version(2, 50, 0) != 0 )
{
// No runtime support for log callback, we can't do anything.
return false;
}
g_log_set_writer_func(LogFilter::wx_log_writer, NULL, NULL);
ms_installed = true;
}
return result;
// Put this object in front of the linked list.
m_next = ms_first;
ms_first = this;
return true;
}
void LogFilter::Uninstall()
{
if ( !ms_installed )
{
// We don't do anything at all in this case.
return;
}
// We should be uninstalling only the currently installed filter.
wxASSERT( ms_first == this );
ms_first = m_next;
}
bool LogFilterByMessage::Filter(GLogLevelFlags WXUNUSED(log_level),
const GLogField* fields,
gsize n_fields) const
{
for ( gsize n = 0; n < n_fields; ++n )
{
const GLogField& f = fields[n];
if ( strcmp(f.key, "MESSAGE") == 0 )
{
if ( strcmp(static_cast<const char*>(f.value), m_message) == 0 )
{
// This is the message we want to filter.
m_warnNotFiltered = false;
return true;
}
}
}
return false;
}
LogFilterByMessage::~LogFilterByMessage()
{
Uninstall();
if ( m_warnNotFiltered )
{
wxLogTrace("gtklog", "Message \"%s\" wasn't logged.", m_message);
}
}
} // namespace wxGTKImpl
/* static */
void wxApp::GTKSuppressDiagnostics(int flags)
{
if (glib_check_version(2, 50, 0) == 0)
{
g_log_set_writer_func(
wx_log_writer,
(wxUIntToPtr)(flags == -1 ? G_LOG_LEVEL_MASK : flags),
NULL);
}
static wxGTKImpl::LogFilterByLevel s_logFilter;
s_logFilter.SetLevelToIgnore(flags);
s_logFilter.Install();
}
#else // glib < 2.50
#else // !wxHAS_GLIB_LOG_WRITER
/* static */
void wxApp::GTKSuppressDiagnostics(int WXUNUSED(flags))
{
// We can't do anything here.
}
#endif // glib >=/< 2.50
#endif // wxHAS_GLIB_LOG_WRITER/!wxHAS_GLIB_LOG_WRITER
//-----------------------------------------------------------------------------
// wxApp

View File

@@ -26,6 +26,7 @@
#include "wx/gtk/private.h"
#include "wx/gtk/private/image.h"
#include "wx/gtk/private/log.h"
#include "wx/gtk/private/stylecontext.h"
//-----------------------------------------------------------------------------
@@ -426,6 +427,12 @@ wxNotebookPage *wxNotebook::DoRemovePage( size_t page )
if ( !client )
return NULL;
// Suppress bogus assertion failures happening deep inside ATK (used by
// GTK) that can't be avoided in any other way, see #22176.
wxGTKImpl::LogFilterByMessage filterLog(
"gtk_notebook_get_tab_label: assertion 'list != NULL' failed"
);
// we don't need to unparent the client->m_widget; GTK+ will do
// that for us (and will throw a warning if we do it!)
gtk_notebook_remove_page( GTK_NOTEBOOK(m_widget), page );