hydrogen 1.2.3
Reporter.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
4 *
5 * http://www.hydrogen-music.org
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY, without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see https://www.gnu.org/licenses
19 *
20 */
21#include <iostream>
22#include <signal.h>
23#include "core/Hydrogen.h"
25#include "core/Logger.h"
26#include "Reporter.h"
27
28
29QString Reporter::m_sPrefix = "Fatal error in: ";
30
31std::set<QProcess *> Reporter::m_children;
32
33using namespace H2Core;
34
35void Reporter::addLine( QString s )
36{
37 // Keep only a few lines of the output
38 while ( m_lines.size() > 128 ) {
39 // Record context
40 if ( m_lines[0].startsWith( m_sPrefix )) {
43 }
44 m_lines.pop_front();
45 }
46 if ( m_lines.size() == 0 ) {
47 m_lines.push_back( QString( "" ) );
48 }
49
50 QStringList parts = s.split( "\n" );
51 QString sLastLine = m_lines.back();
52 m_lines.pop_back();
53 // Append the first part to the last line
54 sLastLine += parts.takeFirst();
55 m_lines.push_back( sLastLine );
56 for ( auto &s : parts ) {
57 m_lines.push_back( s );
58 }
59}
60
61Reporter::Reporter( QProcess *pChild )
62{
63 assert( pChild != nullptr );
64 this->m_pChild = pChild;
65 m_children.insert( pChild );
66
67 connect( pChild, &QProcess::readyReadStandardOutput,
69 connect( pChild, &QProcess::readyReadStandardError,
71 connect( pChild, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
72 this, &Reporter::on_finished );
73}
74
76{
77 m_children.erase( this->m_pChild );
78}
79
81{
82 assert( m_pChild );
83 while ( m_pChild->state() != QProcess::NotRunning ) {
84 m_pChild->waitForFinished();
85 }
86}
87
88
89void Reporter::report( void )
90{
91 std::cerr.flush();
92 std::cout.flush();
93 QString *pContext = Logger::getCrashContext();
94 if ( pContext != nullptr ) {
95 std::cerr << m_sPrefix.toStdString() << pContext->toStdString()
96 << std::endl;
97 std::cerr.flush();
98 }
99}
100
101
103{
104 std::string s = m_pChild->readAllStandardError().toStdString();
105 std::cerr << s.c_str();
106 addLine( s.c_str() );
107}
108
110{
111 std::string s = m_pChild->readAllStandardOutput().toStdString();
112 std::cout << s.c_str();
113 addLine( s.c_str() );
114}
115
117{
118 qDebug() << "Open log...";
119 QDesktopServices::openUrl( QUrl::fromLocalFile( H2Core::Filesystem::log_file_path() ) );
120}
121
122void Reporter::on_finished( int exitCode, QProcess::ExitStatus exitStatus )
123{
126
127 if ( m_pChild->exitStatus() != QProcess::NormalExit ) {
128
129 char *argv[] = { (char *)"-" };
130 int argc = 1;
131 QApplication app ( argc, argv );
132 app.setApplicationName( "Hydrogen" );
133
134 QString sDetails;
135 for ( QString &s : m_lines ) {
136 // Filter out escape sequences
137 s.remove( "\e[0m" );
138 s.remove( "\e[31m" );
139 s.remove( "\e[32m" );
140 s.remove( "\e[35m" );
141 s.remove( "\e[35;1m" );
142 s.remove( "\e[36m" );
143 sDetails += s + "\n";
144 if ( s.startsWith( m_sPrefix ) ) {
145 m_sContext = s;
146 }
147 }
148
149 QMessageBox msgBox;
150 msgBox.setText( tr( "Hydrogen exited abnormally" ) );
151
152 QString sInformative;
153 if ( !m_sContext.isNull() ) {
154 sInformative = m_sContext + "\n\n";
155 }
156 sInformative += tr( "You can check the Hydrogen issue tracker on Github to see if this issue "
157 "is already known about. "
158 "If not, you can report it there to help the development team get you back on track "
159 "and improve Hydrogen for the future." ) + "\n";
160 msgBox.setInformativeText( sInformative );
161
162 msgBox.setStandardButtons( QMessageBox::Ok );
163 msgBox.setDefaultButton( QMessageBox::Discard );
164 msgBox.setWindowTitle( "Hydrogen" );
165 msgBox.setIcon( QMessageBox::Critical );
166
167 msgBox.setDetailedText( sDetails );
168
169 QPushButton *pLogButton = msgBox.addButton( tr( "Open log file..." ),
170 QMessageBox::ActionRole );
171
172 QPushButton *pIssuesButton = msgBox.addButton( tr( "Github Issue tracker..." ),
173 QMessageBox::ActionRole );
174
175 do {
176 msgBox.exec();
177 QAbstractButton *pPushed = msgBox.clickedButton();
178
179 if ( pLogButton == pPushed ) {
180 QDesktopServices::openUrl( QUrl::fromLocalFile( H2Core::Filesystem::log_file_path() ) );
181
182 } else if ( pPushed == pIssuesButton ) {
183 QDesktopServices::openUrl( QUrl( "https://github.com/hydrogen-music/hydrogen/issues") );
184
185 } else {
186 break;
187 }
188 } while ( true );
189
190 }
191}
192
193void Reporter::handleSignal( int nSignal )
194{
195 // First disable signal handler to allow normal termination
196 signal( nSignal, SIG_DFL );
197
198 for ( QProcess *pChild : m_children ) {
199#ifndef WIN32
200 kill( pChild->processId(), nSignal );
201#else
202 // On Windows, we can't use kill() to pass along the signal we received, so just use
203 // QProcess::terminate()
204 pChild->terminate();
205#endif
206 }
207
208 raise( nSignal );
209}
210
211static void handleSignal( int nSignal ) {
212 Reporter::handleSignal( nSignal );
213}
214
215void Reporter::spawn(int argc, char *argv[])
216{
217 QStringList arguments;
218 for ( int i = 1; i < argc; i++ ) {
219 if ( argv[i] == QString("--child") ) {
220 return;
221 }
222 arguments << QString( argv[i] );
223 }
224
225 QProcess subProcess;
226 arguments << "--child";
227 subProcess.start(argv[0], arguments);
228
229 // Signal handler
230 for ( int nSignal : { SIGINT, SIGTERM
231#ifndef WIN32
232 , SIGHUP
233#endif
234 } ) {
235 signal( nSignal, ::handleSignal );
236 }
237
238 Reporter reporter( &subProcess );
239 reporter.waitForFinished();
240 exit( subProcess.exitCode() );
241}
static void handleSignal(int nSignal)
Definition Reporter.cpp:211
static QString log_file_path()
returns the full path (including filename) of the logfile
static QString * getCrashContext()
Definition Logger.h:136
static void setCrashContext(QString *pContext)
Definition Logger.h:135
Crash reporter class.
Definition Reporter.h:55
void on_openLog(void)
Definition Reporter.cpp:116
static QString m_sPrefix
Definition Reporter.h:61
static std::set< QProcess * > m_children
Definition Reporter.h:68
Reporter(QProcess *child)
Definition Reporter.cpp:61
void on_readyReadStandardError(void)
Definition Reporter.cpp:102
void on_readyReadStandardOutput(void)
Definition Reporter.cpp:109
QProcess * m_pChild
Definition Reporter.h:58
std::deque< QString > m_lines
Definition Reporter.h:59
void addLine(QString s)
Definition Reporter.cpp:35
void on_finished(int exitCode, QProcess::ExitStatus exitStatus)
Definition Reporter.cpp:122
static void report(void)
Report some crash details in a crashing child (mostly, the Logger 'crash context' string)
Definition Reporter.cpp:89
void waitForFinished()
Definition Reporter.cpp:80
static void handleSignal(int nSignal)
Definition Reporter.cpp:193
static void spawn(int argc, char *argv[])
Potentially spawn child process.
Definition Reporter.cpp:215
QString m_sContext
Definition Reporter.h:62