on bcbc40d help sign in
unity8/plugins/Unity/Debug/consolelog.cpp
221 lines 5.2 kB history raw
1/*
2 * Copyright 2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
17#include "consolelog.h"
19#include <unistd.h>
20#include <stdio.h>
21#include <thread>
22#include <iostream>
23#include <sys/ioctl.h>
25#include <functional>
27namespace
30int wait_fd(std::function<int(void)> fn)
32 int ret = -1;
33 bool fd_blocked = false;
34 do
35 {
36 ret = fn();
37 fd_blocked = (errno == EINTR || errno == EBUSY);
38 if (fd_blocked)
39 QThread::msleep(10);
40 }
41 while (ret < 0);
42 return ret;
45#define PIPE_WAIT(val) wait_fd(std::bind(pipe, val))
46#define DUP_WAIT(fd) wait_fd(std::bind(dup, fd))
47#define DUP2_WAIT(fd1, fd2) wait_fd(std::bind(dup2, fd1, fd2))
48#define CLOSE_WAIT(fd) wait_fd(std::bind(close, fd))
52LogRedirector::LogRedirector()
53 : m_ref(0)
54 , m_stop(false)
56 setObjectName("ConsoleLog");
58 // make stdout & stderr streams unbuffered
59 // so that we don't need to flush the streams
60 // before capture and after capture
61 // (fflush can cause a deadlock if the stream is currently being used)
62 setvbuf(stdout,NULL,_IONBF,0);
63 setvbuf(stderr,NULL,_IONBF,0);
66void LogRedirector::run()
68 PIPE_WAIT(m_pipe);
70 int oldStdOut = DUP_WAIT(fileno(stdout));
71 int oldStdErr = DUP_WAIT(fileno(stderr));
73 DUP2_WAIT(m_pipe[WRITE], fileno(stdout));
74 DUP2_WAIT(m_pipe[WRITE], fileno(stderr));
75 CLOSE_WAIT(m_pipe[WRITE]);
77 while(true) {
78 {
79 QMutexLocker lock(&m_mutex);
80 if (m_stop) break;
81 }
82 checkLog();
83 QThread::msleep(50);
84 }
86 DUP2_WAIT(oldStdOut, fileno(stdout));
87 DUP2_WAIT(oldStdErr, fileno(stderr));
89 CLOSE_WAIT(oldStdOut);
90 CLOSE_WAIT(oldStdErr);
91 CLOSE_WAIT(m_pipe[READ]);
95void LogRedirector::checkLog()
97 // Do not allow read to block with no data.
98 // If we stop the thread while waiting a read,
99 // it will block the main thread waiting for this thread to stop.
100 int count = 0;
101 ioctl(m_pipe[READ], FIONREAD, &count);
102 if (count <= 0) return;
104 std::string captured;
105 std::string buf;
106 const int bufSize = 1024;
107 buf.resize(bufSize);
108 int bytesRead = 0;
109 bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
110 while(bytesRead == bufSize)
111 {
112 captured += buf;
113 bytesRead = 0;
114 bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
115 }
116 if (bytesRead > 0)
117 {
118 buf.resize(bytesRead);
119 captured += buf;
120 }
122 if (!captured.empty()) {
123 Q_EMIT log(QString::fromStdString(captured));
124 }
127LogRedirector *LogRedirector::instance()
129 static LogRedirector* log = nullptr;
130 if (!log) {
131 log = new LogRedirector();
132 }
133 return log;
136void LogRedirector::add(ConsoleLog* logger)
138 QMutexLocker lock(&m_mutex);
139 connect(this, &LogRedirector::log, logger, &ConsoleLog::logged, Qt::UniqueConnection);
141 m_ref++;
142 if (!LogRedirector::instance()->isRunning()) {
143 m_stop = false;
144 LogRedirector::instance()->start();
145 }
148void LogRedirector::remove(ConsoleLog* logger)
150 QMutexLocker lock(&m_mutex);
151 disconnect(this, &LogRedirector::log, logger, &ConsoleLog::logged);
153 m_ref = qMax(m_ref-1, 0);
154 if (m_ref == 0 && LogRedirector::instance()->isRunning()) {
155 m_stop = true;
156 lock.unlock();
157 LogRedirector::instance()->wait();
158 }
161ConsoleLog::ConsoleLog(QObject *parent)
162 : QObject(parent)
163 , m_enabled(false)
164 , m_maxLines(60)
166 auto updateEnabled = [this]() {
167 if (m_enabled) {
168 LogRedirector::instance()->add(this);
169 } else {
170 LogRedirector::instance()->remove(this);
171 }
172 };
173 connect(this, &ConsoleLog::enabledChanged, this, updateEnabled);
176ConsoleLog::~ConsoleLog()
178 if (m_enabled) {
179 LogRedirector::instance()->remove(this);
180 }
183void ConsoleLog::setEnabled(bool enabled)
185 if (m_enabled == enabled) {
186 return;
187 }
189 m_enabled = enabled;
190 Q_EMIT enabledChanged();
193QString ConsoleLog::out() const
195 return m_out.join("\n");
198void ConsoleLog::setMaxLines(int maxLines)
200 if (m_maxLines == maxLines) {
201 return;
202 }
204 m_maxLines = maxLines;
205 while (m_out.count() > m_maxLines) {
206 m_out.removeLast();
207 }
208 Q_EMIT outChanged();
209 Q_EMIT maxLinesChanged();
212void ConsoleLog::logged(QString captured)
214 QStringList li = captured.split("\n", QString::SkipEmptyParts);
215 li << m_out;
216 m_out = li;
217 while (m_out.count() > m_maxLines) {
218 m_out.removeLast();
219 }
220 Q_EMIT outChanged();