hydrogen 1.2.6
Reporter.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2008-2025 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 "Reporter.h"
22#include "Parser.h"
23
24#include <iostream>
25#include <signal.h>
26
27#include <core/Hydrogen.h>
29#include <core/Logger.h>
31
32
33QString Reporter::m_sPrefix = "Fatal error in: ";
35
36std::set<QProcess *> Reporter::m_children;
37
38using namespace H2Core;
39
40void Reporter::addLine( QString s )
41{
42 // Keep only a few lines of the output
43 while ( m_lines.size() > 128 ) {
44 // Record context
45 if ( m_lines[0].startsWith( m_sPrefix )) {
48 }
49 m_lines.pop_front();
50 }
51 if ( m_lines.size() == 0 ) {
52 m_lines.push_back( QString( "" ) );
53 }
54
55 QStringList parts = s.split( "\n" );
56 QString sLastLine = m_lines.back();
57 m_lines.pop_back();
58 // Append the first part to the last line
59 sLastLine += parts.takeFirst();
60 m_lines.push_back( sLastLine );
61 for ( auto &s : parts ) {
62 m_lines.push_back( s );
63 }
64}
65
66Reporter::Reporter( QProcess *pChild )
67{
68 assert( pChild != nullptr );
69 this->m_pChild = pChild;
70 m_children.insert( pChild );
71
72 if ( m_sLogFile.isEmpty() ) {
74 }
75
76 connect( pChild, &QProcess::readyReadStandardOutput,
78 connect( pChild, &QProcess::readyReadStandardError,
80 connect( pChild, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
81 this, &Reporter::on_finished );
82}
83
85{
86 m_children.erase( this->m_pChild );
87}
88
90{
91 assert( m_pChild );
92 while ( m_pChild->state() != QProcess::NotRunning ) {
93 m_pChild->waitForFinished();
94 }
95}
96
97
98void Reporter::report( void )
99{
100 std::cerr.flush();
101 std::cout.flush();
102 QString *pContext = Logger::getCrashContext();
103 if ( pContext != nullptr ) {
104 std::cerr << m_sPrefix.toStdString() << pContext->toStdString()
105 << std::endl;
106 std::cerr.flush();
107 }
108}
109
110
112{
113 std::string s = m_pChild->readAllStandardError().toStdString();
114 std::cerr << s.c_str();
115 addLine( s.c_str() );
116}
117
119{
120 std::string s = m_pChild->readAllStandardOutput().toStdString();
121 std::cout << s.c_str();
122 addLine( s.c_str() );
123}
124
126{
127 qDebug() << "Open log...";
128 QDesktopServices::openUrl( QUrl::fromLocalFile( Reporter::m_sLogFile ) );
129}
130
131void Reporter::on_finished( int exitCode, QProcess::ExitStatus exitStatus )
132{
135
136 if ( m_pChild->exitStatus() != QProcess::NormalExit
137 || m_pChild->exitCode() != 0 ) {
138
139 char *argv[] = { (char *)"-" };
140 int argc = 1;
141 QApplication app ( argc, argv );
142 app.setApplicationName( "Hydrogen" );
143
144 QString sDetails;
145 for ( QString &s : m_lines ) {
146 // Filter out escape sequences
147 s.remove( "\e[0m" );
148 s.remove( "\e[31m" );
149 s.remove( "\e[32m" );
150 s.remove( "\e[35m" );
151 s.remove( "\e[35;1m" );
152 s.remove( "\e[36m" );
153 sDetails += s + "\n";
154 if ( s.startsWith( m_sPrefix ) ) {
155 m_sContext = s;
156 }
157 }
158
159 QMessageBox msgBox;
160 msgBox.setText( tr( "Hydrogen exited abnormally" ) );
161
162 QString sInformative;
163 if ( !m_sContext.isNull() ) {
164 sInformative = m_sContext + "\n\n";
165 }
166 sInformative += tr( "You can check the Hydrogen issue tracker on Github to see if this issue "
167 "is already known about. "
168 "If not, you can report it there to help the development team get you back on track "
169 "and improve Hydrogen for the future." ) + "\n";
170 msgBox.setInformativeText( sInformative );
171
172 msgBox.setStandardButtons( QMessageBox::Ok );
173 msgBox.setDefaultButton( QMessageBox::Discard );
174 msgBox.setWindowTitle( "Hydrogen" );
175 msgBox.setIcon( QMessageBox::Critical );
176
177 msgBox.setDetailedText( sDetails );
178
179 QPushButton *pLogButton = msgBox.addButton( tr( "Open log file..." ),
180 QMessageBox::ActionRole );
181
182 QPushButton *pIssuesButton = msgBox.addButton( tr( "Github Issue tracker..." ),
183 QMessageBox::ActionRole );
184
185 do {
186 msgBox.exec();
187 QAbstractButton *pPushed = msgBox.clickedButton();
188
189 if ( pLogButton == pPushed ) {
190 QDesktopServices::openUrl( QUrl::fromLocalFile( Reporter::m_sLogFile ) );
191
192 } else if ( pPushed == pIssuesButton ) {
193 QDesktopServices::openUrl( QUrl( "https://github.com/hydrogen-music/hydrogen/issues") );
194
195 } else {
196 break;
197 }
198 } while ( true );
199
200 }
201}
202
203void Reporter::handleSignal( int nSignal )
204{
205 // First disable signal handler to allow normal termination
206 signal( nSignal, SIG_DFL );
207
208 for ( QProcess *pChild : m_children ) {
209#ifndef WIN32
210 kill( pChild->processId(), nSignal );
211#else
212 // On Windows, we can't use kill() to pass along the signal we received, so just use
213 // QProcess::terminate()
214 pChild->terminate();
215#endif
216 }
217
218 raise( nSignal );
219}
220
221static void handleSignal( int nSignal ) {
222 Reporter::handleSignal( nSignal );
223}
224
225void Reporter::spawn(int argc, char *argv[])
226{
227 Parser parser;
228 if ( ! parser.parse( argc, argv ) ) {
229 std::cerr << "ERROR: Unable to parse CLI arguments. Abort..."
230 << std::endl;
231 exit( 1 );
232 }
233
234 // --child option was supplied indicating that we do not want to use the
235 // Reporter.
236 if ( parser.getNoReporter() ) {
237 return;
238 }
239
240 if ( ! parser.getLogFile().isEmpty() ) {
242 }
243
244 QStringList arguments;
245 for ( int ii = 1; ii < argc; ii++ ) {
246 arguments << QString( argv[ii] );
247 }
248 arguments << "--child";
249
250 QProcess subProcess;
251 subProcess.start(argv[0], arguments);
252
253 // Signal handler
254 for ( int nSignal : { SIGINT, SIGTERM
255#ifndef WIN32
256 , SIGHUP
257#endif
258 } ) {
259 signal( nSignal, ::handleSignal );
260 }
261
262 Reporter reporter( &subProcess );
263 reporter.waitForFinished();
264 exit( subProcess.exitCode() );
265}
static void handleSignal(int nSignal)
Definition Reporter.cpp:221
static QString log_file_path()
returns the full path (including filename) of the logfile
static QString * getCrashContext()
Definition Logger.h:145
static void setCrashContext(QString *pContext)
Definition Logger.h:144
Reusable parser for provided command line arguments.
Definition Parser.h:46
const QString & getLogFile() const
Definition Parser.h:68
bool parse(int argc, char *argv[])
Definition Parser.cpp:40
bool getNoReporter() const
Definition Parser.h:90
void on_openLog(void)
Definition Reporter.cpp:125
static QString m_sPrefix
Definition Reporter.h:61
static std::set< QProcess * > m_children
Definition Reporter.h:69
Reporter(QProcess *child)
Definition Reporter.cpp:66
void on_readyReadStandardError(void)
Definition Reporter.cpp:111
void on_readyReadStandardOutput(void)
Definition Reporter.cpp:118
QProcess * m_pChild
Definition Reporter.h:58
static QString m_sLogFile
Definition Reporter.h:63
std::deque< QString > m_lines
Definition Reporter.h:59
void addLine(QString s)
Definition Reporter.cpp:40
void on_finished(int exitCode, QProcess::ExitStatus exitStatus)
Definition Reporter.cpp:131
static void report(void)
Report some crash details in a crashing child (mostly, the Logger 'crash context' string)
Definition Reporter.cpp:98
void waitForFinished()
Definition Reporter.cpp:89
static void handleSignal(int nSignal)
Definition Reporter.cpp:203
static void spawn(int argc, char *argv[])
Potentially spawn child process.
Definition Reporter.cpp:225
QString m_sContext
Definition Reporter.h:62