QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
LogRemoteSink.cc
Go to the documentation of this file.
1#include "LogRemoteSink.h"
2
3#include <QtCore/QJsonDocument>
4#include <QtCore/QJsonObject>
5
6#include "LogEntry.h"
7#include "LogFormatter.h"
8#include "QGCCompression.h"
10#include "QGCNetworkHelper.h"
11
12QGC_LOGGING_CATEGORY(LogRemoteSinkLog, "Utilities.LogRemoteSink")
13
14LogRemoteSink::LogRemoteSink(QObject* parent) : QObject(parent), _transport(this)
15{
16 _batchTimer.setSingleShot(true);
17 (void)connect(&_batchTimer, &QChronoTimer::timeout, this, &LogRemoteSink::_flushBatch);
18
19 (void)connect(&_transport, &TransportStrategy::connected, this, [this]() { emit tcpConnectedChanged(); });
20 (void)connect(&_transport, &TransportStrategy::disconnected, this, [this]() { emit tcpConnectedChanged(); });
21 (void)connect(&_transport, &TransportStrategy::errorOccurred, this, [this](const QString& msg) {
22 _setLastError(msg);
23 });
24}
25
26LogRemoteSink::~LogRemoteSink()
27{
28 _flushBatch();
29}
30
31// ---------------------------------------------------------------------------
32// Connection-relevant property setters
33// ---------------------------------------------------------------------------
34
35void LogRemoteSink::setEnabled(bool enabled)
36{
37 if (_enabled == enabled) {
38 return;
39 }
40 _enabled = enabled;
41 if (!_enabled) {
42 _flushBatch();
43 _transport.reset();
44 }
45 emit enabledChanged();
46}
47
48void LogRemoteSink::setHost(const QString& host)
49{
50 if (_transport.host() == host) {
51 return;
52 }
53 _transport.setTarget(host, _transport.port());
54 emit hostChanged();
55}
56
57void LogRemoteSink::setPort(quint16 port)
58{
59 if (_transport.port() == port) {
60 return;
61 }
62 _transport.setTarget(_transport.host(), port);
63 emit portChanged();
64}
65
66void LogRemoteSink::setProtocol(TransportStrategy::Protocol protocol)
67{
68 if (_transport.protocol() == protocol) {
69 return;
70 }
71 _transport.setProtocol(protocol);
72 emit protocolChanged();
73}
74
75void LogRemoteSink::setTlsEnabled(bool enabled)
76{
77 if (_transport.tlsEnabled() == enabled) {
78 return;
79 }
80 _transport.setTlsEnabled(enabled);
81 emit tlsEnabledChanged();
82}
83
84void LogRemoteSink::setTlsVerifyPeer(bool verify)
85{
86 if (_transport.tlsVerifyPeer() == verify) {
87 return;
88 }
89 _transport.setTlsVerifyPeer(verify);
91}
92
93// ---------------------------------------------------------------------------
94// Simple property setters
95// ---------------------------------------------------------------------------
96
97void LogRemoteSink::setVehicleId(const QString& id)
98{
99 if (_vehicleId == id) {
100 return;
101 }
102 _vehicleId = id;
103 emit vehicleIdChanged();
104}
105
106void LogRemoteSink::setCompressionEnabled(bool enabled)
107{
108 if (_compressionEnabled == enabled) {
109 return;
110 }
111 _compressionEnabled = enabled;
113}
114
115void LogRemoteSink::setCompressionLevel(int level)
116{
117 level = qBound(1, level, 9);
118 if (_compressionLevel == level) {
119 return;
120 }
121 _compressionLevel = level;
123}
124
125// ---------------------------------------------------------------------------
126// TLS certificate management
127// ---------------------------------------------------------------------------
128
129void LogRemoteSink::resetBytesSent()
130{
131 _bytesSent = 0;
132 emit bytesSentChanged();
133}
134
135bool LogRemoteSink::loadTlsCaCertificates(const QString& filePath)
136{
137 QString error;
138 const auto certs = QGCNetworkHelper::loadCaCertificates(filePath, &error);
139 if (certs.isEmpty()) {
140 _setLastTlsError(error);
141 return false;
142 }
143 _transport.setTlsCaCertificates(certs);
144 return true;
145}
146
147bool LogRemoteSink::loadTlsClientCertificate(const QString& certPath, const QString& keyPath)
148{
149 QString error;
150 QSslCertificate cert;
151 QSslKey key;
152 if (!QGCNetworkHelper::loadClientCertAndKey(certPath, keyPath, cert, key, &error)) {
153 _setLastTlsError(error);
154 return false;
155 }
156 _transport.setTlsClientCertificate(cert, key);
157 return true;
158}
159
160void LogRemoteSink::setMaxPendingEntries(int max)
161{
162 max = qMax(10, max);
163 if (_maxPendingEntries != max) {
164 _maxPendingEntries = max;
166 }
167}
168
169// ---------------------------------------------------------------------------
170// Entry formatting & compression
171// ---------------------------------------------------------------------------
172
173QByteArray LogRemoteSink::_formatEntry(const LogEntry& entry) const
174{
176 if (!_vehicleId.isEmpty()) {
177 obj[QStringLiteral("v")] = _vehicleId;
178 }
179 return QJsonDocument(obj).toJson(QJsonDocument::Compact);
180}
181
182QByteArray LogRemoteSink::_maybeCompress(const QByteArray& data) const
183{
184 if (!_compressionEnabled) {
185 return data;
186 }
187 return QGCCompression::compress(data, static_cast<QGCCompression::CompressionLevel>(_compressionLevel));
188}
189
190// ---------------------------------------------------------------------------
191// Batched sending
192// ---------------------------------------------------------------------------
193
194void LogRemoteSink::send(const LogEntry& entry)
195{
196 if (!_enabled || _transport.host().isEmpty() || _transport.port() == 0) {
197 return;
198 }
199
200 if (_batch.size() >= _maxPendingEntries) {
201 if (++_droppedEntries == 1 || (_droppedEntries % 100) == 0) {
203 }
204 return;
205 }
206
207 _batch.append(_formatEntry(entry));
208 if (_batch.size() >= kBatchSize) {
209 _batchTimer.stop();
210 _flushBatch();
211 } else if (!_batchTimer.isActive()) {
212 _batchTimer.start();
213 }
214}
215
216void LogRemoteSink::_flushBatch()
217{
218 _batchTimer.stop();
219
220 if (_batch.isEmpty()) {
221 return;
222 }
223
224 QByteArray payload;
225 if (_batch.size() == 1) {
226 payload = _batch.first();
227 } else {
228 int totalSize = 2; // '[' + ']'
229 for (const auto& b : std::as_const(_batch)) {
230 totalSize += b.size() + 1; // entry + ','
231 }
232 payload.reserve(totalSize);
233 payload.append('[');
234 for (int i = 0; i < _batch.size(); ++i) {
235 if (i > 0) {
236 payload.append(',');
237 }
238 payload.append(_batch.at(i));
239 }
240 payload.append(']');
241 }
242
243 const QByteArray data = _maybeCompress(payload);
244 const quint64 sent = _transport.send(data);
245
246 if (sent > 0) {
247 _bytesSent += sent;
248 _batch.clear();
249 emit bytesSentChanged();
250
251 if (!_lastError.isEmpty()) {
252 _lastError.clear();
253 emit lastErrorChanged();
254 }
255 } else {
256 // Transport not ready (e.g. TCP connecting) — keep batch for retry
257 while (_batch.size() > _maxPendingEntries) {
258 _batch.removeFirst();
259 ++_droppedEntries;
260 }
261 }
262}
263
264// ---------------------------------------------------------------------------
265// Error reporting
266// ---------------------------------------------------------------------------
267
268void LogRemoteSink::_setLastError(const QString& error)
269{
270 if (_lastError != error) {
271 _lastError = error;
272 emit lastErrorChanged();
273 emit errorOccurred(error);
274 }
275}
276
277void LogRemoteSink::_setLastTlsError(const QString& error)
278{
279 if (_lastTlsError != error) {
280 _lastTlsError = error;
281 emit lastTlsErrorChanged();
282 }
283}
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void droppedEntriesChanged()
void protocolChanged()
void vehicleIdChanged()
void errorOccurred(const QString &message)
void enabledChanged()
void bytesSentChanged()
void tlsEnabledChanged()
void compressionLevelChanged()
void portChanged()
void lastTlsErrorChanged()
void lastErrorChanged()
void compressionEnabledChanged()
void tlsVerifyPeerChanged()
void maxPendingEntriesChanged()
void hostChanged()
void setTlsClientCertificate(const QSslCertificate &cert, const QSslKey &key)
void errorOccurred(const QString &message)
void setTarget(const QString &host, quint16 port)
Protocol protocol() const
quint16 port() const
void setTlsCaCertificates(const QList< QSslCertificate > &certs)
void setTlsVerifyPeer(bool verify)
quint64 send(const QByteArray &data)
Send pre-formatted payload. Returns bytes sent (0 on failure/pending).
void setTlsEnabled(bool enabled)
QString host() const
bool tlsEnabled() const
bool tlsVerifyPeer() const
void reset()
Reset transports (e.g. after host/port change).
void setProtocol(Protocol protocol)
QJsonObject entryToJson(const LogEntry &e, JsonSchema schema)
QByteArray compress(const QByteArray &data, CompressionLevel level)
QList< QSslCertificate > loadCaCertificates(const QString &filePath, QString *errorOut)
bool loadClientCertAndKey(const QString &certPath, const QString &keyPath, QSslCertificate &certOut, QSslKey &keyOut, QString *errorOut)